Почему лямбда-выражение не может быть разыменовано?

import java.util.*;

class TreeMapDemo
{
    public static void main(String args[])
    {
        Comparator <String> c1 = (str1, str2) -> 0;

        Comparator <String> c2 = (str1, str2) -> 1;

        TreeMap <String, Double> tm1 = new TreeMap(c1.thenComparing(c2));
        //Working fine

        TreeMap <String, Double> tm2 = new TreeMap(((str1, str2) -> 0).thenComparing((str1, str2) -> 1));
        //Error: Lambda expression not expected here
        //<none> can not be dereferenced
    }
}

Мой запрос:

Если

c1 = (str1, str2) -> 0 а также c2 = (str1, str2) -> 1,

Тогда почему

c1.thenComparing(c2) работает нормально и

((str1, str2) -> 0).thenComparing((str1, str2) -> 1) не является?

3 ответа

Решение

Java использует контекст лямбда-выражения для определения его типа. Присвоение лямбда-переменной переменной или передача ее в качестве аргумента метода обеспечивает достаточный контекст, но вызывает метод - такой как thenComparing() - лямбда не дает никакого полезного контекста вообще.

Это должно работать:

Comparator <String> c1 = (str1, str2) -> 0;
TreeMap <String, Double> tm2 = new TreeMap(c1.thenComparing((str1, str2) -> 1));

Более подробно:

Лямбда-выражение оценивает объект типа, который реализует некоторый функциональный интерфейс, и Java полагается на контекст, в котором появляется выражение, чтобы определить, какой это функциональный интерфейс. Во втором случае аргумент конструктора не является контекстом лямбды; вызов thenComparing() является. Именно возвращаемое значение этого метода является аргументом конструктора. Но прежде чем Java сможет определить что-либо о thenComparing() ему нужно знать тип объекта, для которого он вызывается. Тип сообщает о методе, а не наоборот.

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

Лямбда-выражения должны быть нацелены на определенный тип.

c1 = (str1, str2) -> 0;

все в порядке, потому что тип c1 известен.

Сам по себе (str1, str2) -> 0 неоднозначно.

Например

BiFunction<String, String, Integer> x = (str1, str2) -> 0;

имеет смысл тоже.

Чтобы сделать это конкретным, посмотрите на этот пример.

public interface Foo extends BiFunction<String, String, Integer> {
    default Comparator<String> thenComparing(Comparator<String> comparator) {
        return String.CASE_INSENSITIVE_ORDER;
    }
}

public static void main(String[] args) {
    Foo foo = (str1, str2) -> 0;        // overriding the sole method of BiFunction
    TreeMap<String, Double> treeMap = new TreeMap<>(foo.thenComparing((str1, str2) -> 1));
}

Это прекрасно компилируется. Так что, если мы просто напишем

new TreeMap<>((str1, str2) -> 0).thenComparing((str1, str2) -> 1));

у компилятора нет возможности узнать, (str1, str2) -> 0 это Foo или Comparator<String>, Таким образом, логический вывод не работает в обратном направлении.

Это ограничение вывода типа в Java. Прежде всего, вы используете необработанный тип, и компаратор будет преобразован в Comparator и не Comparator<String>

TreeMap <String, Double> tm1 = new TreeMap((str1, str2) -> 0);// wouldn't work

где

TreeMap <String, Double> tm1 = new TreeMap<>((str1, str2) -> 0);// works

Теперь вы не можете вызывать определенный метод для типа, пока он не будет выведен.

Comparator<String> c1 = ((str1, str2) -> 0).thenComparing((str1, str2) -> 1); // doesn't work

Если вы не предоставите некоторую дополнительную помощь компилятору

Comparator <String> c1 = ((Comparator<String>)((str1, str2) -> 0)).thenComparing((str1, str2) -> 1); // works
Другие вопросы по тегам