2012-06-07 12 views
34

W wyjątkami java mieć przynajmniej te cztery konstruktorów:zdefiniować własne wyjątki z konstruktorów przeładowanych w Scala

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

Jeśli chcesz zdefiniować własne rozszerzenia, po prostu trzeba zadeklarować wyjątki potomkiem i wdrożyć każdy pożądany konstruktor wywołujący super konstruktora korespondencyjnego

Jak możesz osiągnąć to samo w scala?

tak daleko teraz widziałem this article i ten SO answer, ale podejrzewam, że musi być łatwiejszy sposób, aby osiągnąć taką wspólną rzeczą

Odpowiedz

56

Wartość domyślna dla ma wartość null. I dla message to albo cause.toString() lub zerowy:

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 

Więc może po prostu użyć wartości domyślnych:

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

nie powinien wyjątek przedłużenia RuntimeException? – opensas

+0

świetna odpowiedź, co więcej, jeśli zadeklarujesz to jako klasę case, możesz pozbyć się "nowego" ... – opensas

+1

@opensas 'object MyException {def apply (wiadomość: String = null, powód: Throwable = null) = new MyException (message, cause)} 'is enough. – senia

11

dobrze, jest to najlepszy jaki dotychczas

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

ten sposób można korzystać z „nową MissingConfigurationException” albo zastosować metodę z obiektu towarzyszącego

Zresztą, nadal jestem zaskoczony, że nie istnieje prostszy sposób to osiągnąć

6

Można użyć Throwable.initCause.

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

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

Dla mnie wydaje się, istnieją trzy różne potrzeby, które mają dynamiczne napięcie między sobą:

  1. wygody EXTENDER RuntimeException; tj. minimalny kod do zapisania w celu utworzenia potomka o RuntimeException
  2. Postrzegana przez klienta łatwość użycia; czyli minimalny kod do zapisania w call-site
  3. preferencji
  4. Klienta w celu uniknięcia przecieki bał Java null do ich kodu

Jeśli ktoś nie dba o numer 3, a następnie this answer (peer to ten jeden) wydaje się dość zwięzły.

Jednakże, jeśli jedna wartość numer 3 podczas próby zbliżenia się do liczby 1 i 2, jak to możliwe, poniższe rozwiązanie skutecznie hermetyluje wyciek Java null do interfejsu API Scala.

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

A jeśli chcesz wyeliminować konieczności korzystania new gdzie MyRuntimeException jest rzeczywiście używany, dodać ten przedmiot do towarzystwa (który tylko przekazuje wszystkich połączeń zastosowania do istniejącej „master” konstruktora klasy):

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

Osobiście wolę faktycznie tłumić użycie operatora new w jak największej ilości kodu, aby ułatwić przyszłe refaktoryzacje.Jest to szczególnie pomocne, gdy wspomniane refaktoryzacje są silnie związane z wzorcem Fabryki. Mój końcowy wynik, choć bardziej szczegółowy, powinien być całkiem przyjemny dla klientów.

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 
) 


Exploring bardziej wyrafinowane RuntimeException wzoru:

to tylko mały krok od pierwotnego pytania pragnąć stworzyć ekosystem wyspecjalizowanych RuntimeException s na opakowaniu lub API . Chodzi o zdefiniowanie "root" RuntimeException, z którego można stworzyć nowy ekosystem określonych wyjątków potomnych. Dla mnie ważne było, aby korzystanie z catch i match było łatwiejsze do wykorzystania w przypadku określonych typów błędów.

Na przykład mam zdefiniowaną metodę validate, która weryfikuje zestaw warunków przed zezwoleniem na utworzenie klasy przypadku. Każdy niepowodzenie generuje instancję RuntimeException. A następnie lista RuntimeInstance s są zwracane przez metodę. Daje to klientowi możliwość decydowania, w jaki sposób chciałby obsłużyć odpowiedź; throw wyjątek zatrzymania listy, przeskanuj listę pod kątem czegoś konkretnego i po prostu przepchnij całą rzecz w górę łańcucha wywołań bez angażowania bardzo kosztownej komendy JVM throw.

Ta szczególna przestrzeń problem ma trzy różne potomków RuntimeException, jeden abstrakcyjne (FailedPrecondition) i dwa betonowe (FailedPreconditionMustBeNonEmptyList i FailedPreconditionsException).

Pierwsza, FailedPrecondition, jest potomkiem bezpośrednim do RuntimeException, bardzo podobnym do MyRuntimeException i jest abstrakcyjna (aby zapobiec bezpośrednim instancjom). FailedPrecondition ma "cechę obiektu towarzyszącego", FailedPreconditionObject, która działa jako fabryka instancji (tłumienie operatora 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 
) 

Drugi FailedPreconditionMustBeNonEmptyList, jest pośrednim RuntimeException potomek i bezpośrednią konkretna realizacja FailedPrecondition. Definiuje zarówno obiekt towarzyszący, jak i klasę. Obiekt towarzyszący rozszerza cechę FailedPreconditionObject. Klasa po prostu rozszerza klasę abstrakcyjną FailedPrecondition i oznacza ją final, aby zapobiec dalszym rozszerzeniom.

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 
) 

Trzeci FailedPreconditionsException jest bezpośrednim potomkiem RuntimeException który owija List z FailedPrecondition S i dynamicznie kieruje emitowanie wiadomości wyjątku.

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

a potem przenosząc wszystko to razem jako całość i schludny rzeczy, I miejsce zarówno FailedPrecondition i FailedPreconditionMustBeNonEmptyList wewnątrz obiektu FailedPreconditionsException.I to właśnie efekt końcowy wygląda tak:

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

i to będzie wyglądać dla klienta do korzystania z wyżej kod, aby tworzyć własne wyprowadzenie wyjątek nazywany 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 
) 

i następnie zastosowanie tego wyjątku wygląda następująco:

throw FailedPreconditionMustBeNonEmptyString() 

poszedłem daleko poza odpowiedzi na oryginalne pytanie, bo okazało się, że tak trudne do zlokalizowania anyth bliskość bycia specyficznym i wszechstronnym w Scala-ifying RuntimeException w specyficznym lub rozszerzającym się do bardziej ogólnego "ekosystemu wyjątków", z którym tak dobrze się rozwijałem w Javie.

Chciałbym usłyszeć opinie (inne niż wariacje na temat "Wow! To zbyt szczegółowe dla mnie") w moim zestawie rozwiązań. I chciałbym, aby wszelkie dodatkowe optymalizacje lub sposoby zmniejszenia gadatliwości BEZ ZGUBIENIA żadnej z wartości lub zwięzłości generowałem dla klientów tego wzorca.

0

Dopasowywanie wzorców Scala w blokach try/catch działa na interfejsach. Moim rozwiązaniem jest użycie interfejsu dla nazwy wyjątku, a następnie użycie osobnych instancji klasy.

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

Tworzenie instancji MyClass wyświetli "oops".

0

Oto podobne podejście do tego z @ roman-borisov, ale bardziej bezpieczne.

case class ShortException(message: String = "", cause: Option[Throwable] = None) 
    extends Exception(message) { 
    cause.foreach(initCause) 
} 

Następnie można utworzyć wyjątki w sposób Java:

throw ShortException() 
throw ShortException(message) 
throw ShortException(message, Some(cause)) 
throw ShortException(cause = Some(cause)) 
Powiązane problemy