Java8中对Lambda表达式中方法参数的类型推断(二)

来源:互联网 发布:兰州网络教育学生平台 编辑:程序博客网 时间:2024/05/17 08:53

接着上次的问题,我先来看看Collections类的sort()方法。

 public static <T> void sort(List<T> list, Comparator<? super T> c) {        list.sort(c);    }

疑惑:JDK的设计者们为什么把Collections<T>中方法public static <T> void sort(List<T> list, Comparator<? super T> c)里的比较器泛型要定义成<? super T>而不是直接<T>。按照我们直观的理解,我们就是对list里的每个T类型的元素排序,比较器里的类型直接写成T不就可以了吗?实际上,设计者是这样来考虑的。

思路:假设有这么一个Student类Student implements A, B, C ,因为他实现了A,B,C三个接口,所以我们可以按接口A, B或C中任何一个的特性去排序,但是我比较完之后最终返回的一定是List<Student>这种类型的。我们定义Comparator<A> c,就是按A类型排序;定义Comparator<C> c,就是按C类型排序。也就是我们可以按自己的类型(Student),或者是他的父类型(A, B, C)这种方式来去进行比较,比较的点或者参照物是不一样的,但我们最终比较完之后返回回来的一定List<Student>这种类型的。 所以<? super T><T>从语义上看更宽泛一些。

其实public static <T> void sort(List<T> list, Comparator<? super T> c)里的泛型Comparator<? super T> c语义上更宽泛一些,但实际上比较的就是T类型本身

现在我们要对list排序,由于list集合里面的元素是String类型的,所以我们的比较器只能是以String类型,或者String更高的类型去比较list这个集合里的元素(这符合我们刚才说的那个泛型的语义化)。

那么编译器他这怎么能推断出这个item一定是个String呢?

按上面说的,这里的类型其实可以是String(< T>),也可以是String之上的类型(< ? super T>)如:Serializable,但是在我们没指定类型时候,编译器应该默认认为是String(< T>)。

这就是为什么,下面这lambda表达式可以直接推断出参数item的类型为String。

public class MyComparatorTest2 {    public static void main(String[] args) {        List<String> list = Arrays.asList("nihao", "hello", "world", "welcome");        //按字符串长度排序        Collections.sort(list, (item1, item2) -> item1.length() - item2.length() );        list.forEach(System.out::println);    }}

再看之前报错推断不出类型参数的那句代码:
这里写图片描述

这里要注意的是,我们这里的比较器是多层的,comparingInt返回一个比较器在调用reversed再返回一个比较器,最终我们需要的比较器是reversed返回的。第一个例子的lambda表达式就是一个比较器,我们直接就传入,java编译器很容易就推断出这个比较器里的类型参数。而第二个例子的lambda表达式仅仅作为第一层比较器comparingInt的参数传进去,它离这个上下文已经很远了,所以它对上下文的感知是很弱很远的,不能感知到集合list里的元素类型是什么!它就只能推断成Object类型。

我们可以验证一下,很简单,现在它是2层,lambda表达式在第一层,我们把reversed去掉,不在第一层了吗?按理java就能感知到它的类型了。
这里写图片描述

的确如此,现在编译器就可以推断出类型参数了。说明java编译器它能感知离上下文最近的,第二个reversed离上下文最近,我们可以推断它返回的比较器的类型参数,没问题,但是comparingInt(ToIntFunction<? super T> keyExtractor)里的参数ToIntFunction<? super T> keyExtractor离上下文很远了,无法推断其参数的类型参数。

Java的类型推断跟上下文很有关系。

2 0