Общий статический метод слишком сильно ограничивает типы
В этом ответе Я попытался создать статический метод утилиты, чтобы сделать List
в Map
:
public static <K, T> Map<K, T> toMapBy(List<T> list,
Function<? super T, ? extends K> mapper) {
return list.stream().collect(Collectors.toMap(mapper, Function.identity()));
}
Это прекрасно работает. Однако я обнаружил, что метод не может использоваться во всех тех же контекстах, что и выражение list.stream().collect(...)
. Метод не такой гибкий.
List<Student> students = Arrays.asList();
Map<Long, Student> studentsById1 = students.stream()
.collect(Collectors.toMap(Student::getId, Function.identity()));
Map<Long, Student> studentsById2 = toMapBy(students, Student::getId);
Map<Long, Person> peopleById1 = students.stream()
.collect(Collectors.toMap(Student::getId, Function.identity()));
Map<Long, Person> peopleById2 = toMapBy(students, Student::getId); // compile error!
В этом примере Student
является подтипом Person
и имеет метод getId
, который возвращает Long
.
Последний оператор не работает с incompatible types: inference variable T has incompatible bounds ...
(JDK 1.8.0_25). Есть ли способ определить параметры типа, чтобы статический метод работал в тех же контекстах, что и содержащееся выражение?
Ответы
Ответ 1
Вы можете добавить параметр типа для значений карты, чтобы они отличались от T:
public static <K, V, T extends V> Map<K, V> toMapBy(List<T> list,
Function<? super T, ? extends K> mapper) {
return list.stream().collect(Collectors.toMap(mapper, Function.identity()));
}
Ответ 2
В последней строке вызывается метод toMapBy
, в котором компилятор указывает тип Student
для T
. Поэтому он явно возвращает List<Long, Student>
.
Но дженерики не ковариантны!
Это означает, что вы не можете назначить List<Long, Student>
переменной типа List<Long, Person>
, потому что они не находятся в отношении подтипа.
Решение заключается в использовании формы подтипа:
Map<Long, ? extends Person> peopleById2 = toMapBy(students, Student::getId); // no compiler error
Ответ 3
С этим битом:
Map<Long, Person> peopleById1 = students.stream()
.collect(Collectors.toMap(Student::getId, Function.identity()));
Обратите внимание, что вы не предоставляете аргументы Function.identity()
. Компилятор может вывести его как Function.<Person>identity()
для устранения разницы, налагаемой присвоением возвращаемого значения.
Это должно быть достаточно хорошо для вашей цели:
public static <K, T> Map<K, T> toMapBy(
List<? extends T> list, // <- note
Function<? super T, ? extends K> mapper
) {
...
}
Теперь элементы списка могут быть подтипом значений карты. Или вы можете определить третий параметр, например @Alex.