我如何在扩展
Exception
类的 Scala 中创建自定义异常,并在异常发生时抛出它们并捕获它们。
Java 中的示例:
class CustomException extends Exception {
public final static String _FAIL_TO_INSERT = "FAIL_TO_INSERT";
}
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
}
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
开关的开关 -- 它们中的每一个与 null
的行为都略有不同,并且这些差异只能通过多个构造函数来维持。
如果 Scala 允许通过
super
调用超类构造函数,那会更干净一点,但事实并非如此:(
为什么不是案例班?
null
的行为是不可能的;具体来说,def this()
和def this(message: String)
都必须将cause
设置为null
,而最初它设置为this
。toString
不会被覆盖。getMessage
和 getCause
公开。添加另一个引用这些是多余的。equals
将被覆盖并且 的行为会有所不同。new Exception("m") == new Exception("m") // false
new CaseException("m") == new CaseException("m") // true
如果希望通过模式匹配访问消息和原因,可以简单地实现
unapply
方法:
object MyException {
def unapply(e: MyException): Option[(String,Throwable)] = Some((e.getMessage, e.getCause))
}
您可能想创建一个密封特征:
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 class
,您不仅需要扩展 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)
}
为了反映 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
}
这里的优点是您不必坚持父异常的任何特定构造函数。
或多或少是这样的。
您可以像这样定义自定义异常
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)
}
添加上面的所有答案,如果您想要一个错误层次结构,抽象类会有所帮助。
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 并在应用程序/控制器边界上解构(模式匹配)它。