在this answer中,我尝试创建一个静态实用工具方法,将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)。有没有办法定义类型参数,以便静态方法在与它包含的表达式相同的上下文中工作?
答案 0 :(得分:6)
您可以为地图的值添加类型参数,以便它们可以与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()));
}
答案 1 :(得分:1)
您的最后一行调用方法toMapBy
,其中编译器推断Student
的类型T
。所以它显然会返回List<Long, Student>
。
But generics aren't covariant!
这意味着,您无法将List<Long, Student>
分配给List<Long, Person>
类型的变量,因为它们不属于子类型关系。
解决方案是使用子类型:
Map<Long, ? extends Person> peopleById2 = toMapBy(students, Student::getId); // no compiler error
答案 2 :(得分:0)
这一点:
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
) {
...
}
现在List的元素可以是Map值的子类型。或者您可以定义第三个参数,如@Alex建议的那样。