简介 泛型的优点 1、泛型的本质是为了参数化类型,也就是在在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型,很明显这种方法提高了代码的复用性。
2、泛型的引入提高了安全性,泛型提供了编译时类型安全检测机制,该机制允许开发者在编译时检测到非法的类型。。
3、在没有泛型的情况的下,通过对类型 Object 的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是本身就是一个安全隐患。
那么泛型的好处就是在编译的时候能够检查类型安全,并且所有的强制转换都是自动和隐式的。
public class GlmapperGeneric <T > { private T t; public void set (T t) { this .t = t; } public T get () { return t; } public static void main (String[] args) { } public void noSpecifyType () { GlmapperGeneric glmapperGeneric = new GlmapperGeneric(); glmapperGeneric.set("test" ); String test = (String) glmapperGeneric.get(); System.out.println(test); } public void specifyType () { GlmapperGeneric<String> glmapperGeneric = new GlmapperGeneric(); glmapperGeneric.set("test" ); String test = glmapperGeneric.get(); System.out.println(test); } }
为什么提高了安全性? 再举例子说明一下
不安全举例
package keyAndDifficultPoints.Generic;import java.util.ArrayList;import java.util.List;public class Test_Safe { public static void main (String[] args) { test(); } public static void test () { List arrayList = new ArrayList(); arrayList.add("aaaa" ); arrayList.add(100 ); for (int i = 0 ; i < arrayList.size(); i++) { String s = (String) arrayList.get(i); System.out.println(s); } } }
结果:
aaaa Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String at keyAndDifficultPoints.Generic.Test_Safe.test(Test_Safe.java:25) at keyAndDifficultPoints.Generic.Test_Safe.main(Test_Safe.java:16)
很明显的一个类型转换错误。ArrayList可以存放任意类型,例子中添加了一个String类型,添加了一个Integer类型,再使用时都以String的方式使用,因此程序崩溃了。为了解决类似这样的问题(在编译阶段就可以解决),泛型应运而生。
泛型提高安全性
将上面的代码稍微改一下
public static void test01 () { List<String> arrayList = new ArrayList<>(); arrayList.add("aaaa" ); arrayList.add(100 ); for (int i = 0 ; i < arrayList.size(); i++) { String s = (String) arrayList.get(i); System.out.println(s); } }
通过泛型来提前检测类型,编译时就通不过。
泛型为什么很重要 我们看一下比较常用的JUC包
public <U> CompletableFuture<U> thenComposeAsync ( Function<? super T, ? extends CompletionStage<U>> fn) { return uniComposeStage(asyncPool, fn); } public <U> CompletableFuture<U> thenComposeAsync ( Function<? super T, ? extends CompletionStage<U>> fn, Executor executor) { return uniComposeStage(screenExecutor(executor), fn); } public CompletableFuture<T> whenComplete ( BiConsumer<? super T, ? super Throwable> action) { return uniWhenCompleteStage(null , action); } public CompletableFuture<T> whenCompleteAsync ( BiConsumer<? super T, ? super Throwable> action) { return uniWhenCompleteStage(asyncPool, action); }
这些都大量的用到了泛型,如果不把泛型学好,想真正深入源码了解一些东西,可能就完全看不懂了。
泛型类 泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map。
最普通的泛型类:
package keyAndDifficultPoints.Generic;public class Test_GenericClass { public static void main (String[] args) { test(); } public static void test () { Generic<Integer> genericInteger1 = new Generic<Integer>(123 ); Generic<Integer> genericInteger = new Generic<>(123 ); Generic<String> genericString = new Generic<String>("my" ); System.out.println(genericInteger.getVar()); System.out.println(genericString.getVar()); } } class Generic <T > { private T var ; public Generic (T var ) { this .var = var ; } public T getVar () { return var ; } } class MyMap <K , V > { private K key; private V value; public K getKey () { return this .key; } public V getValue () { return this .value; } public void setKey (K key) { this .key = key; } public void setValue (V value) { this .value = value; } };
结果:
123 my Process finished with exit code 0
定义的泛型类,就一定要传入泛型类型实参么?并不是这样,在使用泛型的时候如果传入泛型实参,则会根据传入的泛型实参做相应的限制,此时泛型才会起到本应起到的限制作用。如果不传入泛型类型实参的话,在泛型类中使用泛型的方法或成员变量定义的类型可以为任何的类型。
还是以上面的泛型类为例进行测试
public static void test01 () { Generic generic = new Generic("我是字符串" ); Generic generic1 = new Generic(123 ); Generic generic2 = new Generic(123.123 ); Generic generic3 = new Generic(false ); System.out.println(generic.getVar()); System.out.println(generic1.getVar()); System.out.println(generic2.getVar()); System.out.println(generic3.getVar()); }
结果:
我是字符串 123 123.123 false Process finished with exit code 0
没有报错,正确输出了。
泛型接口 泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中,可以看一个例子:
interface Info <T > { public T getVar () ; }
当实现泛型接口的类,未传入泛型实参时:
class InfoImpl <T > implements Info <T > { private T var ; public InfoImpl (T var ) { this .setVar(var ); } public void setVar (T var ) { this .var = var ; } public T getVar () { return this .var ; } }
当实现泛型接口的类,传入泛型实参时:
class InfoImpl01 implements Info <String > { private String var ; public InfoImpl01 (String var ) { this .setVar(var ); } public void setVar (String var ) { this .var = var ; } public String getVar () { return this .var ; } }
泛型方法 在java中,泛型类和接口的定义非常简单,但是泛型方法就比较复杂了。
泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型。
最简单的一个泛型方法
public class Test_GenericMethod { public static void main (String[] args) { Test_GenericMethod test_genericMethod = new Test_GenericMethod(); Integer integer = test_genericMethod.genericMethod(12 ); System.out.println(integer); } public <T> T genericMethod (T a) { return a; } }
基本用法(非泛型类中的泛型方法) 下面来细说一下泛型方法
首先说一个误区
class Generic01 <T > { private T key; public Generic01 (T key) { this .key = key; } public T getKey () { return key; } }
基本用法 (非)
package keyAndDifficultPoints.Generic;public class Test_GenericMethod { public static void main (String[] args) { Test_GenericMethod test_genericMethod = new Test_GenericMethod(); Generic01<Integer> generic01 = new Generic01<>(123 ); Generic01<String> generic02 = new Generic01<>("AAAAA" ); test_genericMethod.genericMethod_test01(generic01); test_genericMethod.genericMethod_test02(generic02, "我是T" ); test_genericMethod.Method01(generic01); } public <T> T genericMethod (T a) { return a; } public <T> T genericMethod_test01 (Generic01<T> generic01) { System.out.println("我是genericMethod_test01:" + generic01.getKey()); T test = generic01.getKey(); return test; } public <T, V> T genericMethod_test02 (Generic01<T> generic01, V value) { System.out.println("我是genericMethod_test02:" + generic01.getKey() + "==> value:" + value); T test = generic01.getKey(); return test; } public void Method01 (Generic01<? extends Number> generic01) { System.out.println(generic01.getKey()); } public void Method02 (Generic01<?> generic01) { System.out.println(generic01.getKey()); } }
结果:
我是genericMethod_test01:123 我是genericMethod_test02:AAAAA==> value:我是T 123 Process finished with exit code 0
泛型类中的泛型方法 当然这并不是泛型方法的全部,泛型方法可以出现杂任何地方和任何场景中使用。但是有一种情况是非常特殊的,当泛型方法出现在泛型类中时,我们再通过一个例子看一下。
package keyAndDifficultPoints.Generic;public class Test_GenericMethod01 { public static void main (String[] args) { Apple apple = new Apple(); Person person = new Person(); GenerateTest<Fruit> generateTest = new GenerateTest<Fruit>(); generateTest.show_1(apple); generateTest.show_2(apple); generateTest.show_2(person); generateTest.show_3(apple); generateTest.show_3(person); } } abstract class GenericFruit {} class Fruit { @Override public String toString () { return "fruit" ; } } class Apple extends Fruit { @Override public String toString () { return "apple" ; } } class Person { @Override public String toString () { return "Person" ; } } class GenerateTest <T > { public void show_1 (T t) { System.out.println(t.toString()); } public <E> void show_3 (E t) { System.out.println(t.toString()); } public <T> void show_2 (T t) { System.out.println(t.toString()); } }
结果:
apple apple Person apple Person Process finished with exit code 0
泛型方法与可变参数 再看一个泛型方法和可变参数的例子:
public class Test_GenericMethod02 { public static void main (String[] args) { print("123" ,753 ,123.12 ); } public static <T> void print (T... args) { for (T t : args) { System.out.println(t); } } }
结果:
123 753 123.12 Process finished with exit code 0
静态方法与泛型 静态方法有一种情况需要注意一下,那就是在类中的静态方法使用泛型:静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。
即:如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法 。
public class StaticGenerator <T > { public static <E> List<E> copyFromArrayToList (E[] arr) { ArrayList<E> list = new ArrayList<>(); for (E e : arr){ list.add(e); } return list; } }
细枝末节
可能合上面的有一些重复
1、泛型异常类
//异常类不能声明为泛型类,编译报错 class MyException<T> extends Exception{ }
2、
package keyAndDifficultPoints.Generic.Minutiae;import java.util.ArrayList;import java.util.List;public class Test_Minutiae1 {} class Order <T > { String orderName; int orderId; T orderT; public Order () { T[] arr = (T[]) new Object[10 ]; } public Order (String orderName,int orderId,T orderT) { this .orderName = orderName; this .orderId = orderId; this .orderT = orderT; } public T getOrderT () { return orderT; } public void setOrderT (T orderT) { this .orderT = orderT; } @Override public String toString () { return "Order{" + "orderName='" + orderName + '\'' + ", orderId=" + orderId + ", orderT=" + orderT + '}' ; } public void show () { } public static <E> List<E> copyFromArrayToList (E[] arr) { ArrayList<E> list = new ArrayList<>(); for (E e : arr){ list.add(e); } return list; } } class SubOrder extends Order <Integer > { public static <E> List<E> copyFromArrayToList (E[] arr) { ArrayList<E> list = new ArrayList<>(); for (E e : arr) { list.add(e); } return list; } } class SubOrder1 <T > extends Order <T > {}
泛型数组 package keyAndDifficultPoints.Generic;import java.util.ArrayList;import java.util.List;public class Test_GenericArray { public static void main (String[] args) { test02(); } public static void test () { } public static void test01 () { List<?>[] ls = new ArrayList<?>[10 ]; ls[1 ] = new ArrayList<String>(); } public static void test02 () { List<?>[] lsa = new List<?>[10 ]; Object o = lsa; Object[] oa = (Object[]) o; List<Integer> li = new ArrayList<Integer>(); li.add(new Integer(3 )); oa[1 ] = li; Integer i = (Integer) lsa[1 ].get(0 ); System.out.println(i); } public static void test03 () { List<String>[] ls = new ArrayList[10 ]; ls[0 ] = new ArrayList<String>(); ls[1 ] = new ArrayList<String>(); ls[0 ].add("x" ); } }
sun文档
泛型在继承方面的细节 直接看代码注释
@Test public void test1 () { Object obj = null ; String str = null ; obj = str; Object[] arr1 = null ; String[] arr2 = null ; arr1 = arr2; List<Object> list1 = null ; List<String> list2 = new ArrayList<String>(); } @Test public void test2 () { AbstractList<String> list1 = null ; List<String> list2 = null ; ArrayList<String> list3 = null ; list1 = list3; list2 = list3; List<String> list4 = new ArrayList<>(); }
泛型通配符 我们在定义泛型类,泛型方法,泛型接口的时候经常会碰见很多不同的通配符,比如 T,E,K,V,? 等等,下面来详细讲一下这些通配符。
常用的通配符 本质上都是通配符没啥区别,只不过是编码时的一种约定俗成的东西(可以说提高了代码可读性)。比如上述代码中的 T ,我们可以换成 A-Z 之间的任何一个大小写字母都可以,并不会影响程序的正常运行,但是如果换成其他的字母代替 T ,在可读性上可能会弱一些。通常情况下,T,E,K,V,? 是这样约定的:
? 表示不确定的 java 类型
T (Type) 表示具体的一个java类型
K V (Key Value) 分别代表java键值中的Key Value
E (element) 代表Element
比较难的就是?
通配符,下面就着重讲一下
‘ ? ‘无界通配符 基本用法
但是如果用通配符的话:
List<? extends Animal> listAnimals
为什么要使用通配符而不是简单的泛型呢?通配符其实在声明局部变量时是没有什么意义的,但是当你为一个方法声明一个参数时,它是非常重要的。
package keyAndDifficultPoints.Generic;import java.util.ArrayList;import java.util.List;public class Test_Wildcard_Character { public static void main (String[] args) { List<Dog> dogList = new ArrayList<>(); test(dogList); test1(dogList); } static void test (List<? extends Animal> animals) { System.out.println("test输出:" ); for (Animal animal : animals) { System.out.print(animal.toString() + "-" ); } } static void test1 (List<Animal> animals) { System.out.println("test1输出:" ); for (Animal animal : animals) { System.out.print(animal.toString() + "-" ); } } } class Animal { @Override public String toString () { return "Animal" ; } } class Dog extends Animal { @Override public String toString () { return "Dog" ; } }
test1()
在编译时就会飘红
所以,对于不确定或者不关心实际要操作的类型,可以使用无限制通配符(尖括号里一个问号,即 <?> ),表示可以持有任何类型。像 test()
方法中,限定了上界,但是不关心具体类型是什么,所以对于传入的 Animal 的所有子类都可以支持,并且不会报错,而test1()
就不行。
‘ ? ‘通配符的继承 @Test public void test3 () { List<Object> list1 = null ; List<String> list2 = null ; List<?> list = null ; list = list1; list = list2; List<String> list3 = new ArrayList<>(); list3.add("AA" ); list3.add("BB" ); list3.add("CC" ); list = list3; list.add(null ); Object o = list.get(0 ); System.out.println(o); }
extends和super上下界 上界通配符 < ? extends E>
上结:用 extends 关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。
在类型参数中使用 extends 表示这个泛型中的参数必须是 E 或者 E 的子类,这样有两个好处:
如果传入的类型不是 E 或者 E 的子类,编译不成功
泛型中可以使用 E 的方法,要不然还得强转成 E 才能使用
下界通配符 < ? super E>
下界: 用 super 进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至 Object
在类型参数中使用 super 表示这个泛型中的参数必须是 E 或者 E 的父类。
举例 @Test public void test4 () { List<? extends Person> list1 = null ; List<? super Person> list2 = null ; List<Student> list3 = new ArrayList<Student>(); List<Person> list4 = new ArrayList<Person>(); List<Object> list5 = new ArrayList<Object>(); list1 = list3; list1 = list4; list2 = list4; list2 = list5; list1 = list3; Person p = list1.get(0 ); list2 = list4; Object obj = list2.get(0 ); list2.add(new Person()); list2.add(new Student()); } } class Person {} class Student extends Person {}
##? 和 T 的区别
?和 T 都表示不确定的类型,区别在于我们可以对 T 进行操作,但是对 ? 不行,比如如下这种 :
T t = operate(); ? car = operate();
简单总结下:
T 是一个 确定的 类型,通常用于泛型类和泛型方法的定义,?是一个 不确定 的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。
区别1:通过T来确保泛型参数的一致性 package keyAndDifficultPoints.Wildcard_Character;import java.util.ArrayList;import java.util.List;public class Test_difference { public static void main (String[] args) { List<Integer> integerList = new ArrayList<>(); List<Float> floatList = new ArrayList<>(); test1(integerList, floatList); test(integerList, integerList); test1(integerList, integerList); } public static <T extends Number> void test (List<T> dest, List<T> src) { } public static void test1 (List<? extends Number> dest, List<? extends Number> src) { } }
区别2:T可以通过&进行多重限定 public class Test_difference { public static void main (String[] args) { ArrayList list = new ArrayList<>(); ArrayDeque deque = new ArrayDeque<>(); LinkedList<Object> linkedList = new LinkedList<>(); test2(list); test2(linkedList); test3(linkedList); } public static <T extends List & Collection> void test2 (T t) { } public static <T extends Queue & List> void test3 (T t) { } }
区别3:?通配符可以使用超类限定而T不行 类型参数 T 只具有 一种 类型限定方式:
T extends A
但是通配符 ? 可以进行 两种限定:
? extends A
? super A
关于反射和泛型的一点东西 package keyAndDifficultPoints.Wildcard_Character;public class Test_Reflect { public static void main (String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException { A a = createInstance(A.class); B b = createInstance(B.class); } public static <T> T createInstance (Class<T> clazz) throws IllegalAccessException, InstantiationException { return clazz.newInstance(); } public static void getA (String path) throws ClassNotFoundException, IllegalAccessException, InstantiationException { A a = (A) Class.forName("keyAndDifficultPoints.Wildcard_Character.A" ).newInstance(); System.out.println(a.toString()); } } class A { String name; @Override public String toString () { return "我是对象A" ; } } class B { String name; @Override public String toString () { return "我是对象B" ; } } class C { public Class<?> clazz1; } class D <T > { public Class<?> clazz; public Class<T> clazzT; }
泛型原理(泛型擦除) 类型擦除简介 Java的泛型是伪泛型,为什么说Java的泛型是伪泛型呢?因为在编译期间,所有的泛型信息都会被擦除掉,我们常称为泛型擦除 。
Java中的泛型基本上都是在编译器这个层次来实现的,在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,编译器在编译的时候去掉,这个过程就称为类型擦除。
如在代码中定义的List<object>
和List<String>
等类型,在编译后都会编程List,JVM看到的只是List。而由泛型附加的类型信息对JVM来说是不可见的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。类型擦除也是Java的泛型实现方法与C++模版机制实现方式之间的重要区别。
可以通过两个例子,来证明java泛型的类型擦除。
例1:
@Test public void test () { List<String> stringList = new ArrayList<String>(); stringList.add("my" ); List<Integer> integerList = new ArrayList<Integer>(); integerList.add(123 ); System.out.println(stringList.getClass() == integerList.getClass()); }
结果:
true Process finished with exit code 0
在这个例子中,我们定义了两个List,不过一个是List泛型类型,只能存储字符串。一个是List泛型类型,只能存储整形。最后,我们通过stringList对象和integerList对象的getClass方法获取它们的类的信息,最后发现结果为true。说明泛型类型String和Integer都被擦除掉了,只剩下了原始类型。
例2:
@Test public void test01 () throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { List<Integer> list = new ArrayList<Integer>(); list.add(1 ); list.getClass().getMethod("add" , Object.class).invoke(list, "my" ); for (int i = 0 ; i < list.size(); i++) { System.out.println(list.get(i)); } }
结果:
1 my Process finished with exit code 0
在程序中定义了一个List泛型类型,如果直接调用add方法,那么只能存储整形的数据。不过当我们利用反射调用add方法的时候,却可以存储字符串。这说明了Integer泛型实例在编译之后被擦除了,只保留了 原始类型。
类型擦除后保留的原始类型 1、在上面,几次提到了原始类型 。什么是原始类型?原始类型就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型。无论何时定义一个泛型类型,相应的原始类型都会被自动地提供。类型变量被擦除,并使用其限定类型(无限定的变量用Object替换)。
例3:
package keyAndDifficultPoints.principle;public class Test_principle02 { public static void main (String[] args) { } } class Test_Generic <T > { private T value; public T getValue () { return value; } public void setValue (T value) { this .value = value; } }
下面我们用IDEA的工具,查看这个类的字节码信息。我把完整的字节码复制在下方:
class keyAndDifficultPoints /principle /Test_Generic { private Ljava/lang/Object; value <init>()V L0 LINENUMBER 13 L0 ALOAD 0 INVOKESPECIAL java/lang/Object.<init> ()V RETURN L1 LOCALVARIABLE this LkeyAndDifficultPoints/principle/Test_Generic; L0 L1 0 MAXSTACK = 1 MAXLOCALS = 1 public getValue () Ljava/lang/Object ; L0 LINENUMBER 17 L0 ALOAD 0 GETFIELD keyAndDifficultPoints/principle/Test_Generic.value : Ljava/lang/Object; ARETURN L1 LOCALVARIABLE this LkeyAndDifficultPoints/principle/Test_Generic; L0 L1 0 MAXSTACK = 1 MAXLOCALS = 1 public setValue (Ljava/lang/Object;) V L0 LINENUMBER 21 L0 ALOAD 0 ALOAD 1 PUTFIELD keyAndDifficultPoints/principle/Test_Generic.value : Ljava/lang/Object ; L1 LINENUMBER 22 L1 RETURN L2 LOCALVARIABLE this LkeyAndDifficultPoints/principle/Test_Generic; L0 L2 0 LOCALVARIABLE value Ljava/lang/Object; L0 L2 1 MAXSTACK = 2 MAXLOCALS = 2 }
可以明显的看到泛型T 被替换成了Object 。
因为在Test_Generic中,T是一个无限定的类型变量,所以用Object替换。其结果就是一个普通的类,如同泛型加入java变成语言之前已经实现的那样。在程序中可以包含不同类型的Test_Generic,如Test_Generic或Test_Generic,但是,擦除类型后它们就成为原始的Test_Generic类型了,原始类型都是Object。
从上面的那个例2中,我们也可以明白List被擦除类型后,原始类型也变成了Object,所以通过反射我们就可以存储字符串了。
2、如果类型变量有限定,那么原始类型就用第一个边界的类型变量来替换。
比如Test_Generic这样声明
class Test_Generic1 <T extends List & Collection >
我们还是看字节码(后面如无必须,只截取部分字节码)
class keyAndDifficultPoints /principle /Test_Generic1 { private Ljava/util/List; value
会发现T变成了List
如果顺序变一下
class Test_Generic1 <T extends Collection & List >
字节码就变了
T变成了Collection
class keyAndDifficultPoints /principle /Test_Generic1 { private Ljava/util/Collection; value
也就是说在进行字节码编译的时候是使用离T最近的一个类型。
解答一个疑惑 在上文说到&的多重限定时
package keyAndDifficultPoints.principle;import java.util.*;public class Test_principle04 { public static void main (String[] args) { List list = new ArrayList<>(); Queue queue = new ArrayDeque<>(); LinkedList<Object> linkedList = new LinkedList<>(); test2(list); test4(list); test2(linkedList); test3(linkedList); test4(linkedList); } public static <T extends List & Collection> void test2 (T t) { } public static <T extends Queue & List> void test3 (T t) { } public static <T extends Collection & List> void test4 (T t) { } }
首先来看一下字节码
public class keyAndDifficultPoints /principle /Test_principle04 { public <init>()V L0 LINENUMBER 11 L0 ALOAD 0 INVOKESPECIAL java/lang/Object.<init> ()V RETURN L1 LOCALVARIABLE this LkeyAndDifficultPoints/principle/Test_principle04; L0 L1 0 MAXSTACK = 1 MAXLOCALS = 1 public static main ([Ljava/lang/String;) V L0 LINENUMBER 17 L0 NEW java/util/ArrayList DUP INVOKESPECIAL java/util/ArrayList.<init> () V ASTORE 1 L1 LINENUMBER 18 L1 NEW java/util/ArrayDeque DUP INVOKESPECIAL java/util/ArrayDeque.<init> () V ASTORE 2 L2 LINENUMBER 19 L2 NEW java/util/LinkedList DUP INVOKESPECIAL java/util/LinkedList.<init> () V ASTORE 3 L3 LINENUMBER 22 L3 ALOAD 1 INVOKESTATIC keyAndDifficultPoints/principle/Test_principle04.test2 (Ljava/util/List;) V L4 LINENUMBER 24 L4 ALOAD 1 INVOKESTATIC keyAndDifficultPoints/principle/Test_principle04.test4 (Ljava/util/Collection;) V L5 LINENUMBER 33 L5 ALOAD 3 INVOKESTATIC keyAndDifficultPoints/principle/Test_principle04.test2 (Ljava/util/List;) V L6 LINENUMBER 34 L6 ALOAD 3 INVOKESTATIC keyAndDifficultPoints/principle/Test_principle04.test3 (Ljava/util/Queue;) V L7 LINENUMBER 35 L7 ALOAD 3 INVOKESTATIC keyAndDifficultPoints/principle/Test_principle04.test4 (Ljava/util/Collection;) V L8 LINENUMBER 38 L8 RETURN L9 LOCALVARIABLE args [Ljava/lang/String ; L0 L9 0 LOCALVARIABLE list Ljava/util/List; L1 L9 1 LOCALVARIABLE queue Ljava/util/Queue; L2 L9 2 LOCALVARIABLE linkedList Ljava/util/LinkedList; L3 L9 3 MAXSTACK = 2 MAXLOCALS = 4 public static test2 (Ljava/util/List;) V L0 LINENUMBER 44 L0 RETURN L1 LOCALVARIABLE t Ljava/util/List ; L0 L1 0 MAXSTACK = 0 MAXLOCALS = 1 public static test3 (Ljava/util/Queue;) V L0 LINENUMBER 49 L0 RETURN L1 LOCALVARIABLE t Ljava/util/Queue ; L0 L1 0 MAXSTACK = 0 MAXLOCALS = 1 public static test4 (Ljava/util/Collection;) V L0 LINENUMBER 54 L0 RETURN L1 LOCALVARIABLE t Ljava/util/Collection ; L0 L1 0 MAXSTACK = 0 MAXLOCALS = 1 }
test4()
方法里离T最近的是Collection
,那么T在编译后就被Collection
代替了。那按理来说
1、这里我们传一个Collection的实现类Queue,也应该是可以的啊,但是为什么报错了呢?注意一点报错报的是编译错误,泛型提供编译前检测机制,也就是说在没运行前,泛型规定了多重限定时,在编译的时候取最小范围或共同子类
。
2、那实际上到底可以不可以传Queue呢?根据之前的讲解,我相信大家已经有了结论。实际上是可以的,只不过要跳过编译检测机制,通过反射来放Queue。
泛型方法调用 在调用泛型方法的时候,可以指定泛型,也可以不指定泛型。在不指定泛型的情况下,泛型变量的类型为 该方法中的几种类型的同一个父类的最小级,直到Object。在指定泛型的时候,该方法中的几种类型必须是该泛型实例类型或者其子类。
class Test { public static void main (String[] args) { int a1 = add(1 , 2 ); Number b1 = add(1 , 1.2 ); Object c1 = add(1 , "my" ); int a = Test.<Integer>add(1 , 2 ); Number c = Test.<Number>add(1 , 2.2 ); } public static <T> T add (T x, T y) { return x; } }
类型擦除引起的问题及解决方法 类型检测针对谁? public static void main (String[] args) { ArrayList<String> arrayList=new ArrayList<String>(); arrayList.add("123" ); arrayList.add(123 ); }
类型擦除后,原始类型为Object,是应该运行任意引用类型的添加的。可实际上却不是这样,这恰恰说明了关于泛型变量的使用,是会在编译之前检查的。
那么,这么类型检查是针对谁的呢?我们来看例子:
public static void main (String[] args) { ArrayList<String> arrayList = new ArrayList<String>(); arrayList.add(1 ); ArrayList<String> arrayList1 = new ArrayList(); arrayList1.add(1 ); ArrayList arrayList2 = new ArrayList<String>(); arrayList2.add(1 ); }
通过上面的例子,我们可以明白,类型检查就是针对引用的,谁是一个引用,用这个引用调用泛型方法,就会对这个引用调用的方法进行类型检测,而无关它真正引用的对象。
自动类型转换 因为类型擦除的问题,所以所有的泛型类型变量最后都会被替换为原始类型。这样就引起了一个问题,既然都被替换为原始类型,那么为什么我们在获取的时候,不需要进行强制类型转换呢?
我么来看一下List的get()方法:
public E get (int index) { rangeCheck(index); return elementData(index); } E elementData (int index) { return (E) elementData[index]; }
可以看到基本各个类都已经自动帮你转了。
类型擦除与多态的冲突和解决方法 这个其实是类型擦除引起的最大的问题了。
public class Test_principle05 { public static void main (String[] args) { } } class Generic <T > { private T var ; public T getVar () { return var ; } public void setVar (T var ) { this .var = var ; } } class MyGeneric extends Generic <Integer > { @Override public Integer getVar () { return super .getVar(); } @Override public void setVar (Integer var ) { super .setVar(var ); } }
实际上,从他们的@Override标签中也可以看到,在子类中重写这两个方法一点问题也没有,实际上是这样的吗?
分析:
泛型擦除后,父类是下面这样子
class Generic { private Object var ; public Object getVar () { return var ; } public void setVar (Object var ) { this .var = var ; } }
子类还是这样
class MyGeneric extends Generic <Integer > { @Override public Integer getVar () { return super .getVar(); } @Override public void setVar (Integer var ) { super .setVar(var ); } }
先来分析setValue方法,父类的类型是Object,而子类的类型是Date,参数类型不一样,这如果实在普通的继承关系中,根本就不会是重写,而是重载。
重载(Overload):首先是位于一个类之中或者其子类中,具有相同的方法名,但是方法的参数不同,返回值类型可以相同也可以不同。
(1):方法名必须相同。
(2):方法的参数列表一定不一样。
(3):访问修饰符和返回值类型可以相同也可以不同。
重写(override):一般都是表示子类和父类之间的关系,其主要的特征是:方法名相同,参数相同,但是具体的实现不同。
重写的特征:
(1):方法名必须相同,返回值类型必须相同
(2):参数列表必须相同
(3):访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected。
(4):子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为private和final的方法。
(5):构造方法不能被重写
我们来测试下到底是重载还是重写
public static void main (String[] args) { MyGeneric myGeneric = new MyGeneric(); myGeneric.setVar(new Integer(1 )); myGeneric.setVar(new Object()); }
如果是重载的话,第四行代码是不会报错的,因为调的是不同的重载方法。但是发现编译报错了,也就是说没有参数是Object的这样的重载函数。所以说是重写了,导致MyGeneric对象只能调用自己重写的方法。
为什么会这样呢?
原因是这样的,我们传入父类的泛型类型是Integer,Generic,我们的本意是将泛型类变为如下:
class Generic { private Integer var ; public Integer getVar () { return var ; } public void setVar (Integer var ) { this .var = var ; } }
然后再子类中重写参数类型为Date的那两个方法,实现继承中的多态。
可是由于种种原因,虚拟机并不能将泛型类型变为Integer,只能将类型擦除掉,变为原始类型Object。这样,我们的本意是进行重写,实现多态。可是类型擦除后,只能变为了重载。这样,类型擦除就和多态有了冲突。JVM知道你的本意吗?知道,可是它能直接实现吗,不能。如果真的不能的话,那我们怎么去重写我们想要的Integer类型参数的方法啊。
JVM采用了一个特殊的方法,来完成这项功能,那就是桥方法 。
我们对下面这个类进行编译,看其字节码
public class MyGeneric extends Generic <Integer > { public static void main (String[] args) { } @Override public Integer getVar () { return super .getVar(); } @Override public void setVar (Integer var ) { super .setVar(var ); } }
字节码:
public class keyAndDifficultPoints /principle /MyGeneric extends keyAndDifficultPoints /principle /Generic { public <init>()V L0 LINENUMBER 9 L0 ALOAD 0 INVOKESPECIAL keyAndDifficultPoints/principle/Generic.<init> ()V RETURN L1 LOCALVARIABLE this LkeyAndDifficultPoints/principle/MyGeneric; L0 L1 0 MAXSTACK = 1 MAXLOCALS = 1 public static main ([Ljava/lang/String;) V L0 LINENUMBER 12 L0 RETURN L1 LOCALVARIABLE args [Ljava/lang/String ; L0 L1 0 MAXSTACK = 0 MAXLOCALS = 1 public getVar () Ljava/lang/Integer ; L0 LINENUMBER 16 L0 ALOAD 0 INVOKESPECIAL keyAndDifficultPoints/principle/Generic.getVar ()Ljava/lang/Object; CHECKCAST java/lang/Integer ARETURN L1 LOCALVARIABLE this LkeyAndDifficultPoints/principle/MyGeneric; L0 L1 0 MAXSTACK = 1 MAXLOCALS = 1 public setVar (Ljava/lang/Integer;) V L0 LINENUMBER 20 L0 ALOAD 0 ALOAD 1 INVOKESPECIAL keyAndDifficultPoints/principle/Generic.setVar (Ljava/lang/Object;) V L1 LINENUMBER 21 L1 RETURN L2 LOCALVARIABLE this LkeyAndDifficultPoints/principle/MyGeneric ; L0 L2 0 LOCALVARIABLE var Ljava/lang/Integer; L0 L2 1 MAXSTACK = 2 MAXLOCALS = 2 public synthetic bridge setVar (Ljava/lang/Object;) V L0 LINENUMBER 9 L0 ALOAD 0 ALOAD 1 CHECKCAST java/lang/Integer INVOKEVIRTUAL keyAndDifficultPoints/principle/MyGeneric.setVar (Ljava/lang/Integer;) V RETURN L1 LOCALVARIABLE this LkeyAndDifficultPoints/principle/MyGeneric ; L0 L1 0 MAXSTACK = 2 MAXLOCALS = 2 public synthetic bridge getVar () Ljava/lang/Object ; L0 LINENUMBER 9 L0 ALOAD 0 INVOKEVIRTUAL keyAndDifficultPoints/principle/MyGeneric.getVar ()Ljava/lang/Integer; ARETURN L1 LOCALVARIABLE this LkeyAndDifficultPoints/principle/MyGeneric; L0 L1 0 MAXSTACK = 1 MAXLOCALS = 1 }
从编译的结果来看,我们本意重写setValue和getValue方法的子类,竟然有4个方法。最后的两个方法,就是编译器自己生成的桥方法。可以看到桥方法的参数类型都是Object,也就是说,子类中真正覆盖父类两个方法的就是这两个我们看不到的桥方法。而打在我们自己定义的setvalue和getValue方法上面的@Oveerride只不过是假象。而桥方法的内部实现,就只是去调用我们自己重写的那两个方法。
所以,虚拟机巧妙的使用了巧方法,来解决了类型擦除和多态的冲突。