泛型

以下内容均为java语言环境下的情况

泛型的分类:

  • 泛型接口:interface GenericInterface<T> {}

  • 泛型类:class GenericClass<T> implements GenericInterface<T> {}

    • 类(接口)的泛型定义位置紧跟在类(接口)之后,可替代该类(接口)定义内部的任意类型;
    • 该类(接口)被声明时,确定泛型参数;
  • 泛型方法:public <K, E extends Exception> K genericMethod(K param) throws E {}

    • 方法的泛型定义位置在修饰符之后,返回值之前,可替代该方法中使用的任意类型;

    • 方法被调用时确定泛型参数;一般情况下是通过方法参数确定的泛型参数;

    • 无参的泛型方法使用如下语法指定泛型参数:

      // 声明泛型方法
      public static <K extends Number>K getNum(){
          return null;
      }
      // 在方法名之前确定泛型参数
      Integer integer = GenericTest.<Integer>getNum();
      

有界泛型

  1. ? 通配符类型,指代某一个任意类型,但不是Object
  2. T extends UpperBound 泛型T的上界,可用于定义及声明
    在方法、接口、类的定义时需要使用泛型参数名(例如K、T),在声明位置使用时需要使用通配符?
  3. T super LowerBound 泛型T的下界,泛型下界只能用于声明处,表示泛型参数一定是指定类或其父类。

泛型上界只能用于读取T及其子类,不能写入,泛型下界只能写入T及其子类,不能读

泛型通配符?示例

package generic;

class Parent {}
class Sub1 extends Parent {}
class Sub2 extends Parent {}
// 无界泛型
public class WildcardSample<T> {
    
    T obj;
    void test() {
        WildcardSample<Parent> sample1 = new WildcardSample<>();
        // 编译错误
        // Required type: WildcardSample<Parent>
        // Provided: WildcardSample<Sub1>
        WildcardSample<Parent> sample2 = new WildcardSample<Sub1>();
        
        WildcardSample<?> sample3 = new WildcardSample<Parent>();
        WildcardSample<?> sample4 = new WildcardSample<Sub1>();
        WildcardSample<?> sample5 = new WildcardSample<Sub2>();
        
        sample1.obj = new Sub1();
        // 编译错误:
        // Required type: capture of ?
        // Provided: Sub1
        sample3.obj = new Sub1();
    }
}

上界泛型示例:

package generic;

public class ExtendSample<T extends Parent> {
    
    T obj;
    
    <K extends Sub1> T extendMethod(K param) {
        return this.obj;
    }
}

class Generic {
    public static void main(String[] args) {
        // 泛型上界
        ExtendSample<Parent> sample1 = new ExtendSample<>();
        ExtendSample<Sub1> sample2 = new ExtendSample<Sub1>();
        ExtendSample<? extends Parent> sample3 = new ExtendSample<Sub1>();
        ExtendSample<? extends Sub1> sample4;

        // 编译错误
        // Required type: ExtendSample<? extends Sub1>
        // Provided: ExtendSample<Sub2>
        sample4 = new ExtendSample<Sub2>();

        // 编译错误:声明的范围不在ExtendSample定义指定的范围内
        // Type parameter '? extends Number' is not within its bound; should extend 'generic.Parent'
        ExtendSample<? extends Number> sample5;
        sample1.obj = new Sub1();
        
        // 编译错误:上界泛型只能读,不能写
        // Required type: capture of ? extends Parent
        // Provided: Parent
        sample3.obj = new Parent();
        // 编译通过,使用上界Parent可以读出数据
        Parent obj = sample3.obj;
    }
}

下界泛型示例:

package generic;

public class SuperSample<T> {
    T obj;
}

class GenericSuper {
    public static void main(String[] args) {
        // 泛型下界
        SuperSample<? super Parent> sample1 = new SuperSample<Parent>();
        // 编译错误:只能存放Parent或它的父类
        SuperSample<? super Parent> sample2 = new SuperSample<Sub1>();
        SuperSample<? super Sub1> sample3 = new SuperSample<Parent>();
        
        sample1.obj = new Sub1();
        sample1.obj = new Sub2();
        sample1.obj = new Parent();
        
        sample3.obj = new Sub1();
        // 编译错误
        sample3.obj = new Sub2();
        // 编译错误:下界泛型只能写入声明的下界类型或其子类
        sample3.obj = new Parent();
        // 下界泛型只能写入,不能读,只能是Object类型接收数据
        Object obj = sample3.obj;
    }
}

复杂泛型

  1. 多个泛型参数定义由,分隔,例如<T, K>
  2. 同一个泛型参数如果有多个上界,那么各个上界用符号&连接(在反射时该参数按照第一个上界处理,使用反射获取该方法时需要方法名和参数类型,此时参数类型只能使用第一个上界的类型才能获取到)
  3. 多个上界类型里最多只能有一个类,其他必须为接口,如果有类则必须放在第一位。

数组和泛型容器

  1. 数组具有协变性,而泛型具有无关性
  2. 数组是具象化的,而泛型不是
    若类A是类B的子类,则记作A≤B,设有变换f(),则有以下定律:
  • 当A≤B时,有f(A)≤f(B),则f()具有协变性
  • 当A≤B时,有f(B)≤f(A),则f()具有逆变性
  • 如果以上两者都不成立,则变换f具有无关性

例如:

        // 编译通过
        Object[] array = new String[10];
        // 编译不通过
        ArrayList<Object> list1 = new ArrayList<String>();
        // 编译不通过
        ArrayList<String> list2 = new ArrayList<Object>();
        
        // String ≤ Object
        // 数组的变换f(A) = A[]即:
        // f(Object) = Object[]    f(String) = String[]
        // f(String) ≤ f(Object)成立,其具有协变性
        
        // ArrayList的变换同理
        // f(String) = ArrayList<String>    f(Object) = ArrayList<Object>
        // f(String) ≥ f(Object) 与 f(String) ≤ f(Object)均不成立,故其具有无关性

        // 编译通过,但运行时抛出异常:java.lang.ArrayStoreException
        array[0] = 1;
        // 编译不通过:java中数组和泛型是不能混合使用的,数组要求类型是具象化的,而泛型恰好不是
        // Generic array creation
        List<String>[] genericListArray = new ArrayList<String>[10];

具象化:完全在运行时可用的类型被称为具象化类型(refiable type),有些类型会在编译过程中被擦除而无法在运行时可用
具象化包括:

  • 非泛型类声明、接口类型声明
  • 所有泛型参数类型为无界通配符(仅用?修饰)的泛型参数类
  • 原始类型
  • 基本数据类型
  • 其元素类型为具象化类型的数组
  • 嵌套类(内部类、匿名内部类等),并且嵌套过程中的每个类都是具象化的

泛型擦除
编译器处理带泛型定义的类、接口、方法时,会在字节码指令集中抹去全部泛型类型信息,泛型被擦除后字节码里只保留泛型的原始类型(一般对应的是泛型的定义上界,例如:<T>对应的原始类型是Object,<T extends String>对应是原始类型是String)。类型擦除的关键在于从泛型类型中清除类型参数的相关信息,然后在必要的时候添加类型检查和类型转换的方法。
泛型的类型检查是针对引用的,而不是变量实际指向的对象

泛型擦除带来的问题:

  1. 泛型类型变量不能是基本数据类型
  2. 类型的丢失:泛型对象使用instanceof进行类型判断时不能用具体类型了,只能使用通配符?(list instanceof ArrayList<?>)
  3. catch中不能使用泛型异常类,也不能在catch中使用泛型变量T,由于泛型擦除会导致T被擦除为Throwable,捕获原则是先捕获子类类型异常,再捕获父类类型异常,违背了异常捕获原则,这种写法不被允许
  4. 泛型类的静态方法和属性不能使用泛型

编译器保留的泛型信息:

  • 泛型接口、类、方法定义上的所有泛型
  • 成员变量声明处的泛型
    字节码中指令集之外的地方会保留部分泛型信息,只有局部代码块里的泛型被擦除了

反射获取泛型信息
示例代码

posted @ 2021-02-28 22:07  Abserver  阅读(190)  评论(0)    收藏  举报