泛型
泛型中通配符
通常情况下,T,E,K,V,?约定
? 表示不确定的 java 类型
T (type) 表示具体的一个java类型
K V (key value) 分别代表java键值中的Key Value
E (element) 代表Element
Java泛型都是在编译器这个层次上实现,在生成的字节码中是不包含泛型中的类型信息,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除。
在编译后都会变成List,JVM看到的只是List,由泛型附加的类型信息对JVM是看不到。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法在运行时刻出现的类型转换异常的情况
1、原始类型相等
public class Test { public static void main(String[] args) { ArrayList<String> list1 = new ArrayList<String>(); list1.add("abc"); ArrayList<Integer> list2 = new ArrayList<Integer>(); list2.add(123); System.out.println(list1.getClass() == list2.getClass()); } }
定义了两个ArrayList数组,不过一个是ArrayList<String>泛型类型的,只能存储字符串;一个是ArrayList<Integer>泛型类型的,只能存储整数,最后,我们通过list1对象和list2对象的getClass()方法获取他们的类的信息,最后发现结果为true。说明泛型类型String和Integer都被擦除掉了,只剩下原始类型。
2、通过反射添加其它类型元素
public class Test { public static void main(String[] args) throws Exception { ArrayList<Integer> list = new ArrayList<Integer>(); list.add(1); //这样调用 add 方法只能存储整形,因为泛型类型的实例为 Integer list.getClass().getMethod("add", Object.class).invoke(list, "asd"); for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } } }
程序中定义一个ArrayList泛型类型实例化为Integer对象,如果直接调用add()方法,那么只能存储整数数据,不过当我们利用反射调用add()方法的时候,却可以存储字符串,这说明了Integer泛型实例在编译之后被擦除掉了,只保留了原始类型。
类型擦除后保留的原始类型
原始类型 就是擦除去泛型信息,在字节码中的类型变量的真正类型,无论何时定义一个泛型,相应的原始类型都会被自动提供,类型变量擦除,并使用其限定类型(无限定的变量用Object)替换。
public class Pair<T> { private T value; public T getValue() { return value; } public void setValue(T value) { this.value = value; } }
Pair的原始类型为:
public class Pair { private Object value; public Object getValue() { return value; } public void setValue(Object value) { this.value = value; } }
因为在Pair<T>中,T 是一个无限定的类型变量,所以用Object替换,其结果就是一个普通的类;Pair这样声明
public class Pair<T extends Comparable> {}
原始类型就是Comparable
区分原始类型和泛型变量的类型,调用泛型方法时,可以指定泛型,可以不指定泛型。
- 不指定泛型,泛型变量的类型为该方法中的几种类型的同一父类的最小级,直到 Object。
- 指定泛型,该方法的几种类型必须是该泛型的实例的类型或者其子类
public class Test { public static void main(String[] args) { /**不指定泛型的时候*/ int i = Test.add(1, 2); //这两个参数都是Integer,所以T为Integer类型 Number f = Test.add(1, 1.2); //这两个参数一个是Integer,以风格是Float,所以取同一父类的最小级,为Number Object o = Test.add(1, "asd"); //这两个参数一个是Integer,以风格是Float,所以取同一父类的最小级,为Object /**指定泛型的时候*/ int a = Test.<Integer>add(1, 2); //指定了Integer,所以只能为Integer类型或者其子类 int b = Test.<Integer>add(1, 2.2); //编译错误,指定了Integer,不能为Float Number c = Test.<Number>add(1, 2.2); //指定为Number,所以可以为Integer和Float } //这是一个简单的泛型方法 public static <T> T add(T x,T y){ return y; } }
类型擦除引起的问题及解决方法
1、先检查再编译以及编译的对象和引用传递问题
Q: 类型变量会在编译的时候擦除掉,那为什么我们往 ArrayList 创建的对象中添加整数会报错呢?
A: Java编译器是通过先检查代码中泛型的类型,然后在进行类型擦除,再进行编译。
public static void main(String[] args) { ArrayList<String> list = new ArrayList<String>(); list.add("123"); list.add(123);//编译错误 }
2、自动类型转换
类型擦除的问题,所有的泛型类型变量最后都会被替换为原始类型
public E get(int index) { RangeCheck(index); return (E) elementData[index]; }
在return之前,会根据泛型变量进行强转
3、泛型类型变量不能是基本数据类型
4、泛型在静态方法和静态类中的问题
泛型类中的静态方法和静态变量不可以使用泛型类所声明的泛型类型参数
public class Test2<T> { public static T one; //编译错误 public static T show(T one){ //编译错误 return null; } }
因为泛型类中的泛型参数的实例化是在定义对象的时候指定的,而静态变量和静态方法不需要使用对象来调用。
public class Test2<T> { public static <T >T show(T one){ //这是正确的 return null; } }
浙公网安备 33010602011771号