什么是泛型

泛型即参数化类型,将类型由原来的具体类型参数化,类似方法中的变量参数。

提供编译期的安全检测机制,JDK 5 引入

泛型通配符

  • 限定通配符:
  • E:元素
  • K:键
  • N:数字
  • T:类型
  • V:值
  • S、U、V 等:多参数情况中的第 2、3、4 个类型
  • 非限定通配符: ?
    某些情况下,编写指定未知类型的代码很有用。问号 (?) 通配符可用于使用泛型代码表示未知类型。通配符可用于参数、字段、局部变量和返回类型。但最好不要在返回类型中使用通配符,因为确切知道方法返回的类型更安全。

泛型通配符

? extends T

描述了通配符的上界,即具体的泛型参数必须是 T 类型或者 T 的子类型

List<? extends Number> numberArray = new ArrayList<Number>();  // Number 是 Number 类型的
List<? extends Number> numberArray = new ArrayList<Integer>(); // Integer 是 Number 的子类
List<? extends Number> numberArray = new ArrayList<Double>();  // Double 是 Number 的子类

读取:numberArray 中可以读到 Number 对象,因为里面存放的元素必须是 Number or Number 的子类型

写入:不能写入,因为 numberArray 可以是子类型的任一类型,编译器不知道写入哪种,Double 类型的元素不能写入到 ```List``

? super T

描述了通配符的下届,即具体的泛型参数必须是 T 类型或者它的父类

// 在这里, Integer 可以认为是 Integer 的 "父类"
List<? super Integer> array = new ArrayList<Integer>();
// Number 是 Integer 的 父类
List<? super Integer> array = new ArrayList<Number>();
// Object 是 Integer 的 父类
List<? super Integer> array = new ArrayList<Object>();

读取:只能保证从 array 读取到 Object 实例,并不能知道具体是哪个实例

写入:可以添加 Integer 或者 Integer 的子类对象到 array 中,若 T 是 Integer,那么可以无障碍地添加 Integer 或者 Integer 子类的对象到容器中,因为对象都可以向上转型为 Integer。若 T 是 Integer 的父类,那么添加的类型都可以向上转型为 Integer 的父类到容器中。

List<?>List<Object>区别

  • List,原始类型,其引用变量可以添加任意类型的元素,但是编译器很难发现错误,不建议使用。
  • List<Object>表示可以容纳任意类型的对象,万事万物皆为 Object
  • List<?>无限定通配符类型,只能包含某一种未知对象类型,?表示未知类型的泛型,它只有读的能力,并没有写的能力,因为编译器不知道放哪个子类型到集合中。
public void test(List<?> list) {
      Object o = list.get(0);
    //编译器不允许该操作
   // list.add("jaljal");
}

泛型类

用于类的定义,通过泛型完成一组类的操作,并对外开放相同的接口,参考 容器类。

在实例化 Test 的时候,若指定 T 的具体类型那么该类型不能是基本数据类型

public class Generic<T> {

    private T key;

    public Generic(T key){
        this.key = key;
    }

    public T getKey(){
        return key;
    }
}

Generic generic = new Generic(123);
System.out.println(generic.getKey());
Generic generic1 = new Generic<Integer>(123);
System.out.println(generic.getKey());

泛型接口

与泛型类定义基本相同,泛型接口的实现类可以是普通的类,也可以是泛型类。

普通类必须定义的时候指定参数类型;泛型类实现泛型接口的时候,需要声明泛型参数

// 定义泛型接口
public interface Test<T> {
    public T next();
}

// 普通类 指定参数类型
public class TestA implements Test<String>{

    @Override
    public String get() {
        return null;
    }

    @Override
    public void set(String s) {

    }
}

// 泛型类 传递参数类型
public class TestB<T> implements Test<T>{

    private T t;

    public TestB(T t){
        set(t);
    }

    @Override
    public T get() {
        return t;
    }

    @Override
    public void set(T t) {
        this.t = t;
    }
}

// 也可声明多个参数
public class TestC<T,K,V> implements Test<T> {

    private T t;
    private K k;
    private V v;
    
    public TestC(T t,K k,V v){
        this.t = t;
        this.k = k;
        this.v = v;
    }
    
    @Override
    public T get() {
        return t;
    }

    @Override
    public void set(T t) {
        this.t = t;
    }
}

泛型方法

使用规则

  • 在方法返回值前面,声明 类型参数,用尖括号括起来,将表示这是一个泛型方法
  • 类型参数只能是引用类型,不能是基本数据类型
public static < E > void printArray( E[] inputArray ){}
public <E, C extends E> void testDependys(E e, C c) {}  // 第二个参数必须是 E 类型或者 E 的子类型

若静态方法要使用泛型,那么静态方法必须为泛型方法

public static <T> void show(T t){}

泛型擦除

泛型值存在于编译期,代码在进入虚拟机后泛型就会会被擦除掉,这个者特性就叫做类型擦除。

当泛型被擦除后,他有两种转换方式,第一种是如果泛型没有设置类型上限,那么将泛型转化成Object 类型。

下面所示,get,set 方法是 Object,在操作的时候会 checkcast

public class ManipulatorA <T>{
    private T t;
    public void setT(T t) {
        this.t = t;
    }
    public T getT() {
        return t;
    }
    public static void main(String[] args) {
        ManipulatorA<String> manipulatorA = new ManipulatorA<>();
        manipulatorA.setT("123");
        System.out.println(manipulatorA.getT());
    }
}

Compiled from "ManipulatorA.java"
public class fanxing.ManipulatorA<T> {
  public fanxing.ManipulatorA();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public void setT(T);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #2                  // Field t:Ljava/lang/Object;
       5: return

  public T getT();
    Code:
       0: aload_0
       1: getfield      #2                  // Field t:Ljava/lang/Object;
       4: areturn

  public static void main(java.lang.String[]);
    Code:
       0: new           #3                  // class fanxing/ManipulatorA
       3: dup
       4: invokespecial #4                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: ldc           #5                  // String 123
      11: invokevirtual #6                  // Method setT:(Ljava/lang/Object;)V
      14: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
      17: aload_1
      18: invokevirtual #8                  // Method getT:()Ljava/lang/Object;
      21: checkcast     #9                  // class java/lang/String
      24: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      27: return
}

第二种是如果设置了类型上限,那么将泛型转化成他的类型上限。下面的例子就只会擦除到 HasF 类型。

如下所示,get,set 都是 Number 类型,也不用转换

public class Manipulator <T extends Number>{

    private T t;

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }

    public static void main(String[] args) {
        Manipulator manipulator = new Manipulator();
        manipulator.setT(123);
        System.out.println(manipulator.getT());
    }
}

Compiled from "Manipulator.java"
public class fanxing.Manipulator<T extends java.lang.Number> {
  public fanxing.Manipulator();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public T getT();
    Code:
       0: aload_0
       1: getfield      #2                  // Field t:Ljava/lang/Number;
       4: areturn

  public void setT(T);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #2                  // Field t:Ljava/lang/Number;
       5: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #3                  // class fanxing/Manipulator
       3: dup
       4: invokespecial #4                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: bipush        123
      11: invokestatic  #5                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      14: invokevirtual #6                  // Method setT:(Ljava/lang/Number;)V
      17: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
      20: aload_1
      21: invokevirtual #8                  // Method getT:()Ljava/lang/Number;
      24: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
      27: return
}