用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的紧张关系:
-
RuntimeException
的扩展的方便性; 即编写最小代码来创buildRuntimeException
的后代 - 客户感觉易用性; 即在呼叫站点写最less的代码
- 客户希望避免将可怕的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的新生态系统。 对我来说,使用catch
和match
更容易利用特定types的错误是非常重要的。
例如,我有一个validate
方法,在允许创build一个案例类之前validation一组条件。 每个失败的条件都会生成一个RuntimeException
实例。 然后该方法返回RuntimeInstance
的列表。 这使得客户有能力决定他们如何处理回应。 throw
列表持有exception,扫描列表中的具体内容,并throw
或只是推动整个事情的调用链,而无需使用非常昂贵的JVM throw
命令。
这个特定的问题空间有RuntimeException
三个不同的后代,一个抽象( FailedPrecondition
)和两个具体的( FailedPreconditionMustBeNonEmptyList
和FailedPreconditionsException
)。
首先, FailedPrecondition
是RuntimeException
的直接后裔,与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 )
第三, FailedPreconditionsException
是RuntimeException
的直接后裔,它包装了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))
然后将所有这些整合在一起并整理好,我将FailedPrecondition
和FailedPreconditionMustBeNonEmptyList
放在对象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))