2012-06-07 155 views
34

定義與重載構造自己的異常在Java異常至少有以下四個構造函數:斯卡拉

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

如果要定義自己的擴展,你只需要聲明一個派生的異常,並實現每所期望構造函數調用符合超級構造函數

如何在scala中實現相同的功能?

到目前爲止,現在我看到了this articleSO answer,但我懷疑有必須要達到這樣的普通的事

回答

56

cause的默認值爲空。而對於message這要麼是cause.toString()或空:

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 
+0

不應該例外,繼承RuntimeException? – opensas

+0

偉大的答案,而且,如果你聲明它作爲一個案例類,你可以擺脫「新」... – opensas

+1

@opensas對象MyException {def apply(message:String = null,cause:Throwable = null)= new MyException(message,cause)}'就夠了。 – senia

11

以及更簡單的方法,這是我發現迄今最好的

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

這種方式,您可以使用「新MissingConfigurationException」或同伴對象的申請方法

不管怎麼說,我還是驚訝,沒有一個簡單的方法來實現它

6

您可以使用Throwable.initCause

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

    def this(message: String) = this(message, null) 
} 
5

對我來說,它似乎有具有彼此動態張力三個不同的需求:

  1. RuntimeException擴展器的方便;即要寫入的最小代碼以創建後代RuntimeException
  2. 客戶感知的易用性;即最少的代碼在調用點寫入
  3. 客戶的偏好,以避免泄露可怕的Java null到他們的代碼

如果不關心3號,然後this answer(同行對這一)看起來很簡潔。

但是,如果在嘗試儘可能接近數字1和2時將值設爲3,下面的解決方案將Java漏洞有效地封裝到您的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) 
} 

如果你想消除不必使用new,其中實際使用MyRuntimeException,添加這個同伴對象(這只是轉發所有的應用調用現有的「主」類的構造):

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創建的專門RuntimeException秒的生態系統。這個想法是定義一個「根」RuntimeException,從中可以創建一個新的特定後代異常生態系統。對我而言,使用catchmatch更容易利用特定類型的錯誤非常重要。

例如,我有一個validate定義的方法,在允許創建案例類之前驗證一組條件。每個失敗的條件都會生成一個RuntimeException實例。然後該方法返回RuntimeInstance的列表。這使客戶能夠決定他們想如何處理響應; 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。而且這個類只是擴展抽象類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它包裝FailedPreconditionList一個秒,然後動態地管理異常消息的發射。

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

,這是它會是什麼樣子的客戶端使用上面的代碼來創建自己的異常推導稱爲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() 

我遠遠超出了回答原來的問題,因爲我發現它如此難以找到任何物品接近具體和全面的Scala-ifying RuntimeException具體或擴展到更加一般的「異常生態系統」,我在Java中變得非常舒適。

我很想聽聽我的解決方案集中的反饋(除了「哇,對我來說這太冗長了」的變體)。而且我會喜歡任何額外的優化或者減少冗長的方式,而不會丟失我爲這種模式的客戶產生的任何價值或簡潔。

0

try/catch塊中的Scala模式匹配適用於接口。我的解決方案是使用異常名稱的接口,然後使用單獨的類實例。

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」。

0

這是類似的方法@羅曼 - 博裏索夫,但更多的類型安全。

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