【发布时间】:2016-07-07 10:48:17
【问题描述】:
如何在扩展Exception 类的 Scala 中创建自定义异常,并在发生异常时抛出它们并捕获它们。
java 中的示例:
class CustomException extends Exception {
public final static String _FAIL_TO_INSERT = "FAIL_TO_INSERT";
}
【问题讨论】:
如何在扩展Exception 类的 Scala 中创建自定义异常,并在发生异常时抛出它们并捕获它们。
java 中的示例:
class CustomException extends Exception {
public final static String _FAIL_TO_INSERT = "FAIL_TO_INSERT";
}
【问题讨论】:
class MyException(message: String) extends Exception(message) {
def this(message: String, cause: Throwable) {
this(message)
initCause(cause)
}
def this(cause: Throwable) {
this(Option(cause).map(_.toString).orNull, cause)
}
def this() {
this(null: String)
}
}
这与@Jacek L. 的答案几乎相同。我只是想就这个答案背后的动机添加更多信息。
为什么有这么多构造函数?
Throwable 的写法很有趣。它有 4 个构造函数
-- 忽略带有boolean 切换的那个 -- 在nulls 中,它们每个的行为都略有不同,而这些差异只能通过多个构造函数来维护。
如果 Scala 允许通过super 调用超类构造函数,它会更简洁一些,但它不会:(
为什么不是案例类?
nulls 的行为是不可能的;具体来说,def this() 和def this(message: String) 都必须将cause 设置为null,而最初设置为this。toString 不会被覆盖。getMessage 和getCause 公开。添加另一个引用是多余的。equals 将被覆盖并且将表现不同。new Exception("m") == new Exception("m") // falsenew CaseException("m") == new CaseException("m") // true
如果希望通过模式匹配来访问消息和原因,可以简单地实现unapply 方法:
object MyException {
def unapply(e: MyException): Option[(String,Throwable)] = Some((e.getMessage, e.getCause))
}
【讨论】:
this(null: String) 部分吗?谢谢
MyException 类中,有两个带有单个参数的构造函数——一个带有message: String 的主要构造函数,一个带有cause: Throwable 的构造函数。编译器无法推断对this(null) 的调用,因为它适用于这两个构造函数。指定null 的类型告诉编译器调用哪个构造函数。
final case class CustomException(private val message: String = "",
private val cause: Throwable = None.orNull)
extends Exception(message, cause)
试着抓住:
try {
throw CustomException("optional")
} catch {
case c: CustomException =>
c.printStackTrace
}
【讨论】:
toString 已经实现,而 equals 将被覆盖,其行为与其他异常不同(Exception 使用默认的 Object.equals)。此外,通过getMessage 和getCause 已经可以公开访问消息和原因。如果您想要模式匹配,请实现unapply 方法。
Exceptions。 equals 行为可能是最不重要的区别。是的,toString 的行为与Exception.toString 完全一样——这正是我所说的——这是使用case class 的原因之一。将 case class 值设为私有只会使其完全多余。还有我在my full answer 中讨论的构造函数的问题。
你像这样定义你的自定义异常
case class CustomException(s: String) extends Exception(s)
你可以像这样抛出你的异常:
try{
...
} catch{
case x:Exception => throw new CustomException("whatever")
}
【讨论】:
与其他答案类似,但我更喜欢使用伴随对象而不是替代构造函数。
class MyException(message: String, cause: Throwable = null) extends Exception(message, cause)
object MyException {
def apply(message: String): MyException = new MyException(message)
def apply(message: String, cause: Throwable): MyException = new MyException(message, cause)
}
【讨论】:
您可能想要创建一个密封的特征:
sealed trait MyException {
// This is called a "self annotation". You can use "self" or "dog" or whatever you want.
// It requires that those who extend this trait must also extend Throwable, or a subclass of it.
self: Throwable =>
val message: String
val details: JsValue
}
那么您可以拥有任意数量的case classes,不仅可以扩展Exception,还可以扩展您的新特征。
case class CustomException(message: String) extends Exception(message) with MyException {
override val details: JsValue = Json.obj("message" -> message, "etc" -> "Anything else")
}
现在,使用 Scala 的全部意义在于向更函数化的编程风格迈进,这将使您的应用更具并发性,因此,如果您需要使用新的自定义异常,您可能想尝试这样的事情:
def myExampleMethod(s: Option[String]): Future[Boolean] = Try {
s match {
case Some(text) =>
text.length compareTo 5 match {
case 1 => true
case _ => false
}
case _ => throw CustomException("Was expecting some text")
}
}
match {
case Success(bool) => Future.successful(bool)
case Failure(e) => Future.failed(e)
}
【讨论】:
val: 中有一个不必要的: 错字。我无法编辑问题,因为显然 建议的编辑队列已满。
除了以上所有答案之外,如果您想要有一个错误层次结构,抽象类会有所帮助。
abstract class GenericError(message: String) extends Exception(message)
case class SpecificErrorA(message: String) extends GenericError(message)
case class SpecificErrorB(message: String) extends GenericError(message)
throw new SpecificErrorA("error on A") // OR
throw new SpecificErrorB("error on B")
使用特征而不是抽象类也可以做到这一点,但它们的局限性在于它们没有构造函数参数。
可能在任何地方都使用 GenericError 并在应用程序/控制器边界上解构(模式匹配)它。
【讨论】:
为了反映 Exception 中的所有原始构造函数,我将使用以下模式实现自定义异常:
class CustomException(msg: String) extends Exception(msg) {
def this(msg: String, cause: Throwable) = {
this(msg)
initCause(cause)
}
def this(cause: Throwable) = {
this(Option(cause).map(_.toString).orNull)
initCause(cause)
}
def this() = {
this(null: String)
}
}
这也可以通过前面答案中提到的特征来实现。在这种情况下,我只是不创建单独的类:
trait SomeException { self: Throwable =>
def someDetail: SomeDetail
}
那么,投掷时:
throw new Exception(...) with SomeException {
override val someDetail = ...
}
当匹配时:
try {
...
} catch {
case ex: Throwable with SomeException =>
ex.getCause
ex.getMessage
ex.someDetail
}
这里的优点是您不会坚持父异常的任何特定构造函数。
或多或少类似。
【讨论】: