Завершаемое будущее - полный метод - PullRequest
0 голосов
/ 01 марта 2019

У меня есть код:

CompletableFuture<Integer> c1 = new CompletableFuture<Integer>()
        .thenApply((data) -> data * 2);
c1.thenAccept(System.out::println);

c1.complete(20);


CompletableFuture<Integer> c2 = new CompletableFuture<>();

c2.thenApply(data -> data * 2)
        .thenAccept(System.out::println);
c2.complete(20);

Вывод:

20 40

Вопрос:

  1. Почему существует разница в выходных данных между c1 и c2?
  2. Почему существует необходимость повторить тип будущего в c1, вызвав:

новое CompletableFuture <<strong> Integer > ()

1 Ответ

0 голосов
/ 01 марта 2019

Первое, что нужно отметить, - это то, что методы CompletableFuture (например, thenApply, thenAccept и т. Д.) Возвращают новый CompletableFuture экземпляр.Это образует своего рода «цепочку», где каждая новая стадия зависит от стадии, из которой она была создана - ее родительской стадии.Когда этап завершается, обычно или в исключительных случаях, результат передается на все его зависимые незавершенные * этапы (один и тот же этап может иметь несколько зависимых этапов).


* Как вы увидите ниже, вы можете завершить этап, даже если его родитель еще не завершен.Если и когда родительский этап завершится, зависимые этапы пройден не будут вызваны, так как он уже завершен.Последствия этого кратко описаны в ответе Хольгера на другой вопрос.


Вопрос 1

В первом примере у вас есть следующее:

CompletableFuture<Integer> c1 = new CompletableFuture<Integer>()
        .thenApply((data) -> data * 2);
c1.thenAccept(System.out::println);
c1.complete(20);

Здесь c1 - это этап, полученный из thenApply, а не new CompletableFuture<Integer>().Когда вы звоните c1.complete(20), вы завершаете этап thenApply (обычно) с заданным значением (20).Вызов complete эквивалентен Function, преобразующему результат предыдущего этапа и возвращающему 20.Теперь, когда thenApply завершено, значение увеличивается до thenAccept, в результате чего на консоль выводится 20.

Во втором примере у вас есть следующее:

CompletableFuture<Integer> c2 = new CompletableFuture<>();
c2.thenApply(data -> data * 2)
        .thenAccept(System.out::println);
c2.complete(20);

Здесь c2 - этап, полученный из new CompletableFuture<>(), который является родителем этапа thenApply.Итак, теперь, когда вы вызываете c2.complete(20), вы завершаете этап root , который переводит значение в thenApply.Затем Function преобразует значение, умножив его на 2 и увеличив результат до thenAccept.Это приводит к выводу 40 на консоль.


Вопрос 2

Причина, по которой вы должны повторить <Integer> в первом примере, заключается в том, что компилятор не может определить типпервого этапа без него.thenApply подпись:

<U> CompletableFuture<U> thenApply(Function<? super T, ? extends U>)

* T определяется типом этого CompletableFuture (тот, на котором вызывается метод).U определяется Function и, в некоторой степени, левой частью присвоения переменной, где это применимо.Это означает, что когда вы используете оператор diamond (<>), вы эффективно используете следующее:

CompletableFuture<Integer> c = new CompletableFuture<Object>()
        .thenApply(data -> data * 2);

// same as...
CompletableFuture<Integer> c = new CompletableFuture<>()
        .thenApply(data -> data * 2);

Поскольку компилятор знает о типе data, это умножение на Objectявляется недействительным;Object нельзя умножить на 2.Обратите внимание, что вышеприведенное будет справедливо, если вы просто измените Function с data -> data * 2 на data -> 2 (но, очевидно, эти две функции не эквивалентны).Это связано с тем, что левая часть присваивания связана с результатом thenApply, а не new CompletableFuture<>().

Когда вы явно указываете <Integer>, компилятор знает, что тип ввода (T) thenApply ступени - Integer, что означает, что data - Integer;Integer можно умножить на 2.

...