深入理解Java泛型
#头条创作挑战赛#什么是Java泛型
大家好,我是呼噜噜,Java 泛型(generics)是 Jdk 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制, 该机制允许程序员在编译时检测到非法的类型。
比如 ArrayList list= new ArrayList()这行代码就指明了该 ArrayList 对象只能 存储String类型,如果传入其他类型的对象就会报错。 让我们时光回退到Jdk5的版本,那时ArrayList内部其实就是一个Object[] 数组,配合存储一个当前分配的长度,就可以充当"可变数组": public class ArrayList { private Object[] array; private int size; public void add(Object e) {...} public void remove(int index) {...} public Object get(int index) {...} }
我们来举个简单的例子, ArrayList list = new ArrayList(); list.add("test"); list.add(666);
我们本意是用ArrayList来装String类型的值,但是突然混进去了Integer类型的值,由于ArrayList底层是Object数组,可以存储任意的对象,所以这个时候是没啥问题的,但我们不能只存不用啊,我们需要把值给拿出来使用,这个时候问题来了: for(Object item: list) { System.out.println((String)item); }
结果:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
由于我们需要String类型的值,我们需要把ArrayList的Object值强制转型,但是之前混进去了Integer ,虽然编译阶段通过了,但程序的运行结果会以崩溃结束,报 ClassCastException异常
为了解决这个问题,在Jdk 5版本中就引入了泛型的概念,而引入泛型的很大一部分原因就是为了解决我们上述的问题,允许程序员在编译时检测到非法的类型。不是同类型的就不允许在一块存放,这样也避免了ClassCastException异常的出现,而且因为都是同一类型,也就没必要做强制类型转换了。 我们可以把ArrayList 变量参数化: public class ArrayList { private T[] array;//我们 假设 ArrayList内部会有个T[] array private int size; public void add(T e) {...} public void remove(int index) {...} public T get(int index) {...} }
其中 T叫类型参数 ,T可以是任何class类型,现在ArrayList我们可以如下使用: // 存储String的ArrayList ArrayList list = new ArrayList(); list.add(666);//编译器会在编译阶段发现问题,从而提醒开发者
泛型其本质是参数化类型,也就是说数据类型 作为 参数,解决不确定具体对象类型的问题。 泛型的使用
泛型一般有三种使用方式,分别为:泛型类、泛型接口、泛型方法,我们简单介绍一下泛型的使用 泛型类//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型 //在实例化泛型类时,必须指定T的具体类型 public class Generic{ private T key; public Generic(T key) { this.key = key; } public T getKey(){ return key; } }
如何实例化泛型类: Generic genericInteger = new Generic(666); Generic genericStr = new Generic("hello");泛型接口//定义一个泛型接口 public interface Generator { public T method(); } //实现泛型接口,不指定类型 class GeneratorImpl implements Generator{ @Override public T method() { return null; } } //实现泛型接口,指定类型 class GeneratorImpl implements Generator{ @Override public String method() { return "hello"; } } 泛型方法public class GenericMethods { public void f(T x){ System.out.println(x.getClass().getName()); } public static void main(String[] args) { GenericMethods gm = new GenericMethods(); gm.f("啦啦啦"); gm.f(666); } }
结果: java.lang.String
java.lang.Integer 泛型的底层实现机制ArrayList源码解析
通过上文我们知道,为了让ArrayList存取各种数据类型的值,我们需要把 ArrayList模板化, 将变量的数据类型 给抽象出来,作为 类型参数 public class ArrayList { private T[] array;// 我们以为ArrayList内部会有个T[] array private int size; public void add(T e) {...} public void remove(int index) {...} public T get(int index) {...} }
但当我们查看Jdk8 的ArrayList源码,底层数组还是Object数组:transient Object[] elementData; 那ArrayList为什么还能进行类型约束和自动类型转换呢? 什么是泛型擦除
我们再看一个经典的例子: public class genericTest { public static void main(String [] args) { String str=""; Integer param =null; ArrayList l1 = new ArrayList(); l1.add("aaa"); str = l1.get(0); ArrayList l2 = new ArrayList(); l2.add(666); param = l2.get(0); System.out.println(l1.getClass() == l2.getClass()); } }
结果竟然是true,ArrayList.class 和 ArrayList.class 应该是不同的类型。通过getClass()方法获取他们的类的信息,竟然是一样的。我们来查看这个文件的class文件: public class genericTest { public genericTest() { } public static void main(String[] var0) { String var1 = ""; Integer var2 = null; ArrayList var3 = new ArrayList();//泛型被擦擦了 var3.add("aaa"); var1 = (String)var3.get(0); ArrayList var4 = new ArrayList();//泛型被擦擦了 var4.add(666); var2 = (Integer)var4.get(0); System.out.println(var3.getClass() == var4.getClass()); } }
我们在对其反汇编一下: $ javap -c genericTest : ļ genericTest com.zj.demotest.test5.genericTest Compiled from "genericTest.java" public class com.zj.demotest.test5.genericTest { public com.zj.demotest.test5.genericTest(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: return public static void main(java.lang.String[]); Code: 0: ldc #2 // String 2: astore_1 3: aconst_null 4: astore_2 5: new #3 // class java/util/ArrayList 8: dup 9: invokespecial #4 // Method java/util/ArrayList."":()V 12: astore_3 13: aload_3 14: ldc #5 // String aaa 16: invokevirtual #6 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z 19: pop 20: aload_3 21: iconst_0 22: invokevirtual #7 // Method java/util/ArrayList.get:(I)Ljava/lang/Object; 25: checkcast #8 // class java/lang/String 28: astore_1 29: new #3 // class java/util/ArrayList 32: dup 33: invokespecial #4 // Method java/util/ArrayList."":()V 36: astore 4 38: aload 4 40: sipush 666 43: invokestatic #9 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 46: invokevirtual #6 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z 49: pop 50: aload 4 52: iconst_0 53: invokevirtual #7 // Method java/util/ArrayList.get:(I)Ljava/lang/Object; 56: checkcast #10 // class java/lang/Integer 59: astore_2 60: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream; 63: aload_3 64: invokevirtual #12 // Method java/lang/Object.getClass:()Ljava/lang/Class; 67: aload 4 69: invokevirtual #12 // Method java/lang/Object.getClass:()Ljava/lang/Class; 72: if_acmpne 79 75: iconst_1 76: goto 80 79: iconst_0 80: invokevirtual #13 // Method java/io/PrintStream.println:(Z)V 83: return } 看第16、46处,add进去的是原始类型Object; 看第22、53处,get方法获得也是Object类型,String、Integer类型被擦出,只保留原始类型Object。 看25、55处,checkcast指令是类型转换检查 ,在结合class文件var1 = (String)var3.get(0);``var2 = (Integer)var4.get(0);我们知晓编译器自动帮我们强制类型转换了,我们无需手动类型转换
经过上面的种种现象,我们可以发现,在类加载的 编译阶段 ,泛型类型String和Integer都被擦除掉了,只剩下原始类型,这样他们类的信息都是Object,这样自然而然就相等了。这种机制就叫泛型擦除。
我们需要了解一下类加载生命周期:
详情见:https://mp.weixin.qq.com/s/v91bqRiKDWWgeNl1DIdaDQ
泛型是和编译器的约定, 在编译期对代码进行检查的,由编译器负责解析,JVM并无识别的能力,一个类继承泛型后,当变量存入这个类的时候,编译器会对其进行类型安全检测,当从中取出数据时,编译器会根据与泛型的约定,会自动进行类型转换,无需我们手动强制类型转换。
泛型类型参数化,并不意味这其对象类型是不确定的,相反它的对象类型 对于JVM来说,都是确定的,是Object或Object[]数组 泛型的边界
来看一个经典的例子,我们想要实现一个ArrayList对象能够储存所有的泛型: ArrayList