- 处理非阻塞调用的传统方法是使用事件处理器,程序员为任务完成之后出现的动作注册一个处理器。当然,如果下一个动作也是异步的,在它之后的下一个动作会在一个不同的事件处理器中。尽管程序员会认为,先做步骤1,然后是步骤2,再完成步骤3,但实际上程序逻辑会分散到不同的处理器中。如果必须增加错误处理,情况会更糟糕。假设步骤2是用户登录。可能需要重复这个步骤,因为用户输入凭据时可能会出错。要尝试在一组事件处理器中实现这样一个控制流,或者想要理解所实现的这样一组事件处理器,会很有难度。
Java SE
8的CompletableFuture
类提供了一种候选方法。与事件处理器不同,可完成future可以组合
。- 例如,假设我们希望从一个Web页面抽取所有链接来建立一个网络爬虫。下面假设有这样一个方法
public void CompletableFuture<String> readPage(URL url)
- Web页面可用时这会生成这个页面的文本。如果方法:
public static List<URL> getLinks(String page)
- 生成一个HTML页面的URL,可以调度当页面可用时在调用这个方法:
CompletableFuture<String> contents = readPage(url);
CompletableFuture<List<URL>> links = contents.thenApply(Parser::getLinks);
thenApply
方法不会阻塞。它会返回另一个future
。第一个future
完成时,其结果会提供给getLinks
方法,这个方法的返回值就是最终的结果。- 有很多的方法组合可完成future。先来看处理单个future的方法
- 调用
CompletableFuture<U> future.thenApply(f);
CompletableFuture<U> future.thenApplyAsync(f);
- 会调用一个future,可用时会对future的结果应用f。第二个调用会在另一个线程中运行f。
thenCompose
方法没有取函数T -> U,而是取函数T -> CompletableFuture<U>
。这听上去相当抽象,不过实际上也很自然。考虑从一个给定URL读取一个Web页面的动作。不用提供方法:
public String blockingReadPage(URL url)
更巧妙的做法是让方法返回一个future
public CompletableFuture<String> readPage(URL url)
- 现在,假设我们还有一个方法可以从用户输入得到URL,这可能从一个对话框得到,而在用户点击OK按钮之前不会得到答案。这也是将来的一个事件:
public CompletableFuture<URL> getURLInput(String prompt)
- 这里我们有两个函数
T -> CompletableFuture<U>
和 U -> CompletableFuture<V>
。显然,如果第二个函数在第一个函数完成时调用,它们就可以组合为一个函数 T -> CompletableFuture<V>
。这正是thenCompose
所做的。 - 表中第3个方法强调了,
CompletableFuture
中抛出一个异常时,会捕获这个异常并在调用get方法时包装在一个受查异常ExecutionException
中。不过,可能get
永远也不会被调用。要处理异常,可以使用handle
方法。调用指定的函数时要提供结果(如果没有则为null)和异常(如果没有则为null),这种情况下就要意义了。 - 其余的方法结果都为void,通常用在处理管线的最后
- 下面来看组合多个
future
方法
- 前3个方法并行运行一个
CompletableFuture<T>
和一个CompletableFuture<U>
动作,并组合结果。 - 接下来3个方法并行运行两个
CompletableFuture<T>
动作。一旦其中一个完成,就传递它的结果,并忽略另一个结果。 - 最后的静态
allOf
和anyOf
方法取一组可完成future(数目可变),并生成一个CompletableFuture<Void>
,它会在所有这些future
都完成时或者其中任意一个future
完成时结束。不会传递任何结果。