一个函数签名引发的思考

今天当我偶然间看到一个很常用的Java静态方法时它的函数签名引起了我的注意

public static <T extends Comparable<? super T>> void sort(List<T> list)

其中对于泛型参数的定义让我有些疑惑,既然是排序方法那么只需要实现Comparable接口就好。但是为什么后面Comparable接口中的泛型参数还要以泛型T为下界呢?
抱着这个疑惑回顾了一下Java中关于类型通配符,协变,逆变等概念的回顾并未得到一个合理的解释。在我看来

 public static <T extends Comparable<T>> void sort(List<T> list)

跟之前的函数签名并没有区别。

但是我知道JDK源码必定是字斟句酌,并非如此简单。于是我打算努力写出一个例子来表现出两个函数签名之间的区别。

List<Integer> list = new ArrayList<>();
        Collections.sort(list);

但是很显然它失败了。思索再三,我又下了如下代码。

public class Main1 {
        public static void main(String[] args) {
            List<AA> x = new ArrayList<>();
            sort(x);
        }

        static <T extends Comparable<? super T>> void sort(List<T> list) {
        }
    }

    class AA extends BB {
    }

    class BB implements Comparable<BB> {
        @Override
        public int compareTo(BB o) {
            return 0;
        }
    }

然后当我将自定义方法中的函数签名修改为

static <T extends Comparable<T>> void sort(List<T> list)

IDE果然给出了编译错误。到这里按说应该结束了。毕竟已经解决掉了疑惑。但是关于泛型中extends和super的使用却让我难以确定,到底我该遵从什么样的原则来进行函数签名的设计呢?答案就是get-put原则。

*The get-put principle
Use an extends wildcard when you only get values out of a structure, use a super wildcard when you only put values into a structure, and don’t use a wildcard when you do both.*

get-put原则的描述来源于Naftalin和Wadler合作的一本书,Java Generics and Collections,简单翻译如下

当需要从某种结构中获取值时使用extends通配符,当需要向某种结构中存放值时采用super通配符。当既需要存值又需要取值时不要使用通配符。

该原则简单易懂,以此为准则分析上述Collections.sort()的函数签名会发现,当我们排序时可看作需要从实现Comparable接口的众多实现类中获取我们要排序的类型故前半部分的签名采用extends通配符。而当我们进行排序时需要把排序的逻辑放到对应的排序方法中,故后半部分采用了super通配符。看起来该函数签名似乎陷入了某种循环

T extends Comparable<? super T>

事实上并没有。我们需要排序的是实现Comparable接口的类型。但实现该类型并非一定要自己实现Comparable接口,也可以通过继承某些实现了Comparable接口的类型来达到相同的目的。
除此之外,get-put原则最后部分描述的内容也很好理解了。如果既要获取泛型T或者其子类型的值又要存入T类型或者其父类型的值,那么当前的类型必定是只能是T本身,所以自然不需要使用任何类型通配符啦!

参考链接:https://www.ibm.com/developerworks/java/library/j-jtp07018/index.html?ca=drs-

猜你喜欢

转载自blog.csdn.net/javainmyheart/article/details/79998708