通用静态方法过多地约束类型

时间:2014-11-01 17:55:01

标签: java generics java-8

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!

在此示例中,StudentPerson的子类型,并且getId方法返回Long

最后一个语句失败,incompatible types: inference variable T has incompatible bounds ...(JDK 1.8.0_25)。有没有办法定义类型参数,以便静态方法在与它包含的表达式相同的上下文中工作?

3 个答案:

答案 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建议的那样。