• Tidak ada hasil yang ditemukan

Reactive Programming

Dalam dokumen Book Java 8 Lambdas EN (Halaman 166-169)

CompletableFuture where to perform the work. If no Executor is provided, it just uses the same fork/join thread pool that parallel streams execute on.

Of course, not every IOU has a happy ending. Sometimes we encounter exceptional circumstances and can’t pay our debts. As Example 9-14 demonstrates, the Completa bleFuture API accounts for these situations by letting you completeExceptionally. This can be called as an alternative to complete. You shouldn’t call both complete and completeExceptionally on a CompletableFuture, though.

Example 9-14. Completing a future if there’s an error

future.completeExceptionally(new AlbumLookupException("Unable to find " + name));

A complete investigation of the CompletableFuture API is rather beyond the scope of this chapter, but in many ways it is a hidden goodie bag. There are quite a few useful methods in the API for composing and combining different instances of Completable Future in pretty much any way imaginable. Besides, by now you should be familiar with the fluent style of chaining sequences of higher-order functions to tell the computer what to do.

Let’s take a brief look at a few of those use cases:

• If you want to end your chain with a block of code that returns nothing, such as a Consumer or Runnable, then take a look at thenAccept and thenRun.

• Transforming the value of the CompletableFuture, a bit like using the map method on Stream, can be achieved using thenApply.

• If you want to convert situations in which your CompletableFuture has completed with an exception, the exceptionally method allows you to recover by registering a function to make an alternative value.

• If you need to do a map that takes account of both the exceptional case and regular use cases, use handle.

• When trying to figure out what is happening with your CompletableFuture, you can use the isDone and isCompletedExceptionally methods.

CompletableFuture is really useful for building up concurrent work, but it’s not the only game in town. We’re now going to look at a related concept that offers a bit more flexibility in exchange for more complex code.

a form of declarative programming that lets us program in terms of changes and data flows that get automatically propagated.

You can think of a spreadsheet as a commonly used example of reactive programming.

If you enter =B1+5 in cell C1, it tells the spreadsheet to add 5 to the contents of cell B1 and put the result in C1. In addition, the spreadsheet reacts to any future changes in B1 and updates the value in C1.

The RxJava library is a port of these reactive ideas onto the JVM. We won’t be going into the library in huge amounts of depth here, just covering the key concepts.

RxJava introduces a class called Observable that represents a sequence of events that you can react to. It’s an IOU for a sequence. There is a strong connection between an Observable and the Stream interface that we encountered in Chapter 3.

In both cases we build up recipes for performing work by chaining higher-order func‐

tions and use lambda expressions in order to associate behavior with these general operations. In fact, many of the operations defined on an Observable are the same as on a Stream: map, filter, reduce.

The big difference between the approaches is the use case. Streams are designed to build up computational workflows over in-memory collections. RxJava, on the other hand, is designed to compose and sequence asynchronous and event-based systems. Instead of pulling data out, it gets pushed in. Another way of thinking about RxJava is that it is to a sequence of values what a CompletableFuture is to a single value.

Our concrete example this time around is searching for an artist and is shown in Example 9-15. The search method filters the results by name and nationality. It keeps a local cache of artist names but must look up other artist information, such as nation‐

ality, from external services.

Example 9-15. Searching for an artist by name and nationality

public Observable<Artist> search(String searchedName, String searchedNationality, int maxResults) {

return getSavedArtists()

.filter(name -> name.contains(searchedName)) .flatMap(this::lookupArtist)

.filter(artist -> artist.getNationality()

.contains(searchedNationality)) .take(maxResults);

}

At we get an Observable of the saved artist names. The higher-order functions on the Observable class are similar to those on the Stream interface, so at and we’re

Reactive Programming | 153

able to filter by artist name and nationality, in a similar manner as if we were using a Stream.

At we replace each name with its Artist object. If this were as simple as calling its constructor, we would obviously use the map operation. But in this case we need to compose a sequence of calls to external web services, each of which may be done in its own thread or on a thread pool. Consequently, we need to replace each name with an Observable representing one or more artists. So we use the flatMap operation.

We also need to limit ourselves to maxResults number of results in our search. To implement this at , we call the take method on Observable.

As you can see, the API is quite stream-like in usage. The big difference is that while a Stream is designed to compute final results, the RxJava API acts more like Completa bleFuture in its threading model.

In CompletableFuture we had to pay the IOU by calling complete with a value. Because an Observable represents a stream of events, we need the ability to push multiple values;

Example 9-16 shows how to do this.

Example 9-16. Pushing values into an Observable and completing it

observer.onNext("a");

observer.onNext("b");

observer.onNext("c");

observer.onCompleted();

We call onNext repeatedly, once for each element in the Observable. We can do this in a loop and on whatever thread of execution we want to produce the values from. Once we have finished with whatever work is needed to generate the events, we call onCom pleted to signal the end of the Observable. As well as the full-blown streaming ap‐

proach, there are also several static convenience factory methods for creating Observa ble instances from futures, iterables, and arrays.

In a similar manner to CompletableFuture, the Observable API also allows for fin‐

ishing with an exceptional event. We can use the onError method, shown in Example 9-17, in order to signal an error. The functionality here is a little different from CompletableFuture—you can still get all the events up until an exception occurs, but in both cases you either end normally or end exceptionally.

Example 9-17. Notifying your Observable that an error has occurred

observer.onError(new Exception());

As with CompletableFuture, I’ve only given a flavor of how and where to use the Observable API here. If you want more details, read the project’s comprehensive doc‐

umentation. RxJava is also beginning to be integrated into the existing ecosystem of Java

libraries. The enterprise integration framework Apache Camel, for example, has added a module called Camel RX that gives the ability to use RxJava with its framework. The Vert.x project has also started a project to Rx-ify its API.

Dalam dokumen Book Java 8 Lambdas EN (Halaman 166-169)