2014-11-01 47 views
1

this answer我试图创建一个静态实用方法,使一个ListMap通用静态方法限制类型太多

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)。有没有一种方法来定义类型参数,以便静态方法与其包含的表达式在相同的上下文中工作?

+0

[is-listdog-a-subclass-of-listanimal-why-arent-javas-generics-implicitly-polymorphic?](http://stackoverflow.com/questions/2745265/is-listdog-a-subclass-的-listanimal - 为什么 - ARENT-Java类,泛型隐-p)。 'Map '不能被引用到返回的'Map '。也许考虑使用'Map '。 – Pshemo 2014-11-01 18:05:32

+0

如果你可以使用'Map 'Map '的实例,那么这意味着通过这样的引用你可以在这张地图上放置任何类型的人,不仅是学生(换句话说泛型不是协变的)。 – Pshemo 2014-11-01 18:15:04

回答

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

最后一行调用方法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 
+1

是的,但问题是是否可以定义'toMapBy'的类型参数,以便它可以在与流表达式相同的上下文中工作。换句话说,为什么'Map '是倒数第二行中的赋值目标,而不是最后一行? – glts 2014-11-01 18:15:14

+0

有趣。但看看@Alex'的答案。这是解决方案。 – Seelenvirtuose 2014-11-01 18:29:49

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 
) { 
    ... 
} 

现在列表的元素可以是地图值的亚型。或者您可以定义像@Alex建议的第三个参数。