К сожалению, вывод типа имеет действительно сложную спецификацию, поэтому очень трудно решить, соответствует ли конкретное нечетное поведение спецификации или это просто ошибка компилятора.
Существует два хорошо известных намеренныхограничения на вывод типа.
Во-первых, целевой тип выражения не используется для выражений получателя, т. е. в цепочке вызовов методов.Поэтому, когда у вас есть оператор вида
TargetType x = first.second(…).third(…);
, TargetType
будет использоваться для вывода универсального типа вызова third()
и его выражений аргументов, но не для вызова second(…)
.Таким образом, вывод типа для second(…)
может использовать только автономный тип first
и выражения аргумента.
Это не проблема здесь.Поскольку автономный тип list
четко определен как List<String>
, нет проблем с выводом типа результата Stream<String>
для вызова stream()
, а проблемный вызов collect
является последним вызовом методацепочка, которая может использовать целевой тип TreeMap<Integer, List<String>>
для вывода аргументов типа.
Второе ограничение касается разрешения перегрузки.Разработчики языка сделали преднамеренное сокращение, когда дело доходит до циклической зависимости между неполными типами выражений аргументов, которые должны знать фактический целевой метод и его тип, прежде чем они смогут помочь определить правильный метод для вызова.
Этотакже не применяется здесь.В то время как groupingBy
перегружен, эти методы отличаются количеством параметров, что позволяет выбрать единственный подходящий метод, не зная типов аргументов.Также можно показать, что поведение компилятора не меняется, когда мы заменим groupingBy
другим методом, который имеет предполагаемую подпись, но не перегружен.
Ваша проблема может быть решена с помощью, например,
TreeMap<Integer, List<String>> res = list.stream()
.collect(Collectors.groupingBy(
(String s) -> s.charAt(0) % 3,
() -> new TreeMap<>(Comparator.reverseOrder()),
Collectors.toList()
));
При этом используется лямбда-выражение с явной типизацией для функции группировки, которое, хотя на самом деле не влияет на типы ключа карты, заставляет компилятор находить фактические типы.
Хотя использование лямбда-выражений с явным типом вместо неявно типизированных может иметь значение для разрешения перегрузки метода, как сказано выше, это не должно применяться здесь, поскольку этот конкретный сценарий не является проблемой перегруженных методов.
Как ни странно, даже следующее изменение устраняет ошибку компилятора:
static <X> X dummy(X x) { return x; }
…
TreeMap<Integer, List<String>> res = list.stream()
.collect(Collectors.groupingBy(
s -> s.charAt(0) % 3,
dummy(() -> new TreeMap<>(Comparator.reverseOrder())),
Collectors.toList()
));
Здесь мы не помогаем ни с каким дополнительным явным типом, а также не меняем формальную природу лямбда-выражений, но все же, компилятор внезапно выводит все типы правильно.
Поведениеили, кажется, связано с тем, что лямбда-выражения с нулевым параметром всегда явно типизированы.Поскольку мы не можем изменить природу лямбда-выражения с нулевым параметром, я создал следующий альтернативный метод сборщика для проверки:
public static <T, K, D, A, M extends Map<K, D>>
Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,
Function<Void,M> mapFactory,
Collector<? super T, A, D> downstream) {
return Collectors.groupingBy(classifier, () -> mapFactory.apply(null), downstream);
}
Затем, используя неявно типизированное лямбда-выражение, поскольку фабрика карт компилируется без проблем:
TreeMap<Integer, List<String>> res = list.stream()
.collect(groupingBy(
s -> s.charAt(0) % 3,
x -> new TreeMap<>(Comparator.reverseOrder()),
Collectors.toList()
));
, тогда как использование лямбда-выражения с явной типизацией вызывает ошибку компилятора:
TreeMap<Integer, List<String>> res = list.stream()
.collect(groupingBy( // compiler error
s -> s.charAt(0) % 3,
(Void x) -> new TreeMap<>(Comparator.reverseOrder()),
Collectors.toList()
));
По моему мнению, даже если спецификация поддерживает это поведение, его следует исправить, как следствиепредоставления явных типов никогда не должно заключаться в том, что вывод типов становится хуже, чем без него.Это особенно верно для лямбда-выражений с нулевым аргументом, которые мы не можем превратить в неявно типизированные.
Это также не объясняет, почему превращение всех аргументов в лямбда-выражения с явным типом также устранит ошибку компилятора.