Reputation: 63405
In java exceptions have at least these four constructors:
Exception()
Exception(String message)
Exception(String message, Throwable cause)
Exception(Throwable cause)
If you want to define your own extensions, you just have to declare a descendent exceptions and implement each desired constructor calling the corresponden super constructor
How can you achieve the same thing in scala?
so far now I saw this article and this SO answer, but I suspect there must be an easier way to achieve such a common thing
Upvotes: 37
Views: 21317
Reputation: 685
Here is a similar approach to the one of @roman-borisov but more typesafe.
case class ShortException(message: String = "", cause: Option[Throwable] = None)
extends Exception(message) {
cause.foreach(initCause)
}
Then, you can create Exceptions in the Java manner:
throw ShortException()
throw ShortException(message)
throw ShortException(message, Some(cause))
throw ShortException(cause = Some(cause))
Upvotes: 0
Reputation: 74
Scala pattern matching in try/catch blocks works on interfaces. My solution is to use an interface for the exception name then use separate class instances.
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")
}
}
Instantiating MyClass will output "oops".
Upvotes: 0
Reputation: 38045
Default value for cause
is null. And for message
it is either cause.toString()
or 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
So you can just use default values:
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
Upvotes: 59
Reputation: 5924
To me, it appears there are three different needs which have a dynamic tension with each other:
RuntimeException
; i.e. minimal code to be written to create a descendant of RuntimeException
null
into their codeIf one doesn't care about number 3, then this answer (a peer to this one) seems pretty succinct.
However, if one values number 3 while trying to get as close to number 1 and 2 as possible, the solution below effectively encapsulates the Java null
leak into your 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)
}
And if you would like to eliminate having to use new
where MyRuntimeException
is actually used, add this companion object (which just forwards all of the apply calls to the existing "master" class constructor):
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
)
}
Personally, I prefer to actually suppress the use of the new
operator in as much code as possible so as to ease possible future refactorings. It is especially helpful if said refactoring happens to strongly to the Factory pattern. My final result, while more verbose, should be quite nice for clients to use.
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 a more sophisticated RuntimeException pattern:
It's only a small leap from the original question to desire to create an ecosystem of specialized RuntimeException
s for a package or API. The idea is to define a "root" RuntimeException
from which a new ecosystem of specific descendant exceptions can be created. For me, it was important to make using catch
and match
much easier to exploit for specific types of errors.
For example, I have a validate
method defined which verifies a set of conditions prior to allowing a case class to be created. Each condition that fails generates a RuntimeException
instance. And then the List of RuntimeInstance
s are returned by the method. This gives the client the ability to decide how they would like to handle the response; throw
the list holding exception, scan the list for something specific and throw
that or just push the entire thing up the call chain without engaging the very expensive JVM throw
command.
This particular problem space has three different descendants of RuntimeException
, one abstract (FailedPrecondition
) and two concrete (FailedPreconditionMustBeNonEmptyList
and FailedPreconditionsException
).
The first, FailedPrecondition
, is a direct descendant to RuntimeException
, very similar to MyRuntimeException
, and is abstract (to prevent direct instantiations). FailedPrecondition
has a "companion object trait", FailedPreconditionObject
which acts as the instantiation factory (suppressing the new
operator).
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
)
The second, FailedPreconditionMustBeNonEmptyList
, is an indirect RuntimeException
descendant and a direct concrete implementation of FailedPrecondition
. It defines both a companion object and a class. The companion object extends the trait FailedPreconditionObject
. And the class simply extends the abstract class FailedPrecondition
and marks it final
to prevent any further extensions.
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
)
The third, FailedPreconditionsException
, is a direct descendant to RuntimeException
which wraps a List
of FailedPrecondition
s and then dynamically manages the emitting of the exception message.
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))
And then bringing all of that together as a whole and tidy things up, I place both FailedPrecondition
and FailedPreconditionMustBeNonEmptyList
within object FailedPreconditionsException
. And this is what the final result looks like:
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))
And this is what it would look like for a client to use the above code to create their own exception derivation called 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
)
And then the use of this exception looks like this:
throw FailedPreconditionMustBeNonEmptyString()
I went well beyond answering the original question because I found it so difficult to locate anything close to being both specific and comprehensive in Scala-ifying RuntimeException
in specific or extending into the more general "exception ecosystem" with which I grew so comfortable when in Java.
I'd love to hear feedback (other than variations on, "Wow! That's way too verbose for me.") on my solution set. And I would love any additional optimizations or ways to reduce the verbosity WITHOUT LOSING any of the value or terseness I have generated for the clients of this pattern.
Upvotes: 5
Reputation: 61
You can use Throwable.initCause
.
class MyException (message: String, cause: Throwable)
extends RuntimeException(message) {
if (cause != null)
initCause(cause)
def this(message: String) = this(message, null)
}
Upvotes: 6
Reputation: 63405
well, this is the best I've found so far
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)
}
this way you may use the "new MissingConfigurationException" or the apply method from the companion object
Anyway, I'm still surprised that there isn't a simpler way to achieve it
Upvotes: 12