Java 泛型

泛型产生场景

开发一个能够存储各种对象的容器,如果用 Object 进行存储,显示转换的时候可能出现异常。

泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

定义 Listlist = new ArrayList(),那么 list 只能添加 String 类型的元素

泛型使用场景

  • 泛型类

类后面添加类型参数

1
2
public class Box<T> {
}

支持多个类型参数

1
2
public class MultiGenericContainer<T, S> {
}

  • 泛型接口
1
2
3
4
5
6
7
8
9
10
11
12
public interface Generator<T> {
public T next();
}

public class FruitGenerator implements Generator<String> {
private String[] fruits = new String[]{"Apple", "Banana", "Pear"};
@Override
public String next() {
Random rand = new Random();
return fruits[rand.nextInt(3)];
}
}
  • 泛型方法

方法调用时可以接收不同类型的参数,编译期根据不同的参数类型进行对应处理

1
2
3
4
5
6
public static < E > void printArray( E[] inputArray ){
// 输出数组元素
for ( E element : inputArray ){
System.out.printf( "%s ", element );
}
}

泛型通配符

  • 限定通配符:

    • E:元素
    • K:键
    • N:数字
    • T:类型
    • V:值
    • S、U、V 等:多参数情况中的第 2、3、4 个类型
  • 非限定通配符: ?

    某些情况下,编写指定未知类型的代码很有用。问号 (?) 通配符可用于使用泛型代码表示未知类型。通配符可用于参数、字段、局部变量和返回类型。但最好不要在返回类型中使用通配符,因为确切知道方法返回的类型更安全。

泛型通配符使用注意

将指定的泛型类型控制为指定的类型,需要使用上下界限定符 extends 和 super

< T extends UpperBoundType> list:list 中元素类型必须是 UpperBoundType 类或者是 UpperBoundType 的子类

< T super LowerBoundType> list:list 中元素类型必须是 LowerBoundType 或者是 LowerBoundType 的父类

1
2
3
4
5
6
7
8
9
Class Fruit{}
Class Apple extends Fruit{}
Class Orange extends Fruit{}
public class Plate<T> {
private List<T> list;
public Plate(){}
public void add(T item){list.add(item);}
public T get(){return list.get(0);}
}
  1. Java 并不支持泛型的向上转型,下面写法将不会通过编译
1
Plate<Fruit> plate = new Plate<Apple>();  //Error

下面写法可以通过编译,即 plate 可以指向 Fruit 类的对象或者 Fruit 子类对象

1
Plate<? extends Fruit> plate = new Plate<Apple>();

  1. extends 只能读取对象,不能添加对象

因为 plate 指向 Fruit 类的对象或者 Fruit 子类对象,但是 Fruit 的子类不一定只有 Apple,还有可能是 Orange ,添加的元素不能确定是哪种具体类型。

但是 get 的时候,可以知道 get 到的元素都可以转为 Fruit 类型,所以可以读取对象

1
2
3
4
5
Plate<? extends Fruit> plate = new Plate<Apple>();
plate.add(new Apple()); // Compile Error

Apple apple = plate.get(); // Compile Error
Fruit apple = plate.get(); // SUCCESS
  1. super 只能添加对象,不能读取对象

plate 指向 Apple 类的对象或者 Apple 父类的对象,Fruit 是 Apple 的父类,所以添加对象是可行的。

但是 get 的时候,不清楚 get 到的元素具体类型,有可能是 Apple 有可能是 Fruit,更有可能是 Fruit 的父类,只能使用 Object 去 get ,才能编译不报错。

1
2
3
4
5
6
7
Plate<? super Apple> plate = new Plate<Fruit>();
plate.add(new Apple()); // SUCCESS
plate.add(new Fruit()); // Compile Error

Apple apple = plate.get(); // Compile Error
Fruit fruit = plate.get(); // Compile Error
Object object = plate.get(); // SUCCESS

泛型擦除

1
2
3
4
5
6
7
ArrayList<String> a = new ArrayList<String>();
ArrayList<Integer> b = new ArrayList<Integer>();
Class c1 = a.getClass();
Class c2 = b.getClass();
System.out.println(c1 == c2);

程序输出: true

泛型只存在于编译阶段,在编译阶段会进行泛型检查,检查元素是否满足类型要求,不满足时编译将不通过;

在编译后的 class 文件中,进行了泛型擦除,c1,c2 都是 ArrayList.class,不存在泛型概念。

参考

大白话说Java泛型:入门、使用、原理

深入理解Java泛型