概念:
- 泛型的本质是参数化类型
泛型实质
- 只在编译阶段有效。在编译后,将泛型信息擦出,添加类型检查和类型转换
- 作为语法糖对于JVM是透明,有泛型的和没有泛型的代码,编译生成的二进制代码是相同的。
泛型的作用
- - 更加灵活通用
- - 安全性检查提前到编译期
- 省去类型强制转换
类型擦除
- 运行过程中将具体的类型都抹除。
- 使用泛型加上类型参数,编译会去掉,生成的字节码不包含类型信息
- Java不能实现真正的泛型,只能使用类型擦除来实现伪泛型
类型擦除后原始类型:
- 类型擦除后,使用限定类型,无限定用Object。
Q:类型变量在编译的时候擦除掉, 往 ArrayList 创建的对象中添加整数会报错
- 先检查泛型的类型,在进行类型擦除,再进行编译。
类型检查对象
- 类型检查针对引用的,对引用调用的泛型方法进行类型检测,而无关真正引用的对象。
- 没有引用 ,类型是对象类型
泛型参数不考虑继承关系
- ArrayList<String> list1 = new ArrayList<Object>(); //编译错误
3-2.自动类型转换
- 存取泛型域时会自动插入强制类型转换
3-3.类型擦除与多态的冲突和解决方法
- 类型擦除后,父类的的泛型类型全部变为了原始类型
Object
- 编译器生成桥方法,调用重写方法。
3-6.泛型在静态方法和静态类中的问题
- 泛型类中的T,静态方法不能使用
- 静态方法定义的T,方法内可以使用
instanceof
- 运行时指出对象是否是特定类的一个实例
类型擦除后使用:
- 泛型的类型参数只能是类类型,(类型擦除后,为
Object
,不能存储double
值) - 不能对泛型类型使用instanceof操作(类型擦除后只剩下原始类型,不能使用instanceof)
泛型数组
- java中是”不能创建一个确切的泛型类型的数组”
- 数组的类型不可以是类型变量,除非是采用通配符的方式
- 数组是协变的,
Integer[]
可以转换为Object[]
- 泛型擦除后,运行时能添加任何类型
java泛型中<?>和<T>有什么区别?
- T 代表一种类型
- ?是通配符,泛指所有类型,看成所有类型的父类,真实的类型,类型实参
T和?运用的地方有点不同
- ?是定义在引用变量上,指向多个对象。
- T是类上或方法上
List<T>是泛型方法,List<?>是限制通配符
List<T>一般有两种用途:
- 1、定义通用的泛型方法。
- 2、限制方法参数和返回结果的关系。
List<?>一般就是在泛型起一个限制作用。
- 类型参数“<T>”,声明泛型类或泛型方法。
- 通配符“<?>”,使用泛型类或泛型方法(因为定义在引用变量上,指向多个对象。)
运用
- 如果有泛型方法和非泛型方法,都满足条件,会执行非泛型方法
泛型三种:
- [1]ArrayList<T> al=new ArrayList<T>();指定集合元素只能是T类型
- [2]ArrayList<?> al=new ArrayList<?>();集合元素可以是任意类型,这种没有意义,一般是方法中,只是为了说明用法
- [3]ArrayList<? extends E> al=new ArrayList<? extends E>();
上下界
泛型与向上转型的概念
- 协变:子类能向父类转换
- 逆变: 父类能向子类转换
C
上界:extends
- 指定类型是子类
- 上边界,即传入的类型实参必须是指定类型的子类型
- <任意字符 extends 类/接口>表示泛型的上限。
- T extends A 指传入实参类型T 必须是A或A的子类型
- ? extends T 指实参类型?必须是T或T的子类型
下界: super
- 指定类型是父类
- <任意字符 super 类/接口>表示泛型的下限。
上下界使用
- 上界<? extends T>不能往里存,只能往外取
- 下界<? super T>不影响往里存,但往外取只能放在Object对象里
<T extends Comparable<? super T>>
- extends对泛型上限进行了限制即T必须是Comparable<? super T>的子类,然后<? super T>表示Comparable<>中的类型下限为T!
2. <T extends Comparable<T>>
和 <T extends Comparable<? super T>>
有什么不同
<T extends Comparable<T>>
- 类型T必须实现
Comparable
接口,并且这个接口的类型是T,这样,T的实例之间才能相互比较大小。
<T extends Comparable<? super T>>
- 类型T必须实现
Comparable
接口,并且这个接口的类型是T或者是T的任一父类。这样声明后,T的实例之间和T的父类的实例之间可以相互比较大小。
使用
在调用泛型方法时,可以指定泛型,也可以不指定泛型。
- 不指定泛型,该方法的几种类型的同一父类的最小级,直到Object
- 在指定泛型的情况下,该方法的几种类型必须是该泛型的实例的类型或者其子类
泛型方法指定类型: ArrayAlg.<String>getMiddle(names);
实现
- 当实现泛型接口的类,未传入泛型实参时,将泛型的声明也一起加到类中
- 泛型方法,声明了<T>的方法才是泛型方法
代码关键使用理解:
上下界使用理解
1 为什么要用通配符和边界?
- Plate<Fruit> p=new Plate<Apple>(new Apple()); //错误
- 原因:容器装的东西有继承,但容器没有继承。
上界:
- 能放水果以及水果派生类的盘子,
Plate<? extends Fruit>
是Plate<Fruit>
以及Plate<Apple>
的基类。
下界:
- 能放水果以及水果基类的盘子。
Plate<? super Fruit>
是Plate<Fruit>,Plate<Food>
的基类
上界<? extends T>不能往里存,只能往外取的理解
- Plate<? extends Fruit> p=new Plate<Apple>(new Apple());
- 编译器在类型Plate赋值后。只标上占位符:CAP#1,具体类不知道,插入对象编译器不知道是否匹配,所以都不允许。
- 插入类可能不是编译类就,但有同一父类,
- 读取时可以转化为相应类
- 放东西的set( )方法失效。但取东西get( )方法有效
T都代表同一种类型
- public <T> List<T> fill(T... t);
?是通配符,泛指所有类型,看成所有类型的父类,真实的类型,类型实参
Plate<?>
单纯的就表示:盘子里放了一个东西,是什么我不知道。
总结
Plate<? extends Fruit>
里什么都放不进去。
下界<? super T>不影响往里存,但往外取只能放在Object对象里的理解
- Plate<? super Fruit> p=new Plate<Fruit>(new Fruit());
- 下界规定了元素的最小粒度的下限,实际上是放松了容器元素的类型控制。
- 既然元素是Fruit的基类,那往里存粒度比Fruit小的都可以。
- 但往外读取元素就费劲了,只有所有类的基类Object对象才能装下。但这样的话,元素的类型信息就全部丢失。
PECS(Producer Extends Consumer Super)原则
- 频繁往外读取内容的,适合用上界Extends。
- 经常往里插入的,适合用下界Super。
Java获取泛型T的类型 T.class
泛型实例化后可以获取具体类型
- ((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
总结:
- 返回类型 getClass
- 返回直接继承的父类(或者泛型T的具体类型) getGenericSuperclass
- 参数化类型 ParameterizedType
- 获取第一个泛型参数的类型类 getActualTypeArguments()[0]
Class<Integer> cla;与Class<?> cl;
- 前一个表示cla只能指向Integer这种类型,而后一个cl表示可以指向任意类型。
代码:
泛型的本质是参数化类型
也就是说,泛型就是将所操作的数据类型作为参数的一种语法。
public class Paly<T>{
T play(){}
}
其中T
就是作为一个类型参数在Play
被实例化的时候所传递来的参数,比如:
Play<Integer> playInteger=new Play<>();
当实现泛型接口的类
- 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
- class FruitGenerator<T> implements Generator<T>
’?’是类型实参,而不是类型形参 ,可以把?看成所有类型的父类。是一种真实的类型。
- showKeyValue1(Generic<?> obj
泛型方法,声明了<T>的方法才是泛型方法
- public <T> T genericMethod(Class<T> tClass)
泛型方法与可变参数
- public <T> void printMsg( T... args)
泛型数组
- java中是”不能创建一个确切的泛型类型的数组”
- 数组的类型不可以是类型变量,除非是采用通配符的方式
也就是说下面的这个例子是不可以的:
List<String>[] ls = new ArrayList<String>[10];
而使用通配符创建泛型数组是可以的,如下面这个例子:
List<?>[] ls = new ArrayList<?>[10];
这样也是可以的:
List<String>[] ls = new ArrayList[10];
java泛型中<?>和<T>有什么区别?
T 代表一种类型
?是通配符,泛指所有类型
- 一般定义引用变量,这么做的好处是,如下所示,定义一个sup的引用变量,就可以指向多个对象。
好处:
- SuperClass<?> sup = new SuperClass<String>("lisi");
- sup = new SuperClass<People>(new People());
- sup = new SuperClass<Animal>(new Animal());
1
- 如果有泛型方法和非泛型方法,都满足条件,会执行非泛型方法
- 加了泛型了参数不能调用与参数类型有关的方法
代码:
- public static void printCollecton(Collection <?> collection)
- for(Object obj: collection)
List<T>是泛型方法,List<?>是限制通配符
List<T>一般有两种用途:
- 1、定义一个通用的泛型方法。
- 2、限制方法的参数之间或参数和返回结果之间的关系。
代码:
- List<T> getList<T param1,T param2>
extends
- 如<任意字符 extends 类/接口>表示泛型的上限。
代码
- class Demo<T extends List>{}
- Demo<ArrayList> p = null; // 编译正确
<T extends Comparable<? super T>>
- extends对泛型上限进行了限制即T必须是Comparable<? super T>的子类,然后<? super T>表示Comparable<>中的类型下限为T!
2. <T extends Comparable<T>>
和 <T extends Comparable<? super T>>
有什么不同
<T extends Comparable<T>>
- 类型T必须实现
Comparable
接口,并且这个接口的类型是T,这样,T的实例之间才能相互比较大小。
<T extends Comparable<? super T>>
- 类型T必须实现
Comparable
接口,并且这个接口的类型是T或者是T的任一父类。这样声明后,T的实例之间和T的父类的实例之间可以相互比较大小。
Java获取泛型T的类型 T.class
泛型实例化后可以获取具体类型
- ((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
总结:
- 返回类型 getClass
- 返回直接继承的父类(或者泛型T的具体类型) getGenericSuperclass
- 参数化类型 ParameterizedType
- 获取第一个泛型参数的类型类 getActualTypeArguments()[0]
实践:
- public static <T extends Comparable<? super T>> void mySort2(List<T> l)
- mySort2(animals);
- mySort2(dogs);
这里接口类型:
- 泛型的参数类型。
类型擦除
- 使用泛型的时候加上类型参数,在编译器编译的时候会去掉,生成的字节码中是不包含泛型中的类型信息的
总结
- 使用泛型的时候加上类型参数,在编译器编译的时候会去掉,
1-2.通过两个例子证明Java类型的类型擦除
- 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());
结论:
- 为
true
。说明泛型类型String
和Integer
都被擦除掉了,只剩下原始类型。
例2.通过反射添加其它类型元素
- ArrayList<Integer> list = new ArrayList<Integer>(); list.add(1);
- list.getClass().getMethod("add", Object.class).invoke(list, "asd");
- 可以存储字符串,这说明了
Integer
泛型实例在编译之后被擦除掉了
总结:
- 数组的泛型无论添加还是反射最后都是object
2.类型擦除后保留的原始类型
- 就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型
- 无论何时定义一个泛型,相应的原始类型都会被自动提供,类型变量擦除,并使用其限定类型(无限定的变量用Object)替换。
总结原始类型:
- 擦除去了泛型信息,在字节码中的类型变量的真正类型,无限定用Object。
代码:
- class Pair<T> { private T value;
- class Pair { private Object value;
在调用泛型方法时,可以指定泛型,也可以不指定泛型。
- 在不指定泛型的情况下,泛型变量的类型为该方法中的几种类型的同一父类的最小级,直到Object
- 在指定泛型的情况下,该方法的几种类型必须是该泛型的实例的类型或者其子类
代码:
/**不指定泛型的时候*/
- 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
其实在泛型类中,不指定泛型的时候,也差不多,只不过这个时候的泛型为Object
3-1.先检查,再编译以及编译的对象和引用传递问题
Q: 既然说类型变量会在编译的时候擦除掉,那为什么我们往 ArrayList 创建的对象中添加整数会报错呢?不是说泛型变量String会在编译的时候变为Object类型吗?为什么不能存别的类型呢?既然类型擦除了,如何保证我们只能使用泛型变量限定的类型呢?
- A: Java编译器是通过先检查代码中泛型的类型,然后在进行类型擦除,再进行编译。
类型变量会在编译的时候擦除掉,为什么添加整数会报错
- 先检查代码中泛型的类型,然后在进行类型擦除,再进行编译。
代码:
- ArrayList<String> list = new ArrayList<String>();
- list.add("123");
- list.add(123);//编译错误
类型检查就是针对引用的,谁是一个引用,用这个引用调用泛型方法,就会对这个引用调用的方法进行类型检测,而无关它真正引用的对象。
代码:
- ArrayList<String> list1 = new ArrayList(); list1.add("1"); //编译通过 list1.add(1); //编译错误
- ArrayList list2 = new ArrayList<String>(); list2.add("1"); //编译通过 list2.add(1); //编译通过
- new ArrayList<String>().add("11"); //编译通过 new ArrayList<String>().add(22); //编译错误
总结:没有引用 ,类型是对象类型
泛型中参数话类型为什么不考虑继承关系?
- ArrayList<String> list1 = new ArrayList<Object>(); //编译错误
- ArrayList<Object> list2 = new ArrayList<String>(); //编译错误
3-2.自动类型转换
因为类型擦除的问题,所以所有的泛型类型变量最后都会被替换为原始类型。既然都被替换为原始类型,那么为什么我们在获取的时候,不需要进行强制类型转换呢?
看下ArrayList.get()
方法:
- public E get(int index) {
- return (E) elementData[index]; }
自动
- 不用自己进行强转。当存取一个泛型域时也会自动插入强制类型转换。
3-3.类型擦除与多态的冲突和解决方法
- 类型擦除后,父类的的泛型类型全部变为了原始类型
Object
- 编译器生成桥方法,调用重写方法。
代码
- @Override public void setValue(Date value) { super.setValue(value); }
- @Override public Date getValue() { return super.getValue(); } }
父类原始
- public T getValue() { return value; }
- public void setValue(T value) { this.value = value; }
父类变为
- public Object getValue() { return value; }
- public void setValue(Object value) { this.value = value; }
3-6.泛型在静态方法和静态类中的问题
- public class Test2<T> { public static T one; //编译错误
泛型参数的实例化是在定义对象的时候指定的,而静态变量和静态方法不需要使用对象来调用
- static <T >T show(T one){ //这是正确的
泛型方法中使用的T是自己在方法中定义的 T
总结:
- 泛型类中的T,静态方法不能使用
- 静态方法定义的T,方法内可以使用
参考具体;
代码如下所示:
import java.util.GregorianCalendar;
class Demo<T extends Comparable<T>>{}
//注意这里是没有? super的
public class Test
{
public static void main(String[] args) {
Demo<GregorianCalendar> p = null;
}
}
这里编译报错,因为这里的<T extends Comparable<T>>相当于<GregorianCalendar extends Comparable<GregorianCalendar>>,但是GregorianCalendar中并没有实现Comparable<GregorianCalendar>,而是仅仅持有从Calendar继承过来的Comparable<Calendar>,这样就会因为不在限制范围内而报错。
import java.util.GregorianCalendar;
class Demo<T extends Comparable<? super T>>{}
public class Test1
{
public static void main(String[] args) {
Demo<GregorianCalendar> p = null; // 编译正确
}
}
此时编译通过,这里可以理解为<GregorianCalendar extends Comparable<Calendar>>!因为Calendar为GregorianCalendar 的父类并且GregorianCalendar 实现了Comparable<Calendar>,具体可以在API中进行查看!
3. 实例代码演示
代码如下所示:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Test
{
//第一种声明:简单,灵活性低
public static <T extends Comparable<T>> void mySort1(List<T> list)
{
Collections.sort(list);
}
//第二种声明:复杂,灵活性高
public static <T extends Comparable<? super T>> void mySort2(List<T> l)
{
Collections.sort(list);
}
public static void main(String[] args)
{
//主函数中将分别创建Animal和Dog两个序列,然后调用排序方法对其进行测试
//main函数中具体的两个版本代码将在下面具体展示
}
}
class Animal implements Comparable<Animal>
{
protected int age;
public Animal(int age)
{
this.age = age;
}
//使用年龄与另一实例比较大小
@Override
public int compareTo(Animal other)
{
return this.age - other.age;
}
}
class Dog extends Animal
{
public Dog(int age)
{
super(age);
}
}
上面的代码包括三个类:
Animal
实现了Comparable<Animal>
接口,通过年龄来比较实例的大小- Dog从Animal继承,为其子类。
Test
类中提供了两个排序方法和测试用的main()
方法:mySort1()
使用<T extends Comparable<T>>
类型参数mySort2()
使用<T extends Comparable<? super T>>
类型参数main()
测试方法。在这里将分别创建Animal和Dog两个序列,然后调用排序方法对其进行测试。
3.1 对mySort1()进行测试,main方法代码如下所示:
// 创建一个 Animal List
List<Animal> animals = new ArrayList<Animal>();
animals.add(new Animal(25));
animals.add(new Dog(35));
// 创建一个 Dog List
List<Dog> dogs = new ArrayList<Dog>();
dogs.add(new Dog(5));
dogs.add(new Dog(18));
// 测试 mySort1() 方法
mySort1(animals);
mySort1(dogs);
结果编译出错,报错信息为:
The method mySort1(List<T>) in the type TypeParameterTest is not applicable for the arguments (List<Dog>)
mySort1() 方法的类型参数是<T extends Comparable<T>>,它要求的类型参数是类型为T的Comparable。
如果传入的是List<Animal>程序将正常执行,因为Animal实现了接口Comparable<Animal>。
但是,如果传入的参数是List<Dog>程序将报错,因为Dog类中没有实现接口Comparable<Dog>,它只从Animal继承了一个Comparable<Animal>接口。
注意:animals list中实际上是包含一个Dog实例的。如果碰上类似的情况(子类list不能传入到一个方法中),可以考虑把子类实例放到一个父类 list 中,避免编译错误。
3.2 对mySort12()进行测试,main方法代码如下所示:
public static void main(String[] args)
{
// 创建一个 Animal List
List<Animal> animals = new ArrayList<Animal>();
animals.add(new Animal(25));
animals.add(new Dog(35));
// 创建一个 Dog List
List<Dog> dogs = new ArrayList<Dog>();
dogs.add(new Dog(5));
dogs.add(new Dog(18));
// 测试 mySort2() 方法
mySort2(animals);
mySort2(dogs);
}
这时候我们发现该程序可以正常运行。它不但能够接受Animal implements Comparable<Animal>这样的参数,也可以接收:Dog implements Comparable<Animal>这样的参数。
3.3 是否可以通过将Dog实现Comparable<Dog>来解决问题?
由分析可得程序出现问题是因为Dog类没有实现接口Comparable<Dog>,那么我们能否将该类实现接口Comparable<Dog>来解决问题呢?
代码如下所示:
class Dog extends Animal implements Comparable<Dog>
{
public Dog(int age)
{
super(age);
}
}
结果程序编译报错,错误信息如下所示:
The interface Comparable cannot be implemented more than once with different arguments: Comparable<Animal> and Comparable<Dog>
意义是Dog类已经从Animal中继承了Comparable该接口,无法再实现一个Comparable。
若子类需要使用自己的比较方法,则需要重写父类的public int CompareTo(Animal other)方法。
4. 总结
对Animal/Dog这两个有父子关系的类来说:<T extends Comparable<? super T>>
可以接受List<Animal>,也可以接收 List<Dog> 。而<T extends Comparable<T>>
只可以接收 List<Animal>所以,<T extends Comparable<? super T>>这样的类型参数对所传入的参数限制更少,提高了 API 的灵活性。总的来说,在保证类型安全的前提下,要使用限制最少的类型参数。
1-2.通过两个例子证明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
泛型实例在编译之后被擦除掉了,只保留了原始类型。
例3.原始类型Object
class Pair<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
Pair的原始类型为:
class Pair {
private Object value;
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
3-3.类型擦除与多态的冲突和解决方法
现在有这样一个泛型类:
class Pair<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
然后我们想要一个子类继承它。
class DateInter extends Pair<Date> {
@Override
public void setValue(Date value) {
super.setValue(value);
}
@Override
public Date getValue() {
return super.getValue();
}
}
在这个子类中,我们设定父类的泛型类型为Pair<Date>
,在子类中,我们覆盖了父类的两个方法,我们的原意是这样的:将父类的泛型类型限定为Date
,那么父类里面的两个方法的参数都为Date
类型。
public Date getValue() {
return value;
}
public void setValue(Date value) {
this.value = value;
}
所以,我们在子类中重写这两个方法一点问题也没有,实际上,从他们的@Override
标签中也可以看到,一点问题也没有,实际上是这样的吗?
分析:实际上,类型擦除后,父类的的泛型类型全部变为了原始类型Object
,所以父类编译之后会变成下面的样子:
class Pair {
private Object value;
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
再看子类的两个重写的方法的类型:
@Override
public void setValue(Date value) {
super.setValue(value);
}
@Override
public Date getValue() {
return super.getValue();
}
先来分析setValue
方法,父类的类型是Object
,而子类的类型是Date
,参数类型不一样,这如果实在普通的继承关系中,根本就不会是重写,而是重载。
我们在一个main方法测试一下:
public static void main(String[] args) throws ClassNotFoundException {
DateInter dateInter = new DateInter();
dateInter.setValue(new Date());
dateInter.setValue(new Object()); //编译错误
}
如果是重载,那么子类中两个setValue
方法,一个是参数Object
类型,一个是Date
类型,可是我们发现,根本就没有这样的一个子类继承自父类的Object类型参数的方法。所以说,却是是重写了,而不是重载了。
为什么会这样呢?
原因是这样的,我们传入父类的泛型类型是Date,Pair<Date>
,我们的本意是将泛型类变为如下:
class Pair {
private Date value;
public Date getValue() {
return value;
}
public void setValue(Date value) {
this.value = value;
}
}
然后再子类中重写参数类型为Date的那两个方法,实现继承中的多态。
可是由于种种原因,虚拟机并不能将泛型类型变为Date
,只能将类型擦除掉,变为原始类型Object
。这样,我们的本意是进行重写,实现多态。可是类型擦除后,只能变为了重载。这样,类型擦除就和多态有了冲突。JVM知道你的本意吗?知道!!!可是它能直接实现吗,不能!!!如果真的不能的话,那我们怎么去重写我们想要的Date
类型参数的方法啊。
于是JVM采用了一个特殊的方法,来完成这项功能,那就是桥方法。
首先,我们用javap -c className
的方式反编译下DateInter
子类的字节码,结果如下:
class com.tao.test.DateInter extends com.tao.test.Pair<java.util.Date> {
com.tao.test.DateInter();
Code:
0: aload_0
1: invokespecial #8 // Method com/tao/test/Pair."<init>":()V
4: return
public void setValue(java.util.Date); //我们重写的setValue方法
Code:
0: aload_0
1: aload_1
2: invokespecial #16 // Method com/tao/test/Pair.setValue:(Ljava/lang/Object;)V
5: return
public java.util.Date getValue(); //我们重写的getValue方法
Code:
0: aload_0
1: invokespecial #23 // Method com/tao/test/Pair.getValue:()Ljava/lang/Object;
4: checkcast #26 // class java/util/Date
7: areturn
public java.lang.Object getValue(); //编译时由编译器生成的巧方法
Code:
0: aload_0
1: invokevirtual #28 // Method getValue:()Ljava/util/Date 去调用我们重写的getValue方法;
4: areturn
public void setValue(java.lang.Object); //编译时由编译器生成的巧方法
Code:
0: aload_0
1: aload_1
2: checkcast #26 // class java/util/Date
5: invokevirtual #30 // Method setValue:(Ljava/util/Date; 去调用我们重写的setValue方法)V
8: return
}
从编译的结果来看,我们本意重写setValue
和getValue
方法的子类,竟然有4个方法,其实不用惊奇,最后的两个方法,就是编译器自己生成的桥方法。可以看到桥方法的参数类型都是Object,也就是说,子类中真正覆盖父类两个方法的就是这两个我们看不到的桥方法。而打在我们自己定义的setvalue
和getValue
方法上面的@Oveerride
只不过是假象。而桥方法的内部实现,就只是去调用我们自己重写的那两个方法。
所以,虚拟机巧妙的使用了桥方法,来解决了类型擦除和多态的冲突。
不过,要提到一点,这里面的setValue
和getValue
这两个桥方法的意义又有不同。
setValue
方法是为了解决类型擦除与多态之间的冲突。
而getValue
却有普遍的意义,怎么说呢,如果这是一个普通的继承关系:
那么父类的setValue
方法如下:
public ObjectgetValue() {
return super.getValue();
}
而子类重写的方法是:
public Date getValue() {
return super.getValue();
}
其实这在普通的类继承中也是普遍存在的重写,这就是协变。
关于协变:。。。。。。
并且,还有一点也许会有疑问,子类中的巧方法Object getValue()
和Date getValue()
是同 时存在的,可是如果是常规的两个方法,他们的方法签名是一样的,也就是说虚拟机根本不能分别这两个方法。如果是我们自己编写Java代码,这样的代码是无法通过编译器的检查的,但是虚拟机却是允许这样做的,因为虚拟机通过参数类型和返回类型来确定一个方法,所以编译器为了实现泛型的多态允许自己做这个看起来“不合法”的事情,然后交给虚拟器去区别。
参考:
https://www.cnblogs.com/dengchengchao/p/9717097.html
https://www.cnblogs.com/coprince/p/8603492.html
Java 为什么不支持创建泛型化数组:https://blog.youkuaiyun.com/codejas/article/details/89705168
泛型的上下界
https://www.liangzl.com/get-article-detail-130634.html
https://www.cnblogs.com/jpfss/p/9929045.html
Java泛型的应用——T extends Comparable<? super T>
https://www.cnblogs.com/cherryljr/p/6880657.html
Java泛型类型擦除以及类型擦除带来的问题:
https://www.cnblogs.com/wuqinglong/p/9456193.html
获取泛型具体类型:
https://blog.youkuaiyun.com/hellozhxy/article/details/82024712