天天看点

Java 泛型简单总结实践:<? extends T>和<? super T>

概念:

  • 泛型的本质是参数化类型

泛型实质

  • 只在编译阶段有效。在编译后,将泛型信息擦出,添加类型检查和类型转换
  • 作为语法糖对于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为什么不能创建泛型数组

  • 泛型擦除后,运行时能添加任何类型

java泛型中<?>和<T>有什么区别?

  • T 代表一种类型
  • ?是通配符,泛指所有类型,看成所有类型的父类,真实的类型,类型实参

T和?运用的地方有点不同

  • ?是定义在引用变量上,指向多个对象。
  • T是类上或方法上

List<T>是泛型方法,List<?>是限制通配符

List<T>一般有两种用途:

  • 1、定义通用的泛型方法。
  • 2、限制方法参数和返回结果的关系。

List<?>一般就是在泛型起一个限制作用。

  1. 类型参数“<T>”,声明泛型类或泛型方法。
  2. 通配符“<?>”,使用泛型类或泛型方法(因为定义在引用变量上,指向多个对象。)

运用

  • 如果有泛型方法和非泛型方法,都满足条件,会执行非泛型方法

泛型三种:

  •           [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<>();
           

当实现泛型接口的类

  1. 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
  • 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,方法内可以使用

参考具体;

  代码如下所示:

Java 泛型简单总结实践:&lt;? extends T&gt;和&lt;? super T&gt;
import java.util.GregorianCalendar;
class Demo<T extends Comparable<T>>{}
//注意这里是没有? super的
public class Test
{
    public static void main(String[] args) {
       Demo<GregorianCalendar> p = null; 
        }
}
           
Java 泛型简单总结实践:&lt;? extends T&gt;和&lt;? super T&gt;

  这里编译报错,因为这里的<T extends Comparable<T>>相当于<GregorianCalendar extends Comparable<GregorianCalendar>>,但是GregorianCalendar中并没有实现Comparable<GregorianCalendar>,而是仅仅持有从Calendar继承过来的Comparable<Calendar>,这样就会因为不在限制范围内而报错。

Java 泛型简单总结实践:&lt;? extends T&gt;和&lt;? super T&gt;
import java.util.GregorianCalendar;

class Demo<T extends Comparable<? super T>>{}

public class Test1
{
    public static void main(String[] args) {
       Demo<GregorianCalendar> p = null; // 编译正确
    }
}  
           
Java 泛型简单总结实践:&lt;? extends T&gt;和&lt;? super T&gt;

  此时编译通过,这里可以理解为<GregorianCalendar extends Comparable<Calendar>>!因为Calendar为GregorianCalendar 的父类并且GregorianCalendar 实现了Comparable<Calendar>,具体可以在API中进行查看!

  3. 实例代码演示

  代码如下所示:

Java 泛型简单总结实践:&lt;? extends T&gt;和&lt;? super T&gt;
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);
     }
 }
           
Java 泛型简单总结实践:&lt;? extends T&gt;和&lt;? super T&gt;

上面的代码包括三个类:

  1. Animal

    实现了

    Comparable<Animal>

    接口,通过年龄来比较实例的大小
  2. Dog从Animal继承,为其子类。
  3. Test

    类中提供了两个排序方法和测试用的

    main()

    方法:
    • mySort1()

      使用

      <T extends Comparable<T>>

      类型参数
    • mySort2()

      使用

      <T extends Comparable<? super T>>

      类型参数
    • main()

      测试方法。在这里将分别创建Animal和Dog两个序列,然后调用排序方法对其进行测试。

  3.1 对mySort1()进行测试,main方法代码如下所示:

Java 泛型简单总结实践:&lt;? extends T&gt;和&lt;? super T&gt;
// 创建一个 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);
           
Java 泛型简单总结实践:&lt;? extends T&gt;和&lt;? super T&gt;

  结果编译出错,报错信息为:

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方法代码如下所示:

Java 泛型简单总结实践:&lt;? extends T&gt;和&lt;? super T&gt;
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);
}
           
Java 泛型简单总结实践:&lt;? extends T&gt;和&lt;? super T&gt;

  这时候我们发现该程序可以正常运行。它不但能够接受Animal implements Comparable<Animal>这样的参数,也可以接收:Dog implements Comparable<Animal>这样的参数。

  3.3 是否可以通过将Dog实现Comparable<Dog>来解决问题?

  由分析可得程序出现问题是因为Dog类没有实现接口Comparable<Dog>,那么我们能否将该类实现接口Comparable<Dog>来解决问题呢?

  代码如下所示:

Java 泛型简单总结实践:&lt;? extends T&gt;和&lt;? super T&gt;
class Dog extends Animal implements Comparable<Dog>
{
    public Dog(int age)
    {
        super(age);
    }
}
           
Java 泛型简单总结实践:&lt;? extends T&gt;和&lt;? super T&gt;

  结果程序编译报错,错误信息如下所示:

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.csdn.net/codejas/article/details/89705168

泛型的上下界

https://www.liangzl.com/get-article-detail-130634.html

java泛型中<?>和<T>有什么区别?

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.csdn.net/hellozhxy/article/details/82024712

<? extends T>和<? super T>

https://www.cnblogs.com/drizzlewithwind/p/6100164.html