用scala中的重载构造函数定义你自己的exception

在Javaexception至less有这四个构造函数:

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

如果你想定义自己的扩展,你只需要声明一个派生的exception,并实现每个所需的构造函数调用相应超级构造函数

你怎么能在scala中实现同样的function呢?

到目前为止,我看到这篇文章和这个答案 ,但我怀疑必须有一个更简单的方法来实现这样一个共同的东西

cause默认值为空。 而对于message ,则是cause.toString()或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 

所以你可以使用默认值:

 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 

好吧,这是迄今为止我发现的最好的

 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) } 

这样你可以使用“new MissingConfigurationException”或者伴随对象的apply方法

无论如何,我仍然感到惊讶的是没有一个简单的方法来实现它

您可以使用Throwable.initCause

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

对我来说,似乎有三种不同的需求彼此之间有一种dynamic的紧张关系:

  1. RuntimeException的扩展的方便性; 即编写最小代码来创buildRuntimeException的后代
  2. 客户感觉易用性; 即在呼叫站点写最less的代码
  3. 客户希望避免将可怕的Java null泄漏到他们的代码中

如果一个人不关心数字3,那么这个答案 (这个同行)似乎很简洁。

但是,如果在尽量接近数字1和2的情况下将值设置为3,则下面的解决scheme将Java null泄漏有效地封装到您的Scala API中。

 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) } 

如果你想MyRuntimeException在实际使用MyRuntimeException地方使用new ,添加这个伴随对象(它只是将所有的应用调用转发给现有的“主”类构造函数):

 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 ) } 

就个人而言,我更愿意在尽可能多的代码中实际抑制new操作符的使用,从而减轻未来可能的重构。 如果说这个重构对于工厂模式来说是非常有用的。 我的最终结果虽然比较详细,但对客户来说应该是相当不错的。

 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 ) 


探索更复杂的RuntimeException模式:

从原始问题来看,这只是一个小小的飞跃,希望为包或API创build专门的RuntimeException的生态系统。 这个想法是定义一个“根” RuntimeException ,从中可以创build一个特定的后代exception的新生态系统。 对我来说,使用catchmatch更容易利用特定types的错误是非常重要的。

例如,我有一个validate方法,在允许创build一个案例类之前validation一组条件。 每个失败的条件都会生成一个RuntimeException实例。 然后该方法返回RuntimeInstance的列表。 这使得客户有能力决定他们如何处理回应。 throw列表持有exception,扫描列表中的具体内容,并throw或只是推动整个事情的调用链,而无需使用非常昂贵的JVM throw命令。

这个特定的问题空间有RuntimeException三个不同的后代,一个抽象( FailedPrecondition )和两个具体的( FailedPreconditionMustBeNonEmptyListFailedPreconditionsException )。

首先, FailedPreconditionRuntimeException的直接后裔,与MyRuntimeException非常相似,并且是抽象的(以防止直接实例化)。 FailedPrecondition有一个“伴随对象特征”, FailedPreconditionObject作为实例化工厂(压制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 ) 

第二个FailedPreconditionMustBeNonEmptyList是一个间接的RuntimeException后代,并且是FailedPrecondition的直接具体实现。 它定义了一个伴侣对象和一个类。 随FailedPreconditionObject对象扩展了特征FailedPreconditionObject 。 而类只是扩展了抽象类FailedPrecondition ,并final标记为防止进一步扩展。

 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 ) 

第三, FailedPreconditionsExceptionRuntimeException的直接后裔,它包装了FailedPrecondition List ,然后dynamicpipe理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)) 

然后将所有这些整合在一起并整理好,我将FailedPreconditionFailedPreconditionMustBeNonEmptyList放在对象FailedPreconditionsException 。 这就是最终的结果:

 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)) 

这就是客户使用上面的代码创build自己的exception派生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 ) 

然后使用这个exception看起来像这样:

 throw FailedPreconditionMustBeNonEmptyString() 

除了回答原来的问题之外,我还做了很多工作,因为我发现在Scala-ifying RuntimeException中find任何接近于特定和全面的东西是非常困难的,或者扩展到更一般的“exception生态系统” 。

我很乐意听到我的解决scheme集中的反馈(除了“哇,这对我来说太冗长了”)。 我会喜欢任何额外的优化或减less冗长的方式,而不是丢失我为这种模式的客户产生的任何价值或简洁。

try / catch块中的Scala模式匹配在接口上工作。 我的解决scheme是使用exception名称的接口,然后使用单独的类实例。

 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") } } 

实例化MyClass将输出“oops”。

这是类似的方法@罗曼 – 鲍里索夫,但更多的types安全。

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

然后,可以用Java方式创buildexception:

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