Почему лямбда-выражение не может быть разыменовано?
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