Java 泛型(generics)是JDK1.5中引入的一个新特性,其本质是参数化类型,解决不确定具体对象类型的问题;其所操作的数据类型被指定为一个参数(type parameter)这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
但是在Java中并不是真正的泛型,实际上是“伪泛型”
类型擦除(type Erasure) 为了与之前的版本兼容,JDK1.5中通过类型擦除来增加的泛型功能。Java泛型只是在编译器层次上,在编译后生成的字节码中是不包含泛型中类型的信息的。 通过一个例子来证明类型擦除
1 2 3 4 5 6 7 8 public class main { public static void main (String[] args) { ArrayList<String> sList = new ArrayList<String>(); ArrayList<Integer> iList = new ArrayList<Integer>(); System.out.println(sList.getClass() == iList.getClass()); } }
上面定义了两个ArrayList,一个是ArrayList<String>泛型类型的,一个是ArrayList<Integer>类型的,但是最后打印的是true
,说明两个类型相同。 用javap -c
看一下生成的生成的字节码
可以看到在字节码中,ArrayList<String>和ArrayList<Integer>都被编译成了ArrayList类型,可见编译后发生了类型擦除。
既然编译后发生了类型擦除,那么虚拟机解析、反射等场景是怎么获取到正确的类型的?
在JDk1.5中增加泛型的同时,JCP组织修改了虚拟机规范,增加了Signature
、LocalVariableTypeTable
新属性。 用javap -v
查看一下字节码,在main
方法中包含一段 1 2 3 4 LocalVariableTypeTable: Start Length Slot Name Signature 8 31 1 sList Ljava/util/ArrayList<Ljava/lang/String;>; 16 23 2 iList Ljava/util/ArrayList<Ljava/lang/Integer;>;
LocalVariableTypeTable
是一个可选属性,如果存在泛型,则会出现这个属性。在Signature
下包含了泛型的信息。
接下来,看这段代码
1 2 3 ArrayList<String> sList = new ArrayList<String>(); sList.add("111" ); String s = sList.get(0 );
类型擦除之后,当调用sList.get(0)
是如何确保返回的值不会和String不匹配呢? 用javap -c
查看一下字节码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class com.example.demo.test.main { // .....省略 public static void main(java.lang.String[]) throws java.lang.NoSuchFieldException; Code: 0: new #2 // class java/util/ArrayList 3: dup 4: invokespecial #3 // Method java/util/ArrayList."<init>":()V 7: astore_1 8: aload_1 9: ldc #4 // String 111 11: invokevirtual #5 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z 14: pop 15: aload_1 16: iconst_0 17: invokevirtual #6 // Method java/util/ArrayList.get:(I)Ljava/lang/Object; 20: checkcast #7 // class java/lang/String 23: astore_2 24: return }
在#7
处有一个checkcast
指令,checkcast
用于检查类型强制转换是否可以进行,也就是泛型在获取值的时候进行了强制类型转换。
再来看看下面这段代码
首先定义一个Java泛型类
1 2 3 4 5 6 7 8 9 10 11 12 public class GenericClass <T > { private T value; public T getValue () { return value; } public void setValue (T value) { this .value = value; } }
再定义一个子类继承它
1 2 3 4 5 6 7 8 9 10 11 12 public class GenericClassTest extends GenericClass <Integer > { @Override public void setValue (Integer value) { super .setValue(value); } @Override public Integer getValue () { return super .getValue(); } }
在GenericClassTest
中将GenericClass
的泛型定义为Integer
类型,并重写了get和set方法,因为存在类型擦除,父类GenericClass
的泛型被擦除了。 用javap -c
查看一下GenericClass
编译后的字节码
可以看到类型擦除后泛型变为了Object
。那么GenericClass
也就变为了
1 2 3 4 5 6 7 8 9 10 11 12 public class GenericClass { private Object value; public Object getValue () { return value; } public void setValue (Object value) { this .value = value; } }
这样,父类GenericClass
中set和get方法操作的是Object对象,而子类GenericClassTest
操作的是Integer对象,为什么还可以重写?按照正常的继承关系中,这应该是重载。 按照重载的方式试一下
可以看到设置Object对象出现了红波浪线,不允许这样设置,看来确实是重写,而不是重载。为什么会时重写,这不是跟Java多态冲突么?继续往下研究。 现在用javap -c
看一下子类GenericClassTest
的字节码文件
在GenericClassTest
中get和/set方法都有两个,一个是操作Object对象一个是操作Integer对象。 操作Integer对象的是GenericClassTest
定义的,操作Object对象的是由编译器生成的。 再用javap -v
查看一下字节码更详细的信息。
编译器生成的两个操作Object对象的方法中多了两个ACC_BRIDGE
、ACC_SYNTHETIC
标志。 这就是虚拟机解决类型擦除和多态冲突问题的方法:使用桥接方法
。桥接方法
方法是由编译器生成的,我们在代码中并不能直接使用,但是可以通过反射拿到桥接方法再使用。
泛型一旦编译过后,类型就被擦除了,那到了运行时,怎么获取泛型信息?这就要使用JDK提供的Type类型接口了。
Type类型 在没有泛型之前,所有的类型都通过Class类进行抽象,Class类的一个具体对象就代表了一个类型。 在JDK1.5增加了泛型之后,扩充了数据类型,将泛型也包含了。 JDK在原来的基础上增加了一个Type
接口,它是所有类型的父接口,它的子类有
Class
类: 原始/基本类型,包括平时我们所有的类、枚举、数组、注解,还有int、float等基本类型
ParameterizedType
接口:参数化类型,比如List<String>
TypeVariable
接口:类型变量,比如List<T>中的T就是参数化变量
GenericArrayType
接口: 数组类型,比如List<String>[]、T[]
WildcardType
接口:泛型表达式类型,比如List< ? extends Number>
ParameterizedType 参数化类型,即带有参数的类型,也就是带有<>的类型
1 2 3 4 5 6 7 public interface ParameterizedType extends Type { Type[] getActualTypeArguments(); Type getRawType () ; Type getOwnerType () ; }
getActualTypeArguments()
: 获取类型内部的参数化类型 比如Map<K,V>里面的K,V类型。
getRawType()
: 类的原始类型,比如Map<K,V>中的Map类型。
getOwnerType()
: 获取所有者类型(只有内部类才有所有者,比如Map.Entry他的所有者就是Map),若不是内部类,此处返回null。
实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class GenericClass <T > { private List<String> list; private List<T> tList; public static void main (String[] args) { Class<GenericClass> genericClassClass = GenericClass.class; Field[] declaredFields = genericClassClass.getDeclaredFields(); for (Field declaredField : declaredFields) { Type genericType = declaredField.getGenericType(); if (genericType instanceof ParameterizedType) { System.out.println("==========" + genericType.getTypeName() + "======ParameterizedType类型=====" ); ParameterizedType parameterizedType = (ParameterizedType) genericType; System.out.println("getActualTypeArguments:" ); Type[] actualTypeArguments = (parameterizedType).getActualTypeArguments(); for (Type actualTypeArgument : actualTypeArguments) { System.out.println(" " + actualTypeArgument); } Type rawType = (parameterizedType).getRawType(); System.out.println("getRawType:" ); System.out.println(" " + rawType); } } } }
输出
1 2 3 4 5 6 7 8 9 10 11 ==========java.util.List<java.lang.String>======ParameterizedType类型===== getActualTypeArguments: java.lang.String getRawType: interface java.util.List ==========java.util.List<T>======ParameterizedType类型===== getActualTypeArguments: T getRawType: interface java.util.List
TypeVariable 类型变量,即泛型中的变量,例如:T、K、V等变量,可以表示任何类;
注意: 与ParameterizedType的区别,TypeVariable代表着泛型中的变量,而ParameterizedType则代表整个泛型。比如List<T>中,T是TypeVariable类型,List<T>是ParameterizedType类型
1 2 3 4 5 6 7 8 9 10 public interface TypeVariable <D extends GenericDeclaration > extends Type , AnnotatedElement { Type[] getBounds(); D getGenericDeclaration () ; String getName () ; AnnotatedType[] getAnnotatedBounds(); }
getBounds()
:类型对应的上限,默认为Object 可以有多个。比如List< T extends Number & Serializable>中的Number和Serializable
getGenericDeclaration()
: 获取声明该类型变量实体,比如GenericClass< T>中的GenericClass
getName()
:获取类型变量在源码中定义的名称;
实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class GenericClass <T extends Number > { private T t; public static void main (String[] args) { Class<GenericClass> genericClassClass = GenericClass.class; Field[] declaredFields = genericClassClass.getDeclaredFields(); for (Field declaredField : declaredFields) { Type genericType = declaredField.getGenericType(); if (genericType instanceof TypeVariable) { System.out.println("==========" + genericType.getTypeName() + "======TypeVariable类型=====" ); TypeVariable typeVariable = (TypeVariable) genericType; Type[] bounds = typeVariable.getBounds(); System.out.println("getBounds:" ); for (Type bound : bounds) { System.out.println(" " + bound); } System.out.println("getGenericDeclaration:" ); System.out.println(" " + typeVariable.getGenericDeclaration()); System.out.println("getName:" ); System.out.println(" " + typeVariable.getName()); } } } }
输出:
1 2 3 4 5 6 7 ==========T======TypeVariable类型===== getBounds: class java.lang.Number getGenericDeclaration: class com.example.demo.test.GenericClass getName: T
GenericArrayType 泛型数组类型,用来描述ParameterizedType、TypeVariable类型的数组;例如:List<T>[] 、T[]、List<String>[]等。
注意: GenericArrayType是来描述与泛型相关的数组,与String[]、int[]、float[]这种类型不同。
1 2 3 4 public interface GenericArrayType extends Type { Type getGenericComponentType () ; }
getGenericComponentType()
:返回泛型数组中元素的Type类型,比如List<String>[] 中的 List<String>
实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class GenericClass <T extends Number > { private List<String>[] lists; private T[] ts; public static void main (String[] args) { Class<GenericClass> genericClassClass = GenericClass.class; Field[] declaredFields = genericClassClass.getDeclaredFields(); for (Field declaredField : declaredFields) { Type genericType = declaredField.getGenericType(); if (genericType instanceof GenericArrayType) { GenericArrayType genericArrayType = (GenericArrayType) genericType; System.out.println("==========" + genericType.getTypeName() + "======GenericArrayType类型=====" ); Type genericComponentType = genericArrayType.getGenericComponentType(); System.out.println("getGenericComponentType:" ); System.out.println(" " + genericComponentType); } } } }
输出:
1 2 3 4 5 6 ==========java.util.List<java.lang.String>[]======GenericArrayType类型===== getGenericComponentType: java.util.List<java.lang.String> ==========T[]======GenericArrayType类型===== getGenericComponentType: T
WildcardType 泛型表达式(通配符表达式)。例如:? extend Number、? super Integer。
注意: WildcardType虽然是Type的子接口,但不代表一种类型,,表示的仅仅是类似 ? extends T、? super K这样的通配符表达式。
1 2 3 4 5 6 public interface WildcardType extends Type { Type[] getUpperBounds(); Type[] getLowerBounds(); }
getUpperBounds()
获得泛型表达式上界(上限) 获取泛型变量的上边界(extends)
getLowerBounds()
获得泛型表达式下界(下限) 获取泛型变量的下边界(super)
实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public class GenericClass <T extends Number > { private List<? extends Number> numbers; private List<? super Integer> integers; public static void main (String[] args) { Class<GenericClass> genericClassClass = GenericClass.class; Field[] declaredFields = genericClassClass.getDeclaredFields(); for (Field declaredField : declaredFields) { Type genericType = declaredField.getGenericType(); if (genericType instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) genericType; Type[] actualTypeArguments = (parameterizedType).getActualTypeArguments(); for (Type actualTypeArgument : actualTypeArguments) { if (actualTypeArgument instanceof WildcardType){ System.out.println("==========" + actualTypeArgument.getTypeName() + "======WildcardType类型=====" ); WildcardType wildcardType = (WildcardType) actualTypeArgument; System.out.println("getUpperBounds:" ); Type[] upperBounds = wildcardType.getUpperBounds(); for (Type upperBound : upperBounds) { System.out.println(" " + upperBound); } System.out.println("getLowerBounds:" ); Type[] lowerBounds = wildcardType.getLowerBounds(); for (Type lowerBound : lowerBounds) { System.out.println(" " + lowerBound); } } } } } } }
输出:
1 2 3 4 5 6 7 8 9 10 ==========? extends java.lang.Number======WildcardType类型===== getUpperBounds: class java.lang.Number getLowerBounds: ==========? super java.lang.Integer======WildcardType类型===== getUpperBounds: class java.lang.Object getLowerBounds: class java.lang.Integer