关于泛型的底层,我们先来看看一段代码:
public class Test { public static void main(String[] args) { List<String> strList = Lists.newArrayList(); List<Integer> intList = Lists.newArrayList(); System.out.println(strList.getClass() == intList.getClass()); } }
上面的代码new了2个list,然后判断这2个list的类是不是同一个?如果没有深入的了解过泛型,我们很容易以为上面的代码输出是false,但是运行上面的代码,实际输出是true。为什么呢?因为不管泛型的实际类型是什么,他们在运行时总是同样的类。比如下面的代码:
public class Test<T> { public static void main(String[] args) { Test<String> test = new Test<>(); List<String> strList = Lists.newArrayList(); List<Integer> intList = Lists.newArrayList(); System.out.println(strList.getClass() == intList.getClass()); } }我们在编译之后,用反编译打开jar包看一下,代码的源码如下:
public class Test<T> { public static void main(String[] args) { Test test = new Test(); List strList = Lists.newArrayList(); List intList = Lists.newArrayList(); System.out.println(strList.getClass() == intList.getClass()); } }
从上面的2段代码我们得出一个结论,不管为泛型的类型形参传入哪一种类型实参,对于java来说,他们都被当成了同一个类来处理,在内存中也使用同一块内存空间。所以这里有2个注意的地方;
1),在静态方法,静态初始化块,静态变量的声明和初始化中都不允许使用类型形参。具体看下面的代码演示:
public class Test<T> { //实例属性,实例方法是可以的,具体的T就是初始化这个类时候传入的形参。以下代码正常 T name; public void test(T id) { } //静态属性和静态方法不可以用,这个时候没有实例初始化,也就是说这个T很有可能还没有传入。以下代码出错 static T id; public static void test(T id) { } }
2),在系统中并不会生成泛型类,所以instanceof运算符后不能使用泛型类。具体看下面的代码演示:
public class Test<T> { public void test() { Collection<String> list = Lists.newArrayList(); //下面代码正常 if (list instanceof ArrayList) { } //下面代码报错,instanceof运算符后不能使用泛型 if (list instanceof ArrayList<String>) { } } }
总结:我们在使用泛型的时候,虽然程序只定义了一个List<E>的接口,但是在实际使用过程中我们可以传入随便的E的类型,这样子就可以产生无数多个List接口,只要为E传入不同的类型实参,系统就会多出一个新的List子接口。特别要注意的是,上面的任何一个子接口都不能被替换成一个固定的接口,比如List<String>不会替换成ListString,系统没有进行源代码复制,二级制代码中没有,磁盘和内存中都没有,也就是说这种子类我们只是可以这样子理解,但是在物理上根本不存在。