Skip to content
Vantuz Subhuman edited this page Aug 24, 2015 · 11 revisions

Mātau

Source

About

Try<R> allows you to perform try-catch-finally operations in a functional manner. Type <R> of the class represents a type of the "result value" - an object provided by a dangerous operation, and used throughout the try.

Usage

There're 3 factory methods allowing you to obtain an instance of the Try:

There're also some methods that would be better to know about:

=====

#run


Static factory method Try.run allows you to perform a dangerous runnable operation in a safe manner. Consider an "oldschool" piece of code liek this:

try {
	TryTest.writeFile();
	TryTest.showSuccess();
} catch (IOException e) {
	e.printStackTrace();
} finally {
	TryTest.closeFile();
}

Imagine method "writeFile" is declared like this: void #writeFile() throws IOException. Then the call to the method might be represented as an instance of the DangerousRunnable<E> from the whaka.util.function package:

DangerousRunnable<IOException> writeFile = TryTest::writeFile;

Then this runnable might be used with the Try.run method:

Try<Void> try = Try.run(writeFile);

Method #run safely performs the operation and creates an instance of the Try<Void> that either empty, or contains catched exception. (Note: result of #run is always has type Void and contains null as value, for runnable operation produces no result.) Instance of Try allows you to perform all sorts of operations to operate upon success or fail on an operation. The whole "oldschool" example can be rewritten like this:

Try.run(TryTest::writeFile)
	.onPerformSuccess(empty -> showSuccess())
	.onPerformFail(Exception::printStackTrace)
	.onAnyResult((empty, e) -> closeFile());

Consumer of the same type as try specified into the #onPerformSuccess will be performed only in case of a success, and will receive result of the performed operation (Note: result might be null, for example in case of successful runnable operation).

Consumer<Exception> specified into the #onPerformFail will be performed inly in case of a fail and will definitely receive an exception.

BiConsumer operation specified into the #onAnyResult method will be performed in any case, and will recieve both possible result and possible exception (Note: one of the arguments will always be null, for there's no way result and exception might coexist in a single try. But it is also possible that both arguments will be null, for example in case of successful runnable operation).

=====

#perform


Static factory method Try.perform allows you to perform a dangerous supplier operation in a safe manner. Consider an "oldschool" piece of code liek this:

File file = null;
try {
	file = TryTest.writeFile();
	TryTest.showSuccess(file);
} finally {
	TryTest.closeFile(file);
}

Imagine method "writeFile" is declared like this: File #writeFile() throws IOException. Then the call to the method might be represented as an instance of the DangerousSupplier<T, E> from the whaka.util.function package:

DangerousSupplier<File, IOException> writeFile = TryTest::writeFile;

Then this supplier might be used with the Try.perform method:

Try<File> try = Try.perform(writeFile);

Method #perform safely performs the operation and creates an instance of the Try<T> (where <T> is the return type of the performed supplier) that contains either result of the operation, or catched exception. The whole "oldschool" example can be rewritten like this:

Try.perform(TryTest::writeFile)
	.onAnyResult((file, e) -> closeFile(file))
	.onPerformSuccess(TryTest::showSuccess)
	.onPerformFailDangerous(e -> {throw (IOException) e;});

Method #onPerformSuccess here will definitely receive a non-null value of the type File, unless #writeFile method returned null.

Method '#onPerformFailDangerous' is similar to the #onPerformFail - the difference is that it takes an instance of the DangerousConsumer<Exception, E> from the whaka.util.function package, that allows you to throw exceptions. Method itself will throw the same type of an exception, as specified consumer.

There's similar method: #onPerformFailRethrow() throws Exception
It just rethrows the exception contained in the try (if any). But since Try<R> itself isn't parameterized by the type of an error - it operates on generic Exception without knowing the specific type. So if you want to throw more specific type, you'll have to use #onPerformFailDangerous, like in the example.

There's also similar method: #onPerformFailThrow(Function<Exception, E>) throws E
This method allows you to convert contained exception (if any) into any another Throwable, and then it'll be thrown automatically. Method also declares the same throws type, as the result of the specified function.

So this line: .onPerformFailDangerous(e -> {throw (IOException) e;});
Can be replaced: .onPerformFailThrow(e -> (IOException) e);
Or even better: .onPerformFailThrow(IOException.class::cast);

Note: how method call #onAnyResult has moved to the first line, comparing to the #run example. This is no accident. In the previous example method call #onPerformFail was safe, and didn't throw any exceptions, but in new example method call #onPerformFailDangerous is not safe. All methods are called in the specified order, which means that #onAnyResult will not be called if it was specified after #onPerformFailDangerous that has thrown an exception (or any other method that raised an exception). It is important, since currently it's the only analog of the finally block and it cannot be performed after any other potentially failing methods.

=====

#withResource


Static factory method Try.withResource allows you to safely perform a dangerous supplier operation that provides some abstract "resource", and then safely perform a function that converts the resourse into a result. Resource then gets automatically closed. Consider an "oldschool" piece of code liek this:

String path = "";
try (FileWriter fw = new FileWriter(path)) {
	File file = TryTest.writeFile(fw);
	TryTest.showSuccess(file);
}

Imagine method "writeFile" is declared like this: File #writeFile(Writer) throws IOException. Then the call to the method might be represented as an instance of the DangerousFunction<V, R, E> from the whaka.util.function package:

DangerousFunction<Writer, File, IOException> writeFile = TryTest::writeFile;

And the file writer might be produced by a supplier:

DangerousSupplier<IOException> writer = () -> new FileWriter(path);

Then this supplier might be used with the Try.withResource method:

Try<File> try = Try.withResource(writer, writeFile);

Method #withResource safely performs the supplier, and then (in case of success) applies the function to the supplied resource, and creates an instance of the Try<T> (where <T> is the return type of the applied function) that contains either result of the operation, or catched exception. Resource then gets automatically closed (resource has to implement AutoCloseable). The whole "oldschool" example can be rewritten like this:

Try.withResource(() -> new FileWriter(path), TryTest::writeFile)
	.onPerformSuccess(file -> showSuccess(file))
	.onPerformFailThrow(IOException.class::cast);

Important things to know here are:

  1. If resource supplier fails - function never applied, and Try is created with the catched exception.
  2. If function fails - resource is closed and Try is created with the catched exception.
  3. If function finishes successfully, but resource closing fails - successful Try with a value is created. It will perform all onPerformSuccess* operations, but it will also contain a "suppressed" field - the exception raised by the closing resource. It can be accessed with the #getSuppressed() method.
  4. If function fails and resource closing fails - failed Try will be created with the exception raised by the failed function, but it will also contain a "suppressed" field.

=====

#onPerformFailCatch

Method `Try.onPerformFailCatch` allows you to simulate multiple **catch** operations. Consider an "oldschool" piece of code liek this: ``` try { writeFile(); showSuccess(); } catch (FileNotFoundException e) { processNoFile(e); } catch (UnsupportedEncodingException e) { processEncoding(e); } ```

Imagine method "processNoFile" is declared like this: void processNoFile(FileNotFoundException e), and method "processEncoding" is declared like this: void processEncoding(UnsupportedEncodingException e). Then the calls to methods might be represented as instances of the DangerousConsumer<V, RuntimeException> from the whaka.util.function package:

DangerousConsumer<FileNotFoundException, RuntimeException> fnfe = TryTest::processNoFile;
DangerousConsumer<UnsupportedEncodingException, RuntimeException> uee= TryTest::processEncoding;

Then these consumers might be used with the Try.onPerformFailCatch method:

Try.run(TryTest::writeFile)
	.onPerformFailCatch(FileNotFoundException.class, fnfe)
	.onPerformFailCatch(UnsupportedEncodingException.class, uee);

If "try" has failed and contained exception is instance of the specified class - method will execute specified consumer. Note: the first successful call to the method (the one that matched the exception and executed the specified consumer) will mark the whole instance of the "try" as caught. Once caught instance will ignore any additional calls to the #onPerformFailCatch. The whole example might be rewritten like this:

Try.run(TryTest::writeFile)
	.onPerformSuccess(empty -> showSuccess())
	.onPerformFailCatch(FileNotFoundException.class, TryTest::processNoFile)
	.onPerformFailCatch(UnsupportedEncodingException.class, TryTest::processEncoding)
	.onPerformFailCatch(IOException.class, e -> {throw e;});

If initial try in this example will raise an instance of the FileNotFoundException or UnsupportedEncodingException - then last "catch" for IOException will be ignored, even though both of these exceptions are instances of the IOException.

Note: caught state might be checked with the #isCaught method.

=====

#assertFail

=====

#onPerformSuccessTry

=====

#getResult


Mātau

Mātau

Rākau o te riri

Clone this wiki locally