├── README.md ├── pom.xml └── src └── test └── java └── ExamplesTest.java /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | Java 8 introduced big improvements to its handling of futures. 4 | There is now [CompletionStage](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletionStage.html) 5 | and [CompletableFuture](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html) which are much better 6 | adapted for asyncronous code compared to 7 | the old [Future](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Future.html) interface. 8 | 9 | This guide is mostly targetted at developers who want to start using the futures 10 | in Java 8, but come from a [Google Guava](https://github.com/google/guava) 11 | ([ListenableFuture](https://github.com/google/guava/wiki/ListenableFutureExplained)) background. 12 | 13 | # Guava -> Java 8 cheat sheet 14 | 15 | This is a one way mapping. If you used to use Guava futures, you probably want to 16 | do the java 8 equivalent. This does not mean that the reverse mapping works. 17 | 18 | | Guava style | Java 8 style | 19 | |---|---| 20 | | [`Listenablefuture.addListener(callback)`](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/util/concurrent/ListenableFuture.html#addListener%28java.lang.Runnable,%20java.util.concurrent.Executor%29) | [`future.whenComplete(callback)`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletionStage.html#whenComplete-java.util.function.BiConsumer-) | 21 | | [`Futures.addCallback(callback)`](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/util/concurrent/Futures.html#addCallback%28com.google.common.util.concurrent.ListenableFuture,%20com.google.common.util.concurrent.FutureCallback%29) | [`future.whenComplete(callback)`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletionStage.html#whenComplete-java.util.function.BiConsumer-) | 22 | | [`Futures.transform(future, function)`](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/util/concurrent/Futures.html#transform%28com.google.common.util.concurrent.ListenableFuture,%20com.google.common.base.Function%29) | [`future.thenApply(function)`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletionStage.html#thenApply-java.util.function.Function-) | 23 | | [`Futures.transform(future, asyncFunction)`](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/util/concurrent/Futures.html#transform%28com.google.common.util.concurrent.ListenableFuture,%20com.google.common.util.concurrent.AsyncFunction%29) | [`future.thenCompose(function)`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletionStage.html#thenCompose-java.util.function.Function-) | 24 | | [`Futures.dereference(future)`](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/util/concurrent/Futures.html#dereference%28com.google.common.util.concurrent.ListenableFuture%29) | `future.thenCompose(stage -> stage)` | 25 | | [`Futures.immediateFuture(value)`](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/util/concurrent/Futures.html#immediateFuture%28V%29) | [`CompletableFuture.completedFuture(value)`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html#completedFuture-U-) | 26 | | [`Futures.immediateFailedFuture(throwable)`](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/util/concurrent/Futures.html#immediateFailedFuture%28java.lang.Throwable%29) | `new CompletableFuture().completeExceptionally(throwable)` | 27 | | [`Futures.withFallback(future, function)`](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/util/concurrent/Futures.html#withFallback%28com.google.common.util.concurrent.ListenableFuture,%20com.google.common.util.concurrent.FutureFallback%29) | [`future.exceptionally(function)`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletionStage.html#exceptionally-java.util.function.Function-) | 28 | | [`SettableFuture.create()`](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/util/concurrent/SettableFuture.html#create%28%29) | [`new CompletableFuture()`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html#CompletableFuture--) | 29 | 30 | `Futures.withFallback(future, asyncFunction)` can be mapped to 31 | ```java 32 | stage 33 | .thenApply(v -> CompletableFuture.completedFuture(v)) 34 | .exceptionally(asyncFunction) 35 | .thenCompose(stage -> stage) 36 | ``` 37 | 38 | # Subtle differences 39 | 40 | Guava uses the terms `Function` and `AsyncFunction` where Async means that the 41 | function returns a new future. (This means that all methods that use a regular 42 | `Function` can be implemented with the method that takes an `AsyncFunction` and 43 | wraps the value in a `Futures.immediateFuture(x)`.) 44 | 45 | The equivalent of methods that take an `AsyncFunction` in Java 8 is 46 | `thenCompose` (but that is only implemented for successful futures, not exceptions). 47 | 48 | If you want to transform an exception by returning a different future you 49 | have to use a workaround (see below). 50 | 51 | There are Async variants of the methods for futures in Java 8 too, but 52 | that means something completely different: the function or callback you pass in 53 | will just be executed on a different thread. 54 | 55 | # Futures-extra 56 | 57 | There is a library called [futures-extra](https://github.com/spotify/futures-extra) which provides 58 | more helpers for dealing with both Guava and Java 8 futures. 59 | 60 | Particularly for Java 8, there are some useful helper methods. 61 | CFE here is short hand for CompletableFuturesExtra. 62 | ```java 63 | 64 | // convert a java 8 future to a guava future 65 | ListenableFuture output = CFE.toListenableFuture(CompletionStage input); 66 | 67 | // convert a guava future to a java 8 future 68 | CompletableFuture output = CFE.toCompletableFuture(ListenableFuture input); 69 | 70 | // equivalent to Futures.immediateFailedFuture(throwable) in guava 71 | CompletableFuture output = CFE.exceptionallyCompletedFuture(throwable); 72 | 73 | // equivalent to Futures.withFallback(Futures.transform(future, successAsyncFunction), failureAsyncFunction) 74 | // implemented as CFE.dereference(stage.thenCompose(function)) 75 | CompletionStage output = CFE.handleCompose(stage, function); 76 | 77 | // equivalent to Futures.withFallback(failureAsyncFunction) 78 | // implemented as CFE.dereference(CFE.wrap(stage.thenCompose(function)) 79 | CompletionStage output = CFE.exceptionallyCompose(stage, function); 80 | 81 | // equivalent to FuturesExtra.checkCompleted(future); 82 | // throws IllegalStateException if stage is not completed. 83 | CFE.checkCompleted(stage); 84 | 85 | // equivalent to FuturesExtra.getCompleted(future); 86 | // returns the output if the stage is completed, otherwise throws exception. 87 | // unlike variants of future.get(), this never blocks. 88 | T output = CFE.getCompleted(stage); 89 | 90 | // equivalent to Futures.dereference(future) 91 | // implemented as stage.thenCompose(stage -> stage) 92 | CompletionStage output = CFE.dereference(CompletionStage> stage); 93 | ``` 94 | 95 | ### TODO: document allAsList / successfulAsList 96 | ### TODO: document some of the things in futures-extra 97 | 98 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | Java 8 future guide 5 | dummy 6 | java8-future-guide 7 | 0.0.1-SNAPSHOT 8 | 9 | 10 | 11 | com.google.guava 12 | guava 13 | 18.0 14 | 15 | 16 | com.spotify 17 | futures-extra 18 | 2.5.0 19 | 20 | 21 | junit 22 | junit 23 | 4.11 24 | 25 | 26 | 27 | 28 | 29 | 30 | org.apache.maven.plugins 31 | maven-compiler-plugin 32 | 3.2 33 | 34 | -XDignore.symbol.file 35 | 1.8 36 | 1.8 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/test/java/ExamplesTest.java: -------------------------------------------------------------------------------- 1 | import com.google.common.util.concurrent.Futures; 2 | import com.google.common.util.concurrent.ListenableFuture; 3 | import com.google.common.util.concurrent.SettableFuture; 4 | 5 | import com.spotify.futures.CompletableFuturesExtra; 6 | import com.spotify.futures.FuturesExtra; 7 | 8 | import org.junit.Test; 9 | 10 | import java.util.concurrent.CompletableFuture; 11 | import java.util.concurrent.CompletionStage; 12 | import java.util.concurrent.ExecutionException; 13 | 14 | import static org.junit.Assert.assertEquals; 15 | import static org.junit.Assert.assertFalse; 16 | import static org.junit.Assert.fail; 17 | 18 | public class ExamplesTest { 19 | 20 | @Test 21 | public void testImmediateFuture() throws Exception { 22 | final ListenableFuture hello1 = Futures.immediateFuture("hello"); 23 | assertEquals("hello", hello1.get()); 24 | 25 | final CompletableFuture hello2 = CompletableFuture.completedFuture("hello"); 26 | assertEquals("hello", hello2.get()); 27 | 28 | } 29 | 30 | @Test 31 | public void testImmediateFailedFuture() throws Exception { 32 | final ListenableFuture hello1 = Futures.immediateFailedFuture(new IllegalArgumentException("hello")); 33 | try { 34 | hello1.get(); 35 | fail(); 36 | } catch (ExecutionException e) { 37 | assertEquals("hello", e.getCause().getMessage()); 38 | } 39 | 40 | final CompletableFuture hello2 = CompletableFuturesExtra.exceptionallyCompletedFuture(new IllegalArgumentException("hello")); 41 | 42 | try { 43 | hello2.get(); 44 | fail(); 45 | } catch (ExecutionException e) { 46 | assertEquals("hello", e.getCause().getMessage()); 47 | } 48 | } 49 | 50 | @Test 51 | public void testTransform() throws Exception { 52 | final ListenableFuture result1 = FuturesExtra.syncTransform(Futures.immediateFuture("hello"), s -> s + s); 53 | assertEquals("hellohello", result1.get()); 54 | 55 | final CompletableFuture result2 = CompletableFuture.completedFuture("hello").thenApply(s -> s + s); 56 | assertEquals("hellohello", result2.get()); 57 | } 58 | 59 | @Test 60 | public void testFallback() throws Exception { 61 | final ListenableFuture result1 = Futures.withFallback(Futures.immediateFailedFuture(new IllegalArgumentException("hello")), 62 | t -> Futures.immediateFuture("recover")); 63 | assertEquals("recover", result1.get()); 64 | 65 | final CompletableFuture result2 = CompletableFuturesExtra.exceptionallyCompletedFuture(new IllegalArgumentException("hello")).exceptionally(t -> "recover"); 66 | assertEquals("recover", result2.get()); 67 | } 68 | 69 | @Test 70 | public void testDeferredFallback() throws Exception { 71 | 72 | final ListenableFuture failedFuture1 = Futures.immediateFailedFuture(new IllegalArgumentException("hello")); 73 | final SettableFuture deferred1 = SettableFuture.create(); 74 | final ListenableFuture result1 = Futures.withFallback(failedFuture1, t -> deferred1); 75 | assertFalse(result1.isDone()); 76 | deferred1.set("world"); 77 | assertEquals("world", FuturesExtra.getCompleted(result1)); 78 | 79 | final CompletableFuture failedFuture2 = CompletableFuturesExtra.exceptionallyCompletedFuture(new IllegalArgumentException("hello")); 80 | final CompletableFuture deferred2 = new CompletableFuture<>(); 81 | final CompletionStage result2 = CompletableFuturesExtra.exceptionallyCompose(failedFuture2, throwable -> deferred2); 82 | 83 | assertFalse(result2.toCompletableFuture().isDone()); 84 | deferred2.complete("world"); 85 | assertEquals("world", CompletableFuturesExtra.getCompleted(result2)); 86 | } 87 | 88 | @Test 89 | public void testCombine() throws Exception { 90 | final ListenableFuture f1 = Futures.immediateFuture("a"); 91 | final ListenableFuture f2 = Futures.immediateFuture("b"); 92 | final ListenableFuture f3 = Futures.immediateFuture("c"); 93 | assertEquals("abc", FuturesExtra.syncTransform3(f1, f2, f3, (s1, s2, s3) -> s1 + s2 + s3).get()); 94 | 95 | final CompletableFuture g1 = CompletableFuture.completedFuture("a"); 96 | final CompletableFuture g2 = CompletableFuture.completedFuture("b"); 97 | final CompletableFuture g3 = CompletableFuture.completedFuture("c"); 98 | assertEquals("abc", g1.thenCombine(g2, (s1, s2) -> s1 + s2).thenCombine(g3, (s12, s3) -> s12 + s3).get()); 99 | } 100 | 101 | @Test 102 | public void testDereference() throws Exception { 103 | final ListenableFuture> f = Futures.immediateFuture(Futures.immediateFuture("hello")); 104 | final ListenableFuture result1 = Futures.dereference(f); 105 | assertEquals("hello", FuturesExtra.getCompleted(result1)); 106 | 107 | final CompletableFuture> g = CompletableFuture.completedFuture(CompletableFuture.completedFuture("hello")); 108 | 109 | final CompletionStage result2 = CompletableFuturesExtra.dereference(g); 110 | assertEquals("hello", CompletableFuturesExtra.getCompleted(result2)); 111 | 112 | } 113 | } 114 | --------------------------------------------------------------------------------