cause
的默认值为 null。而对于 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"或 companion 对象中的 apply 方法来实现这一点。
无论如何,我仍然感到惊讶,居然没有更简单的方法来实现它。
Throwable.initCause
。class MyException (message: String, cause: Throwable)
extends RuntimeException(message) {
if (cause != null)
initCause(cause)
def this(message: String) = this(message, null)
}
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
时必须使用new
的情况,请添加此伴生对象(它只是将所有apply调用转发到现有的“主”类构造函数):
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模式:
从最初的问题到创建一个专门的RuntimeException
的生态系统,只需要跨越一小步。这个想法是定义一个“根”RuntimeException
,从中可以创建一个新的特定后代异常的生态系统。对我来说,重要的是使使用catch
和match
在特定类型的错误上更容易利用。
例如,我定义了一个validate
方法,它在允许创建一个case类之前验证一组条件。每个失败的条件都会生成一个RuntimeException
实例。然后方法返回RuntimeInstance
列表。这使客户端能够决定他们如何处理响应;抛出持有异常列表、扫描列表寻找特定异常并将其抛出或者只是将整个异常传递给调用链,而不触发昂贵的JVMthrow
命令。
这个特定的问题空间有三个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
。而类只是扩展了抽象类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
,并动态地管理异常消息的发出。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))
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
)
然后,使用此异常的方式如下:
throw FailedPreconditionMustBeNonEmptyString()
RuntimeException
或者扩展到更一般的"异常生态系统"都很难找到既具体又全面的资料,而在Java中我已经习惯了这种模式。case class ShortException(message: String = "", cause: Option[Throwable] = None)
extends Exception(message) {
cause.foreach(initCause)
}
然后,您可以按照Java的方式创建异常:
throw ShortException()
throw ShortException(message)
throw ShortException(message, Some(cause))
throw ShortException(cause = Some(cause))
Scala中的try/catch块中的模式匹配适用于接口。我的解决方案是使用一个接口作为异常名称,然后使用单独的类实例。
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"。
object MyException { def apply(message: String = null, cause: Throwable = null) = new MyException(message, cause)}
已经足够了。 - seniaRuntimeException(cause)
和Runtime(null, cause)
的行为不同,因为前者设置了detailMessage = cause.toString()
,而后者则将detailMessage = null
。因此,上述代码的行为将不会与在Java中可能调用适当的超类构造函数完全相同。 - Cory Klein