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
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
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
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)
}
Pour moi, il apparaît qu'il existe trois besoins différents qui ont une tension dynamique les uns avec les autres:
RuntimeException
; c'est-à-dire un code minimal à écrire pour créer un descendant de RuntimeException
null
dans leur codeSi 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 new
où MyRuntimeException
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 RuntimeException
s 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 RuntimeInstance
s 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 FailedPrecondition
s, 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.
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))
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".