Java中的泛型(Generics)是一种支持泛型编程的工具,它允许程序员在编译时提供类型信息,从而提高代码的复用性和安全性。泛型的应用统一了数据类型,把运行时期的问题提前到了编译期间,避免了强制类型转换可能出现的异常,应为在编译阶段类型就能确定下来。

泛型中的细节

  • 泛型中不能写基本数据类型(int,float,double,boolean,byte,char,short,long)
  • 指定泛型的具体类型后,传递数据时,可以传入该类型和他的子类类型
  • 如果不写泛型,类型默认是Object

泛型的种类

  1. 泛型类:

允许你定义一个类,该类可以操作任意类型的数据,而不需要在代码中使用具体的类型

1
2
3
4
5
6
7
8
9
public class Box<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
}
  1. 泛型接口:

允许你定义一个接口,该接口可以操作任意类型的数据

1
2
3
public interface Generate<T> {
T next();
}
  1. 泛型方法:

允许你在方法级别指定泛型类型,而不是在类级别

1
2
3
4
5
public <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}
  1. 泛型数组:

Java不允许直接创建泛型数组,但是可以通过数组实例化来间接创建

1
Integer[] intArray = (Integer[]) new Number[10];

泛型的继承和通配符

  • 泛型不具备继承性,但是数据具有继承性

假设我们有一个List<Person>,但我们不能将他赋值给List<Child>类型,即便Child继承了Person。这是因为List<Person>List<Child>是两个不同的泛型类型,并且在泛型系统中不具备继承关系。

1
2
3
List<Parent> parents = new ArrayList<>();
List<Child> children = new ArrayList<>();
parents = children; // 错误:编译不通过

即便泛型类型不支持直接的继承关系,泛型中实际的元素依然可以通过父类来引用。这依赖于对象的继承关系,而不是泛型类型的继承关系。假设你有一个List<Parent>和一个List<Child>,虽然它们的类型不具备继承性,但你可以将Child对象当作Parent来处理。

1
2
3
List<Parent> parentList = new ArrayList<>();
parentList.add(new Parent());
parentList.add(new Child()); // 因为 Child 是 Parent 的子类,数据具有继承性

协变与泛型的关系

在一些编程语言(如 C# 和 Kotlin)中,协变(Covariance)概念部分缓解了这个限制。它允许在泛型接口或类的某些上下文中,使List<Child>可以被视为List<Parent>。不过,Java 并不允许泛型的协变和逆变直接应用于容器类型,但可以通过通配符(Wildcard)如List<? extends Parent>来实现一定的协变效果。

  • 泛型的通配符:允许我们指定一个类型可以是任意类型或者任意类型的子类型

    • ?
    • ? extends E
    • ? super E

使用场景

  • 定义类,方法,接口的时候,如果类型不能确定,就可以定义泛型
  • 如果类型不确定,但是能知道是哪个继承体系中的,可以使用泛型的通配符