Skip to content
Peter Nerg edited this page Dec 8, 2017 · 16 revisions

Try

The Try type represents a computation that may either result in an exception, or return a successfully computed value.
Typical use case is situations where parallel computation takes place resulting in more than one response where it's possible that one or more computations fail.
Though it might not be desirable to raise an exception for failed computations hence the Try acts a place holder for a response that is either failed or successful.
Instances of Try, are either an instance of Success or Failure.

The difference between Try and Option is that Option replaces the value/null value construct and Try replaces the need for raising/catching exceptions.

Consider the following example:

SomeData result = executeSomeOp(SomeInput value) throws Exception;

It could be re-written to not throw an exception.

Try<SomeData> result = executeSomeOp(SomeInput value);

The benefit with the above approach is that the the method executeSomeOp can now be chained into multi-threaded construct that for example produces a Stream of Try. There's no exception raised from the method hence it will not stop execution.

Now the idea is not to go ballistics and replace every throw of an exception with returning Try instances. It's as everything else a tool that should be used where it fits best.

The examples provided on this Wiki are only a subset of the full functionality.
For a complete set of functionality and examples refer to the Java Doc

Success

Represents the 'successful' implementation of Try.
Creating Success instances can be done directly:

Try<String> t = new Success<>("Peter is great");

or using the factory/apply method on Try

Try<String> t = Try.apply("Peter is nice");

One can provide null values when creating Success instances.

Failure

Represents the 'failed' implementation of Try.
Creating Failure instances can be done directly:

Try<String> t = new Failure<>(new Exception("Error, terror"));

or using the factory/apply method on Try

Try<Object> t = Try.apply(new Exception("Things went...kaboom"));

Please note that creating an instance of Failure requires providing a non-null Throwable, i.e. null values are not allowed.

Examples

Try as Iterable

The Try interface extends Iterable thus it can be used in loops as any collection.
In practice this means that Succesful behaves as a collection with a single value and Failure is empty.

E.g. consider the Try

Try<String> t = ...;

Can be used in the imperative loop:

for(String s : t) 
  System.out.println(s);

But since it's an Iterable it can also use the more functional approach:

t.forEach(s->System.out.println(s));

Replacing throws

Let's consider a small method that reads the first line in a file and returns that.
Could be written as this:

public String readFirstLineFromFile(String file) throws FileNotFoundException, IOException {
  try (LineNumberReader r = new LineNumberReader(new FileReader(file))) {
    return r.readLine();
   }
}

Which then forces you to write defensive try-catch clauses:

String s = null;
try {
  s = readFirstLineFromFile("somefile.txt");
catch(IOException ex) {
  s = "Some default text";
}

A small modification to the method could be:

public Try<String> readFirstLineFromFile(String file) {
  try {
    try (LineNumberReader r = new LineNumberReader(new FileReader(file))) {
      return Try.apply(r.readLine());
     }
  } catch (IOException ex) {
     return new Failure(ex);
  }
}

Then performing the same usage pattern would yield:

String s = readFirstLineFromFile("somefile.txt").getOrElse(() -> "Some default text");

Auto-managing Exceptions

The previous [example](Try#Replacing throws) showed how one can replace throwing exceptions by using thy Try as return type.
Let's take that example a bit further and skip the entire exception management entirely by auto-catching any exceptions and automatically returning either a Success or a Failure.
The method for that is Try.apply(ThrowableFunction0). The ThrowableFunction0 class is a Functional interface that allows for throwing checked exceptions. These simple examples illustrate how to use the apply method and providing it with a Lambda expression acting as the function. The second statement will result in a division-by-zero hence an exception is raised.

Try<Integer> resultSuccess = Try.apply(() -> 9 / 3); // results in Success(3)
Try<Integer> resultFailure = Try.apply(() -> 9 / 0); // results in Failure(ArithmeticException)

To make it even more concise we can create Try instances just by using Try(ThrowableFunction0).
This however requires you to statically import the proper methods from the companion class related to Try.

import static javascalautils.TryCompanion.Try;

Try<Integer> t = Try(() -> 9/0);

We can now apply this to the previous [example](Try#Replacing throws).

public Try<String> readFirstLineFromFile(String file) {
  return Try(() -> {
     //the surrounding try-with-resources is only to get auto-close on the reader
     try (LineNumberReader r = new LineNumberReader(new FileReader(file))) {
        return r.readLine();
     }
  });
}

This example is taken from the Executors class.
We try to create an instance of a class specified by a system property and then cast that class to Executor, should that fail we have a Failure at hand and the orElse part is executed returning the default Executor instance. Not a single if-else-then construct. I.e. a more functional approach.

private static Executor createDefaultExecutor() {
    Try<Executor> t = Try(() -> (Executor) Class.forName(System.getProperty("javascalautils.concurrent.executorprovider")).newInstance());
    return t.getOrElse(() -> createCachedThreadPoolExecutor(new NamedSequenceThreadFactory("Executors-Default")));
}

Side effecting functions without return type

For situations where you want to apply a function that has a side effect but now return type there's a special method on the TryCompanion class.
The purpose is to allow the user to invoke a side-effecting function that may succeed/fail but has now return type.
E.g. deleting something from a database may not have an interesting return type.
One is only interested of the outcome, Success/Failure.
The following example illustrates some pseudo-code that performs a deletion in some database.
The delete operation may potentially fail with an exception but we're not interested in any return data.
The construct will return Some(Unit) in case of successful, else a Failure with the exception.

import static javascalautils.TryCompanion.Try;

Try<Unit> t = Try(() -> {
  database.delete(someId);
});

Dealing with multiple responses

Let's say you have a program that concurrently performs some calculation and has stored the result as Try instances in a list.
Using Java 8 and Lambda you can now do really nice constructs.

List<Try<String>> responses = ...;
responses.stream().filter(t -> t.isSuccess()).map(t -> t.orNull()).forEach(s -> System.out.println(s));

In the example I've filtered and kept all Success values and then mapped them to their values (orNull is just to skip the exception handling) and then printed them.

Converting a Failure to a Success

Let's say you have a Try that is a Failure and you want to access the wrapped Throwable.
For this you use the method failed, it will return a Success containing the Throwable from your Failure.
E.g.:

Try<String> failure = new Failure<>(new Exception("Error, terror!!!"));
Try<Throwable> success = failure.failed();
Throwable t = success.orNull();

The last orNull is to avoid exception handling that the method get forces you to. In practice null can never be returned as a Failure always contains a Throwable thus mapping to a Success always with a value.

Let's give a more elaborate example. You have a list with results of type Try that you would like to print the message for every Failure type.

List<Try<String>> results = ...;
results.stream().filter(t -> t.isFailure()).map(t -> t.failed()).forEach(t -> System.out.println(t.orNull().getMessage()));

The same statement as above but with a different flavor:

results.stream().filter(Try::isFailure).map(Try::failed).forEach(t -> System.out.println(t.orNull().getMessage()));

First we filter out all Failure then we map these to Success and lastly print the message from each Throwable