文章目录
一、泛型擦除
二、泛型的上界通配符 <? extends T>
三、泛型的下界通配符 <? super T>
一、泛型擦除
泛型只保留到 编译期 , 在 编译完毕后 , 泛型就不存在了 ;
在运行时 , 通过反射 , 调用泛型类 , 即使违反了泛型规则 , 也能进行相关操作 , 这是因为 在运行时 , 已经没有泛型相关的限制 , 泛型限制在编译时就已经被擦除了 ;
但是 泛型的信息 , 保存在了常量表中 , 仍然可以获取到 ;
泛型擦除 是为了 泛型可以兼容 老版本的 JDK 而设计的 ,
泛型是 JDK 5 加入的 , 如果添加了泛型 , 导致字节码的格式改变 , 必然导致之前版本的 JDK 无法运行有泛型的字节码程序 ;
给定一个 Demo.java 类 , 其中定义了一个 <T> T get() 方法 ;
public class Demo { public <T> T get(){ T t = null; return t; } }
将其编译后 , 查看字节码附加信息 ;
D:\java>javap -v Demo.class Classfile /D:/java/Demo.class Last modified 2021-9-7; size 307 bytes MD5 checksum 727bc59421b23a5f0a31af0e91630ab8 Compiled from "Demo.java" public class Demo minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #3.#14 // java/lang/Object."<init>":()V #2 = Class #15 // Demo #3 = Class #16 // java/lang/Object #4 = Utf8 <init> #5 = Utf8 ()V #6 = Utf8 Code #7 = Utf8 LineNumberTable #8 = Utf8 get #9 = Utf8 ()Ljava/lang/Object; #10 = Utf8 Signature #11 = Utf8 <T:Ljava/lang/Object;>()TT; #12 = Utf8 SourceFile #13 = Utf8 Demo.java #14 = NameAndType #4:#5 // "<init>":()V #15 = Utf8 Demo #16 = Utf8 java/lang/Object { public Demo(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 1: 0 public <T extends java.lang.Object> T get(); descriptor: ()Ljava/lang/Object; flags: ACC_PUBLIC Code: stack=1, locals=2, args_size=1 0: aconst_null 1: astore_1 2: aload_1 3: areturn LineNumberTable: line 3: 0 line 4: 2 Signature: #11 // <T:Ljava/lang/Object;>()TT; } SourceFile: "Demo.java"
Demo 中的 get 方法类型返回值是 Ljava/lang/Object , 不是泛型 T , 这就是泛型在字节码中被擦除了 ;
descriptor: ()Ljava/lang/Object;
执行下面的代码 , 在运行时 , 使用反射 , 向 list1 集合中添加 int 类型的元素 , 添加成功 ; 说明在运行时 , 不再进行泛型检查 , 即使不符合泛型要求 , 也能操作成功 ;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; public class Main { public static void main(String[] args) { // 编译器 在 编译期 进行检查 List<String> list1 = new ArrayList<>(); //list1.add(1); // 编译器 在 编译期 不进行检查 List list2 = new ArrayList<String>(); //list2.add(1); try { Method method = ArrayList.class.getMethod("add", Object.class); method.invoke(list1, 1); System.out.println("list1 集合大小 : " + list1.size()); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } }
执行结果 :
二、泛型的上界通配符 <? extends T>
泛型 上界通配符 : <T extends Person> 表示泛型 T 是 Person 的子类 , <? extends T> 泛型类型 只能是 T 的子类 ;
<T extends Person> 只能在声明泛型时使用 , 不能在 使用 泛型 时使用 ; public class Data<T extends Person> { }
<? extends T> 只能在使用 泛型 时使用 , 不能在声明 泛型类/方法 时 使用 ; Data<? extends Person> data6 = new Data<>();
3 33 个有继承关系的类 :
public class Animal { }
public class Person extends Animal { }
public class Woman extends Person { }
泛型类 : 该泛型类接收一个泛型 , 该泛型 T 是 Person 的子类 ;
public class Data<T extends Person> { }
main 函数 :
// 上边界通配符 // 符合要求 , 可设置 Woman 的父类 , Woman 以及 Person 类 Data<? super Woman> data4 = new Data<>(); // 符合要求 , 可设置 Person 类 Data<? super Person> data5 = new Data<>(); // 不符合要求 , Animal 是最顶层的类 , 其取值都不符合 <T extends Person> 要求 Data<? super Animal> data6 = new Data<>();
报错信息 : 传入不符合要求的泛型的报错信息 ;
三、泛型的下界通配符 <? super T>
泛型 下界通配符 : <? super T> 泛型类型 只能是 T 的父类 ;
<? super T> 只能在使用 泛型 时使用 , 不能在声明 泛型类/方法 时 使用 ; Data<? super Person> data6 = new Data<>();
3 33 个有继承关系的类 :
public class Person extends Animal { }
public class Woman extends Person { }
泛型类 : 该泛型类接收一个泛型 , 该泛型 T 是 Person 的子类 ;
public class Data<T extends Person> { }
main 函数 :
// 上边界通配符 // 符合要求 , 可设置 Woman 的父类 , Woman 以及 Person 类 Data<? super Woman> data4 = new Data<>(); // 符合要求 , 可设置 Person 类 Data<? super Person> data5 = new Data<>(); // 不符合要求 , Animal 是最顶层的类 , 其取值都不符合 <T extends Person> 要求 //Data<? super Animal> data6 = new Data<>();
报错信息 : 不符合要求 , Animal 是最顶层的类 , 其取值都不符合 要求