web-dev-qa-db-fra.com

définir vos propres exceptions avec des constructeurs surchargés dans scala

En Java, les exceptions ont au moins ces quatre constructeurs:

Exception() 
Exception(String message) 
Exception(String message, Throwable cause) 
Exception(Throwable cause) 

Si vous voulez définir vos propres extensions, il vous suffit de déclarer une exception descendante et d'implémenter chaque constructeur souhaité en appelant le super constructeur correspondant.

Comment pouvez-vous réaliser la même chose en scala?

jusqu'à présent, j'ai vu cet article et cette SO réponse , mais je soupçonne qu'il doit exister un moyen plus facile de réaliser une chose aussi commune

36
opensas

La valeur par défaut de cause est null. Et pour message c'est cause.toString() ou null:

val e1 = new RuntimeException()

e.getCause
// res1: Java.lang.Throwable = null

e.getMessage
//res2: Java.lang.String = null

val cause = new RuntimeException("cause msg")
val e2 = new RuntimeException(cause)

e.getMessage()
//res3: String = Java.lang.RuntimeException: cause msg

Vous pouvez donc simplement utiliser les valeurs par défaut:

class MyException(message: String = null, cause: Throwable = null) extends
  RuntimeException(MyException.defaultMessage(message, cause), cause)

object MyException {
  def defaultMessage(message: String, cause: Throwable) =
    if (message != null) message
    else if (cause != null) cause.toString()
    else null
}

// usage:
new MyException(cause = myCause)
// res0: MyException = MyException: Java.lang.RuntimeException: myCause msg
57
senia

eh bien, c'est le meilleur que j'ai trouvé jusqu'à présent

class MissingConfigurationException private(ex: RuntimeException) extends RuntimeException(ex) {
  def this(message:String) = this(new RuntimeException(message))
  def this(message:String, throwable: Throwable) = this(new RuntimeException(message, throwable))
}

object MissingConfigurationException {
  def apply(message:String) = new MissingConfigurationException(message)
  def apply(message:String, throwable: Throwable) = new MissingConfigurationException(message, throwable)
}

de cette façon, vous pouvez utiliser la "nouvelle MissingConfigurationException" ou la méthode apply de l'objet compagnon.

Quoi qu'il en soit, je suis toujours surpris qu'il n'y ait pas de moyen plus simple d'y parvenir

11
opensas

Vous pouvez utiliser Throwable.initCause.

class MyException (message: String, cause: Throwable) 
  extends RuntimeException(message) {
    if (cause != null)
      initCause(cause)

    def this(message: String) = this(message, null)  
}
6
Roman Borisov

Pour moi, il apparaît qu'il existe trois besoins différents qui ont une tension dynamique les uns avec les autres:

  1. La commodité de l’extendeur de RuntimeException; c'est-à-dire un code minimal à écrire pour créer un descendant de RuntimeException
  2. La facilité d'utilisation perçue par le client; c'est-à-dire un code minimal à écrire sur le site de l'appel
  3. Préférence des clients d'éviter de laisser pénétrer le redoutable Java null dans leur code

Si on se fiche du numéro 3, alors cette réponse (un pair à celui-ci) semble assez succinct.

Cependant, si on valorise le nombre 3 en essayant de se rapprocher le plus possible des nombres 1 et 2, la solution ci-dessous encapsule efficacement la fuite Java null dans votre API Scala.

class MyRuntimeException (
  val optionMessage: Option[String],
  val optionCause: Option[Throwable],
  val isEnableSuppression: Boolean,
  val isWritableStackTrace: Boolean
) extends RuntimeException(
  optionMessage match {
    case Some(string) => string
    case None => null
  },
  optionCause match {
    case Some(throwable) => throwable
    case None => null
  },
  isEnableSuppression,
  isWritableStackTrace
) {
  def this() =
    this(None, None, false, false)
  def this(message: String) =
    this(Some(message), None, false, false)
  def this(cause: Throwable) =
    this(None, Some(cause), false, false)
  def this(message: String, cause: Throwable) =
    this(Some(message), Some(cause), false, false)
}

Et si vous souhaitez éviter de devoir utiliser newMyRuntimeException est réellement utilisé, ajoutez cet objet compagnon (qui transmet uniquement tous les appels apply au constructeur de classe "maître" existant):

object MyRuntimeException {
  def apply: MyRuntimeException =
    MyRuntimeException()
  def apply(message: String): MyRuntimeException =
    MyRuntimeException(optionMessage = Some(message))
  def apply(cause: Throwable): MyRuntimeException =
    MyRuntimeException(optionCause = Some(cause))
  def apply(message: String, cause: Throwable): MyRuntimeException =
    MyRuntimeException(optionMessage = Some(message), optionCause = Some(cause))
  def apply(
    optionMessage: Option[String] = None,
    optionCause: Option[Throwable] = None,
    isEnableSuppression: Boolean = false,
    isWritableStackTrace: Boolean = false
  ): MyRuntimeException =
    new MyRuntimeException(
      optionMessage,
      optionCause,
      isEnableSuppression,
      isWritableStackTrace
    )
}

Personnellement, je préfère supprimer l’utilisation de l’opérateur new dans le plus de code possible afin de faciliter les futurs refactorings. Cela est particulièrement utile si le refactoring arrive fortement au motif Factory. Mon résultat final, bien que plus détaillé, devrait être assez agréable pour les clients.

object MyRuntimeException {
  def apply: MyRuntimeException =
    MyRuntimeException()
  def apply(message: String): MyRuntimeException =
    MyRuntimeException(optionMessage = Some(message))
  def apply(cause: Throwable): MyRuntimeException =
    MyRuntimeException(optionCause = Some(cause))
  def apply(message: String, cause: Throwable): MyRuntimeException =
    MyRuntimeException(optionMessage = Some(message), optionCause = Some(cause))
  def apply(
    optionMessage: Option[String] = None,
    optionCause: Option[Throwable] = None,
    isEnableSuppression: Boolean = false,
    isWritableStackTrace: Boolean = false
  ): MyRuntimeException =
    new MyRuntimeException(
      optionMessage,
      optionCause,
      isEnableSuppression,
      isWritableStackTrace
    )
}

class MyRuntimeException private[MyRuntimeException] (
  val optionMessage: Option[String],
  val optionCause: Option[Throwable],
  val isEnableSuppression: Boolean,
  val isWritableStackTrace: Boolean
) extends RuntimeException(
  optionMessage match {
    case Some(string) => string
    case None => null
  },
  optionCause match {
    case Some(throwable) => throwable
    case None => null
  },
  isEnableSuppression,
  isWritableStackTrace
)


Exploration d'un modèle plus sophistiqué RuntimeException: 

Vouloir créer un écosystème de RuntimeExceptions spécialisés pour un package ou une API n’est qu’un petit bond de la question initiale. L'idée est de définir une "racine" RuntimeException à partir de laquelle un nouvel écosystème d'exceptions descendantes spécifiques peut être créé. Pour moi, il était important de rendre l'utilisation de catch et match beaucoup plus facile à exploiter pour des types d'erreur spécifiques.

Par exemple, j'ai défini une méthode validate qui vérifie un ensemble de conditions avant de permettre la création d'une classe de cas. Chaque condition qui échoue génère une instance RuntimeException. Et puis la liste de RuntimeInstances est renvoyée par la méthode. Cela donne au client la possibilité de décider comment il souhaite gérer la réponse. throw l'exception contenant la liste, recherchez dans la liste quelque chose de spécifique et throw ou placez simplement le tout dans la chaîne d'appels sans engager la très coûteuse commande JVM throw.

Cet espace à problèmes a trois descendants différents de RuntimeException, un résumé (FailedPrecondition) et deux éléments concrets (FailedPreconditionMustBeNonEmptyList et FailedPreconditionsException).

Le premier, FailedPrecondition, est un descendant direct de RuntimeException, très similaire à MyRuntimeException et est abstrait (pour éviter les instanciations directes). FailedPrecondition a un "trait d'objet compagnon", FailedPreconditionObject qui agit comme une fabrique d'instanciation (en supprimant l'opérateur new).

trait FailedPreconditionObject[F <: FailedPrecondition] {
  def apply: F =
    apply()

  def apply(message: String): F =
    apply(optionMessage = Some(message))

  def apply(cause: Throwable): F =
    apply(optionCause = Some(cause))

  def apply(message: String, cause: Throwable): F =
    apply(optionMessage = Some(message), optionCause = Some(cause))

  def apply(
      optionMessage: Option[String] = None
    , optionCause: Option[Throwable] = None
    , isEnableSuppression: Boolean = false
    , isWritableStackTrace: Boolean = false
  ): F
}
abstract class FailedPrecondition (
  val optionMessage: Option[String],
  val optionCause: Option[Throwable],
  val isEnableSuppression: Boolean,
  val isWritableStackTrace: Boolean
) extends RuntimeException(
  optionMessage match {
    case Some(string) => string
    case None => null
  },
  optionCause match {
    case Some(throwable) => throwable
    case None => null
  },
  isEnableSuppression,
  isWritableStackTrace
)

Le second, FailedPreconditionMustBeNonEmptyList, est un descendant indirect de RuntimeException et une implémentation directe et concrète de FailedPrecondition. Il définit à la fois un objet compagnon et une classe. L'objet compagnon étend le trait FailedPreconditionObject. Et la classe étend simplement la classe abstraite FailedPrecondition et la marque final pour empêcher toute extension supplémentaire.

object FailedPreconditionMustBeNonEmptyList extends FailedPreconditionObject[FailedPreconditionMustBeNonEmptyList] {
  def apply(
      optionMessage: Option[String] = None
    , optionCause: Option[Throwable] = None
    , isEnableSuppression: Boolean = false
    , isWritableStackTrace: Boolean = false
  ): FailedPreconditionMustBeNonEmptyList =
    new FailedPreconditionMustBeNonEmptyList(
        optionMessage
      , optionCause
      , isEnableSuppression
      , isWritableStackTrace
    )
}
final class FailedPreconditionMustBeNonEmptyList private[FailedPreconditionMustBeNonEmptyList] (
    optionMessage: Option[String]
  , optionCause: Option[Throwable]
  , isEnableSuppression: Boolean
  , isWritableStackTrace: Boolean
) extends
  FailedPrecondition(
      optionMessage
    , optionCause
    , isEnableSuppression
    , isWritableStackTrace
  )

Le troisième, FailedPreconditionsException, est un descendant direct de RuntimeException qui enveloppe un List de FailedPreconditions, puis gère de manière dynamique l’émission du message d’exception.

object FailedPreconditionsException {
  def apply(failedPrecondition: FailedPrecondition): FailedPreconditionsException =
    FailedPreconditionsException(List(failedPrecondition))
  def apply(failedPreconditions: List[FailedPrecondition]): FailedPreconditionsException =
    tryApply(failedPreconditions).get
  def tryApply(failedPrecondition: FailedPrecondition): Try[FailedPreconditionsException] =
    tryApply(List(failedPrecondition))
  def tryApply(failedPreconditions: List[FailedPrecondition]): Try[FailedPreconditionsException] =
    if (failedPreconditions.nonEmpty)
      Success(new FailedPreconditionsException(failedPreconditions))
    else
      Failure(FailedPreconditionMustBeNonEmptyList())
  private def composeMessage(failedPreconditions: List[FailedPrecondition]): String =
    if (failedPreconditions.size > 1)
      s"failed preconditions [${failedPreconditions.size}] have occurred - ${failedPreconditions.map(_.optionMessage.getOrElse("")).mkString("|")}"
    else
      s"failed precondition has occurred - ${failedPreconditions.head.optionMessage.getOrElse("")}"
}
final class FailedPreconditionsException private[FailedPreconditionsException] (
  val failedPreconditions: List[FailedPrecondition]
) extends RuntimeException(FailedPreconditionsException.composeMessage(failedPreconditions))

Et puis, en rassemblant tout cela et en rangeant les choses, je place à la fois FailedPrecondition et FailedPreconditionMustBeNonEmptyList dans l'objet FailedPreconditionsException. Et voici à quoi ressemble le résultat final:

object FailedPreconditionsException {
  trait FailedPreconditionObject[F <: FailedPrecondition] {
    def apply: F =
      apply()

    def apply(message: String): F =
      apply(optionMessage = Some(message))

    def apply(cause: Throwable): F =
      apply(optionCause = Some(cause))

    def apply(message: String, cause: Throwable): F =
      apply(optionMessage = Some(message), optionCause = Some(cause))

    def apply(
        optionMessage: Option[String] = None
      , optionCause: Option[Throwable] = None
      , isEnableSuppression: Boolean = false
      , isWritableStackTrace: Boolean = false
    ): F
  }
  abstract class FailedPrecondition (
      val optionMessage: Option[String]
    , val optionCause: Option[Throwable]
    , val isEnableSuppression: Boolean
    , val isWritableStackTrace: Boolean
  ) extends RuntimeException(
    optionMessage match {
      case Some(string) => string
      case None => null
    },
    optionCause match {
      case Some(throwable) => throwable
      case None => null
    },
    isEnableSuppression,
    isWritableStackTrace
  )

  object FailedPreconditionMustBeNonEmptyList extends FailedPreconditionObject[FailedPreconditionMustBeNonEmptyList] {
    def apply(
        optionMessage: Option[String] = None
      , optionCause: Option[Throwable] = None
      , isEnableSuppression: Boolean = false
      , isWritableStackTrace: Boolean = false
    ): FailedPreconditionMustBeNonEmptyList =
      new FailedPreconditionMustBeNonEmptyList(
          optionMessage
        , optionCause
        , isEnableSuppression
        , isWritableStackTrace
      )
  }
  final class FailedPreconditionMustBeNonEmptyList private[FailedPreconditionMustBeNonEmptyList] (
      optionMessage: Option[String]
    , optionCause: Option[Throwable]
    , isEnableSuppression: Boolean
    , isWritableStackTrace: Boolean
  ) extends
    FailedPrecondition(
        optionMessage
      , optionCause
      , isEnableSuppression
      , isWritableStackTrace
    )

  def apply(failedPrecondition: FailedPrecondition): FailedPreconditionsException =
    FailedPreconditionsException(List(failedPrecondition))

  def apply(failedPreconditions: List[FailedPrecondition]): FailedPreconditionsException =
    tryApply(failedPreconditions).get

  def tryApply(failedPrecondition: FailedPrecondition): Try[FailedPreconditionsException] =
    tryApply(List(failedPrecondition))

  def tryApply(failedPreconditions: List[FailedPrecondition]): Try[FailedPreconditionsException] =
    if (failedPreconditions.nonEmpty)
      Success(new FailedPreconditionsException(failedPreconditions))
    else
      Failure(FailedPreconditionMustBeNonEmptyList())
  private def composeMessage(failedPreconditions: List[FailedPrecondition]): String =
    if (failedPreconditions.size > 1)
      s"failed preconditions [${failedPreconditions.size}] have occurred - ${failedPreconditions.map(_.optionMessage.getOrElse("")).mkString("|")}"
    else
      s"failed precondition has occurred - ${failedPreconditions.head.optionMessage.getOrElse("")}"
}
final class FailedPreconditionsException private[FailedPreconditionsException] (
  val failedPreconditions: List[FailedPreconditionsException.FailedPrecondition]
) extends RuntimeException(FailedPreconditionsException.composeMessage(failedPreconditions))

Et voici à quoi ressemblerait un client qui utilise le code ci-dessus pour créer sa propre dérivation d’exception appelée FailedPreconditionMustBeNonEmptyString:

object FailedPreconditionMustBeNonEmptyString extends FailedPreconditionObject[FailedPreconditionMustBeNonEmptyString] {
  def apply(
      optionMessage: Option[String] = None
    , optionCause: Option[Throwable] = None
    , isEnableSuppression: Boolean = false
    , isWritableStackTrace: Boolean = false
  ): FailedPreconditionMustBeNonEmptyString =
    new FailedPreconditionMustBeNonEmptyString(
        optionMessage
      , optionCause
      , isEnableSuppression
      , isWritableStackTrace
    )
}
final class FailedPreconditionMustBeNonEmptyString private[FailedPreconditionMustBeNonEmptyString] (
    optionMessage: Option[String]
  , optionCause: Option[Throwable]
  , isEnableSuppression: Boolean
  , isWritableStackTrace: Boolean
) extends
  FailedPrecondition(
      optionMessage
    , optionCause
    , isEnableSuppression
    , isWritableStackTrace
  )

Et puis l'utilisation de cette exception ressemble à ceci:

throw FailedPreconditionMustBeNonEmptyString()

Je suis allé bien au-delà de la réponse à la question initiale car il m'est si difficile de trouver quelque chose de proche et de complet dans Scala-ifying RuntimeException ou de m'étendre dans "l'écosystème d'exception" plus général avec lequel je me suis senti si à l'aise à Java .

J'aimerais entendre des commentaires (autres que des variations sur "Wow! C'est trop verbeux pour moi.") Sur l'ensemble de mes solutions. Et j'adorerais toute optimisation supplémentaire ou tout moyen de réduire la verbosité SANS PERDRE la valeur ou la nuance que j'ai générée pour les clients de ce modèle.

5
chaotic3quilibrium

Voici une approche similaire à celle de @ roman-borisov mais plus typée ..__

case class ShortException(message: String = "", cause: Option[Throwable] = None)
    extends Exception(message) {
  cause.foreach(initCause)
}

Ensuite, vous pouvez créer des exceptions de la manière Java:

throw ShortException() 
throw ShortException(message) 
throw ShortException(message, Some(cause)) 
throw ShortException(cause = Some(cause)) 
0
matfax

La correspondance de modèle Scala dans les blocs try/catch fonctionne sur les interfaces. Ma solution consiste à utiliser une interface pour le nom de l'exception, puis à utiliser des instances de classe distinctes.

trait MyException extends RuntimeException

class MyExceptionEmpty() extends RuntimeException with MyException

class MyExceptionStr(msg: String) extends RuntimeException(msg) with MyException

class MyExceptionEx(t: Throwable) extends RuntimeException(t) with MyException

object MyException {
  def apply(): MyException = new MyExceptionEmpty()
  def apply(msg: String): MyException = new MyExceptionStr(msg)
  def apply(t: Throwable): MyException = new MyExceptionEx(t)
}

class MyClass {
  try {
    throw MyException("oops")
  } catch {
    case e: MyException => println(e.getMessage)
    case _: Throwable => println("nope")
  }
}

L'instanciation de MyClass produira "oops".

0
Jose Fernandez