Java的泛型

163 阅读4分钟

Java的泛型(generics)

\quad 前面写了一篇文章专门用来讲HashMap的,确实HashMap在平时是非常常用的,但是其中所涉及的内容还远远不止这些,举个例子:

       HashMap<String,String> hashMap = new HashMap<>();
       hashMap.put(1,"");//Error

\quad首先我们知道,定义了一个键值对类型都是String类型的HashMap,如果你往里面丢入一个类型为非String类型的键值对,那么肯定是会报错的。但是如果把尖括号及里面的内容去掉的话,程序是可以通过编译的。

       HashMap hashMap = new HashMap<>();
       hashMap.put(1,"");//Pass

那么你会问,何苦要去掉括号呢?我明确的知道键值对的类型不是很方便嘛。确实,明确类型之后,编译器可以帮我们检查出很多插入数据类型不正确的问题,但是要知道泛型是在JDK1.5才出现的,那么在那个没有泛型的年代,是怎么确保类型安全的呢?

1.在没有泛型的年代,如何确保类型安全?

\quad要知道,大多数的语言在最开始的时候都是没有泛型的例如C#,Go(现在都不支持),Java。就拿前面例子中的hashMap来说,在JDK1.5之前,可以在其中放入任何类型的数值。但是问题来了,如果我们想构造一个只能传入<String,String>类型的HashMap要怎么做呢?可以用类似装饰器模式来实现,见代码:

public class MyStringHashMapDecorator {
    HashMap hashMap = new HashMap();

    public void put(String key , String value){
        hashMap.put(key,value);
    }

    public HashMap get(String key){
        return (HashMap) hashMap.get(key);
    }
}

\quad但是问题来了,对于茫茫可能发生的情况,按照这样的话,我岂不是需要每一种都列出对应的状态。所以在JDK1.5中推出了泛型,简化了这些麻烦的操作,但是Java为了向后兼容性,所以这种泛型只是在编译期间存在的,运行期间还是会通过类型擦除(Type Erasure),去掉泛型的。这一点可以通过看字节码可以看出:

\quad 那么问题来了,ArrayList<Integer>与ArrayList<Object>是同一个类嘛?
问题的答案是:是也不是,为什么这么说呢?前面提到,在运行期间经过类型擦除之后,他们都回归为ArrayList。说不是的原因在于他们之间不能相互赋值:
同时注意一点,ArrayList<String>并不是ArrayList<Object>的子类型,也是由于泛型擦除:

 public static void main(String[] args) {
       foo(new ArrayList<String>());//Error

    }
    public static void foo(ArrayList<Object> objects){
        //do something
    }

2.泛型的绑定

\quad 下面通过几个例子来引出泛型的使用:

  • 1.extends -限定泛型必须是本身或者是其子类(上边界)

\quad 比如现在要比较两个参数的大小,但是参数的类型不确定,那么就可以用到泛型的绑定了。
举例说明:

public static void main(String[] args) {
        System.out.println(max(1,2));//int
        System.out.println(max(1.0,2.0));//double
        System.out.println(max(1.0f,2.0f));//float
        System.out.println(max(1L,2L));//long
        System.out.println(max("abc","abd"));//long
    }

    private static  <T extends Comparable<T>> T max(T a, T b) {
        return a.compareTo(b) > 0 ? a : b;
    }

结果如下:

2
2.0
2.0
2
abd

Process finished with exit code 0

但是在这就有疑问了:
\quad 1.为什么Comparable明明是个接口,却要用extends关键字?
\quad 2.如果对两种都实现了Comparable接口,但是对二者类型不同的参数进行比较,会有什么结果呢?
回答:
\quad 1.在泛型中,关键字extends后面既可以跟类,也可以跟接口。但是在实际意义上更接近于是绑定类型的自身或者子类型,所以用extends关键字更加接近。
\quad 2.见代码:

在编译期间就会被检查出错误,但是有种办法可以绕过这种检查:
但是运行同样报错:

究其原因的话,可以看字节码:
INVOKEINTERFACE-调用接口方法,通过前面的错误提示可以看到,在这里多态调用了String的compareTo方法,所以只能传入一个String类型的参数。这里我们传入了一个int,在类加载的验证阶段,就会找出这个错误。

  • 2.super-要求泛型必须是本身或者其父类型(下边界)
    举例说明:(Cat继承了Animal类)
public static void main(String[] args) {
//在ArrayList中指定了T的类型,第二个参数传入的是T类型的父类型
        sort(new ArrayList<Cat>(),new AnimalComparator());
    }

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

class AnimalComparator implements Comparator<Animal> {
    @Override
    public int compare(Animal o1, Animal o2) {
        return 0;
    }
}