Try

The class de.fhg.fokus.xtensions.exceptions.Try<R> represents the result of a computation that may have failed. It wraps around either a success (holding a result), a failure (wrapping an exception), or an empty result (null). The class is only generic over the success type, the types of exceptions that may be wrapped in a failed Try should be documented, but are not formally defined via the type system. The Try type is only generic over the success value.

Since Xtend has no checked exceptions, there is no rigorous compiler support to make callers of a method aware that something can go wrong. Methods can only document exceptions to be thrown. So instead, the Try class can now be used as a return type to make it very clear to the caller that an exception has to be handled.

The class has lots of functionality on it, the description here covers only the basics. The reader is encouraged to have a look at the JavaDoc entry for all possible combinator methods available on the Try type.

The following paragraphs will show the most important category of methods ond sub-types available. A general rule for combinator methods starting is that methods starting with the prefix try* will not throw an exception, instead they will return failed Try if a failure occurs during the computation of the method.

The Try States

The three states of the Try class are represented by the three subclasses

  • Try.Success<R>,

  • Try.Failure<R> and

  • Try.Empty<R>

Try.Success is wrapping a successful result value of the type R; Try.Failure is wrapping around an exception that occurred, while Try.Empty means a computation did not yield a value (mostly this means a null result).

Instances of Try can be checked for being instance of one of these classes and casted to these to get to methods specific to the state (e.g. to extract a successful result). Alternatively many combinator methods can be used to check for the state and work with the result or exception values.

Creation

Try instances can directly be created using one of the static factory methods with the create* name prefix:

  • Try::createSuccess(R)

  • Try::cretaeFailure(Exception)

  • Try::createEmpty()

However most likely a computation should be wrapped, so exceptions will be caught automatically. Static factory methods on the Try type with the try* name prefix allow just that:

  • Try::tryCall(⇒R)

  • Try::tryCall(I, (I)⇒R)

  • Try::tryOptional(⇒Optional<R>)

  • Try::tryFlat(⇒Try<R>)

  • Try::tryWith(⇒I,(I)⇒R)

The usage of these methods is similar to try-blocks.

Example:

val s = "123L"
val result = tryCall [
	Long.valueOf(s)
]

Happy Path Progression

Try instance methods with the name prefix thenTry* only have an effect on instances of Try.Success. They pass the value of the successful computation on to a given closure computing a new result value:

  • thenTry((R)⇒U)

  • thenTryWith(⇒I, (I, R)⇒U)

  • thenTryFlat((R)⇒Try<U>)

  • thenTryOptional((R)⇒Optional<U>)

These methods allow a chain of computation that only handles the "happy path" not caring about exceptions to be thrown along the way. Error handling or reporting can then be pushed to the end of a call chain.

Example:

val s = "123L"
val result = tryCall [
	Long.valueOf(s)
].thenTry [
	Math.addExact(it,it)
]
Note

It is usually not advised to call a progression directly after creating a Try. In the example above the content of the progression could be written directly in the first block. However, progressions come handy when be called on a Try received from a method call.

Recovery

In some cases it may be possible to recover from a failure or an empty result and still compute a successful result. To recover a failed or empty Try to a successful value, several instance methods are available on the Try type:

  • recover(R)

  • recoverEmpty(R)

  • tryRecover(⇒R)

  • tryRecoverEmpty(⇒R)

  • tryRecoverFailure((Throwable)⇒R)

  • tryRecoverFailure(Class<E>, (E)⇒R)

  • tryRecoverFailure(Class<? extends E>, Class<? extends E>, (E)⇒R)

  • tryRecoverFailure(Class<? extends E>…​)

The method recover(R) is the only method that will return a unwrapped result value of type R. All other methods either don’t recover all non-success cases or may fail themselves during recovery, which is why they return a Try<R>.

Example:

val s = "123L"
val Try<Long> result = tryCall [
	val parsed = Long.valueOf(s)
	Math.addExact(parsed, parsed)
].tryRecoverFailure(NumberFormatException, ArithmeticException) [
	-1L
]

Extracting Result Values

At some point it is needed to unwrap the actual successful result or exception from a failed Try. The following methods

  • ifSuccess((R)⇒void)

  • ifEmpty(⇒void)

  • ifFailure((Throwable)⇒void)

  • ifFailure(Class<E>, (E)⇒void)

  • ifFailure(Class<? extends E>, Class<? extends E>,(E)⇒void)

  • ifFailure(Class<? extends E>…​)

  • Optional<R> getResult()

  • R getOrNull()

  • R getOrThrow()

  • R getOrThrow(⇒E)

Example:

val s = "123L"
tryCall [
	Long.valueOf(s)
].ifFailure [
	it.printStackTrace
].ifSuccess [
	println(it)
]

When casting to the actual sub-type, instance methods on the types can be used to extract the result values:

  • Throwable Try.Failure#get()

  • R Try.Success#get()

Example:

val String foo = System.getenv("Foo")
val t = tryCall [
	if(foo === null) {
		null
	} else {
		foo.charAt(5)
	}
]
val result = switch(t) {
	Success<Character>: "Character 6 is " + t.get
	Empty<Character>: "No input string"
	Failure<Character>: "Problem occurred: " + t.get.message
}

Note that Try.Success and Try.Failure have even more instance methods worthy to check out.

Tip

Related JavaDocs:

results matching ""

    No results matching ""