Функциональные интерфейсы, лямбды и Stream API

Вопрос 1

Что будет, если попытаться откомпилировать и запустить следующий код:

List<String> lines = new LinkedList<>();

lines.add("Some puzzle");

for (String line : (Iterable<String>) lines::iterator) {

System.out.println(line);

}

а. Код не откомпилируется, потому что в круглых скобках оператора for после двоеточия могут быть только массивы и коллекции.
б. Код не откомпилируется, потому что ссылка на метод lines::iterator не может быть приведена к интерфейсу Iterable<String>.
в. Код откомпилируется и выполнится без проблем, выведя на консоль надпись "Some puzzle".

в. Код откомпилируется и выполнится без проблем, выведя на консоль надпись "Some puzzle".

В круглых скобках оператора for после двоеточия могут быть либо массив, либо любые объекты, классы которых имплементируют интерфейс Iterable. Коллекции имплементируют этот интерфейс и поэтому широко используются в таком цикле for. Но вообще ничто не запрещает написать свой класс, который бы имплементировал этот интерфейс и такой класс станет возможно использовать в for, как любую библиотечную коллекцию.

Ссылки на методы вообще прекрасно приводятся к интерфейсам, если это функциональный интерфейс и сигнатура, а также возвращаемое значение метода, совпадают с сигнатурой и возвращаемым значением единственного абстрактного метода функционального интерфейса. Метод lines::iterator прекрасно приводится к Iterable<String>. Собственно этот метод и добавлен в java.util.Collection (который в итоге наследует и имплементирует наш LinkedList), чтобы имплементировать интерфейс Iterable<E>.

На самом деле приведённый пример лишь иллюстрирует, что происходит в операторе for "под капотом". Когда мы помещаем справа от двоеточия коллекцию, оператор смотрит на неё не как на коллекцию, а как на сущность, имплементирующую интерфейс Iterable, а значит у которой можно выполнить метод iterator, который вернёт собственно итератор коллекции, с помощью которого можно будет перебирать элементы этой самой коллекции.

Вопрос 2

Что будет, если попытаться откомпилировать и запустить следующий код:

Arrays.asList(Math::random, Math::random)

.forEach(rand -> {

System.out.println(rand.get());

});

а. Код не откомпилируется.
б. Код откомпилируется, но во время выполнения программы возникнет исключительная ситуация.
в. Код откомпилируется и выведет в консоль два случайных числа.

а. Код не откомпилируется.

Компилятор выдаст ошибку: varargs mismatch; Object is not a functional interface. Проблема в этой части выражения:

Arrays.asList(Math::random, Math::random)

Метод asList() определён следующим образом:

public static <T> List<T> asList(T... a)

Использование vararg'ов означает, что компилятор гарантирует нам, что все аргументы (у нас их два) будут одного типа. Но в данном случае компилятор не может вывести тип ни одной из ссылок. Поэтому первую ссылку он сочтёт типом Object и при сравнении со второй ссылкой придёт к выводу, что Object не состоит в одной иерархии наследования (is not a) с функциональным интерфейсом. Что по-существу, верно.

Для решение этой проблемы компилятору нужно просто каким-то образом подсказать, к какому конкретно типу (конкретному функциональному интерфейсу) должны сводиться аргументы метода toList(). Например это можно сделать так:

List<Supplier> generators = Arrays.asList(Math::random, Math::random);

generators.forEach(rand -> {

System.out.println(rand.get());

});

Из первой строки компилятор может вывести, что тип параметра списка (List<T>) должен совпадать с типом аргументов метода asList(). Слева от знака равенства переменная объявлена как List<Supplier>, что означает, что аргументы метода asList() должны являться или сводиться (is a) к функциональному интерфейсу Supplier. И действительно, метод Math::random сводится к этому функциональному интерфейсу.

В данном случае у компилятора есть контекст, из которого он может определить, что Math::random -- не просто Object, а Supplier. Поэтому исправленный код откомпилируется.

Кроме того, есть и другие способы помочь компилятору.