基本信息
Java泛型(Generics)是Java 5引入的一项重要特性,它允许在定义类、接口和方法时使用类型参数,从而提高代码的复用性、类型安全性和可读性。
- 泛型的基本概念
泛型的核心思想是参数化类型,即在定义类、接口或方法时,使用一个或多个类型参数来代替具体的类型。这些类型参数在使用时可以被指定为具体的类型。
- 泛型的特点
(1)类型安全
-
泛型在编译时进行类型检查,避免了运行时的类型转换错误。
-
例如,使用
List<String>
时,只能添加String
类型的对象,如果尝试添加其他类型,编译器会报错。
(2)代码复用
-
泛型允许编写通用的代码,可以适用于多种类型。
-
例如,一个
List<T>
可以用于存储String
、Integer
等任意类型的对象。
(3)消除强制类型转换
- 使用泛型后,从集合中获取元素时不需要显式地进行类型转换。
cpp
List<String> list = new ArrayList<>();
list.add("Java");
String s = list.get(0); // 无需强制类型转换
(4)通配符和边界
-
Java泛型支持通配符(
?
)和边界(extends
和super
),用于增强泛型的灵活性。-
<? extends T>
:表示类型参数是T
或其子类。 -
<? super T>
:表示类型参数是T
或其父类。
-
cpp
public void printList(List<? extends Number> list) {
for (Number n : list) {
System.out.println(n);
}
}
(5)类型擦除
-
Java泛型在编译后会进行类型擦除 ,即泛型信息在运行时会被擦除,替换为限定类型或原始类型(通常是
Object
)。 -
这是为了兼容Java 5之前的代码。
java
List<String> list = new ArrayList<>();
// 编译后,List<String>会被擦除为List<Object>
泛型的继承规则
泛型类或接口可以像普通类一样被继承或实现,但需要注意类型参数的处理。
1. 泛型类/接口的继承
(1)继承泛型类
- 子类可以继承父类的泛型类型参数,也可以指定具体的类型。
java
class Box<T> {
private T value;
public void setValue(T value) { this.value = value; }
public T getValue() { return value; }
}
// 子类继承泛型类型参数
class StringBox<T> extends Box<T> {
// 可以使用父类的泛型类型参数
}
// 子类指定具体类型
class IntegerBox extends Box<Integer> {
// 父类的T被固定为Integer
}
(2)接口类似
2. 泛型类型的继承关系
泛型类型的继承关系与普通类型的继承关系不同,主要体现在以下方面:
(1)泛型类型之间没有继承关系
-
即使类型参数之间有继承关系,泛型类型本身也没有继承关系。
-
示例:
javaList<String> list1 = new ArrayList<>(); List<Object> list2 = list1; // 错误!List<String>不是List<Object>的子类
- 虽然
String
是Object
的子类,但List<String>
并不是List<Object>
的子类。
- 虽然
(2)通配符的继承关系
-
使用通配符(
?
)可以表示泛型类型的继承关系。-
<? extends T>
:表示类型参数是T
或其子类。 -
<? super T>
:表示类型参数是T
或其父类。
-
-
示例:
javaList<? extends Number> numbers = new ArrayList<Integer>(); // 正确 List<? super Integer> objects = new ArrayList<Number>(); // 正确
3. 泛型继承规则总结
-
泛型类/接口可以继承或实现,子类可以指定具体类型或保留泛型类型参数。
-
泛型类型之间没有继承关系,即使类型参数之间有继承关系。
-
通配符(
?
)可以表示泛型类型的继承关系:-
<? extends T>
:表示T
或其子类。 -
<? super T>
:表示T
或其父类。
-
-
泛型方法可以继承或重写,但必须保持方法签名一致。
-
泛型数组的继承受限制,通常使用通配符或类型擦除来处理。
反射与泛型
在 Java 中,java.lang.reflect.Type
是一个表示类型(如类、泛型、数组等)的接口,它是 Java 反射机制中用于描述类型信息的核心接口之一。Type
接口及其子接口提供了对 Java 类型系统的全面支持,尤其是在处理泛型时非常有用。
Type
接口本身是一个标记接口(没有方法),它的具体实现由以下几个子接口和类完成:
1. Type
接口的子接口和实现类
Type
接口有四个直接子接口和一个实现类:
(1) Class<T>
-
作用:表示一个具体的类或接口类型。
-
特点:
-
是
Type
接口的唯一直接实现类。 -
可以表示普通类、接口、数组类型和基本类型。
-
通过
Class<T>
可以获取类的元信息(如类名、方法、字段等)。
-
-
示例:
javaClass<String> stringClass = String.class; System.out.println(stringClass.getName()); // 输出 "java.lang.String"
(2) ParameterizedType
-
作用 :表示参数化类型(泛型类型),例如
List<String>
。 -
方法:
-
Type[] getActualTypeArguments()
:返回泛型参数的实际类型(如String
)。 -
Type getRawType()
:返回原始类型(如List
)。 -
Type getOwnerType()
:返回外部类的类型(如果是内部类)。
-
-
示例:
javaList<String> list = new ArrayList<>(); Type type = list.getClass().getGenericSuperclass(); // 获取 ArrayList 的父类类型 if (type instanceof ParameterizedType) { ParameterizedType pType = (ParameterizedType) type; System.out.println(pType.getRawType()); // 输出 "java.util.AbstractList" for (Type arg : pType.getActualTypeArguments()) { System.out.println(arg); // 输出 "E"(泛型参数) } }
(3) TypeVariable<D>
-
作用 :表示泛型类型变量(如
T
、E
等)。 -
方法:
-
Type[] getBounds()
:返回类型变量的上界(如T extends Number
)。 -
D getGenericDeclaration()
:返回声明该类型变量的泛型声明(如类或方法)。 -
String getName()
:返回类型变量的名称(如T
)。
-
-
示例:
javaclass MyClass<T extends Number> { T value; } TypeVariable<?>[] typeVariables = MyClass.class.getTypeParameters(); for (TypeVariable<?> tv : typeVariables) { System.out.println(tv.getName()); // 输出 "T" for (Type bound : tv.getBounds()) { System.out.println(bound); // 输出 "java.lang.Number" } }
(4) GenericArrayType
-
作用 :表示泛型数组类型(如
T[]
)。 -
方法:
Type getGenericComponentType()
:返回数组的组件类型(如T
)。
-
示例:
javaList<String>[] array; Type type = array.getClass().getGenericSuperclass(); if (type instanceof GenericArrayType) { GenericArrayType gaType = (GenericArrayType) type; System.out.println(gaType.getGenericComponentType()); // 输出 "java.util.List<java.lang.String>" }
(5) WildcardType
-
作用 :表示通配符类型(如
?
、? extends Number
、? super Integer
)。 -
方法:
-
Type[] getUpperBounds()
:返回通配符的上界(如Number
)。 -
Type[] getLowerBounds()
:返回通配符的下界(如Integer
)。
-
-
示例:
javaType type = MyClass.class.getGenericSuperclass(); if (type instanceof ParameterizedType) { ParameterizedType pType = (ParameterizedType) type; for (Type arg : pType.getActualTypeArguments()) { if (arg instanceof WildcardType) { WildcardType wType = (WildcardType) arg; for (Type upperBound : wType.getUpperBounds()) { System.out.println(upperBound); // 输出上界 } for (Type lowerBound : wType.getLowerBounds()) { System.out.println(lowerBound); // 输出下界 } } } }
2. Type
与 Class<T>
的关系
-
Class<T>
是Type
的实现类:-
Class<T>
是Type
接口的唯一直接实现类。 -
Class<T>
主要用于表示具体的类或接口类型,而Type
接口及其子接口用于表示更复杂的类型(如泛型、数组、通配符等)。
-
-
Class<T>
的局限性:-
Class<T>
只能表示具体的类型,无法直接表示泛型类型(如List<String>
)。 -
如果需要处理泛型类型,必须使用
Type
接口及其子接口(如ParameterizedType
)。
-
-
Type
的扩展性:-
Type
接口及其子接口提供了对 Java 类型系统的完整描述,包括泛型、数组、通配符等。 -
通过
Type
接口,可以在运行时获取泛型类型的具体信息。
-
3. 使用场景
-
反射 :通过
Type
接口及其子接口,可以在运行时获取泛型类型的具体信息。 -
序列化/反序列化:在处理泛型对象时,需要获取泛型类型信息。
-
框架开发 :许多框架(如 Spring、Gson)使用
Type
接口来处理泛型类型。
4. 示例:获取泛型类型信息
java
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
class MyGenericClass<T> {
private List<T> list;
public MyGenericClass() {
Type type = getClass().getGenericSuperclass();
if (type instanceof ParameterizedType) {
ParameterizedType pType = (ParameterizedType) type;
Type[] actualTypes = pType.getActualTypeArguments();
for (Type actualType : actualTypes) {
System.out.println(actualType); // 输出泛型参数类型
}
}
}
}
public class Main {
public static void main(String[] args) {
new MyGenericClass<String>() {}; // 输出 "class java.lang.String"
}
}
假设有一个类层次结构如下:
java
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
通配符的超类型(上限)限定
语法**<? extends T>,**
含义 :表示类型是 T
或其子类型。
特点:
-
只能从中读取数据 ,且读取的数据类型是
T
。 -
不能向其中写入数据 (除了
null
),因为具体类型未知。
你需要一个方法,能够处理 Animal
及其子类型的列表,并读取其中的数据:
java
public void printAnimals(List<? extends Animal> animals) {
for (Animal animal : animals) {
System.out.println(animal);
}
}
java
List<Dog> dogs = new ArrayList<>();
dogs.add(new Dog());
dogs.add(new Dog());
List<Cat> cats = new ArrayList<>();
cats.add(new Cat());
cats.add(new Cat());
printAnimals(dogs); // 可以处理 Dog 列表
printAnimals(cats); // 可以处理 Cat 列表
为什么用 <? extends T>
:
-
方法可以接受
Animal
及其子类型的列表(如List<Dog>
或List<Cat>
)。 -
从列表中读取的数据可以安全地视为
Animal
类型。
不允许写入元素
java
// Cat 是 Animal子类
List<? extends Animal> pets = new ArrayList<>();
pets.add(new Cat()); // 编译错误
pets.add(new Dog()); // 编译错误
pets.add(new Animal()); // 编译错误
pets.add(null); // 允许,因为 null 是所有引用类型的有效值
原因在于 List<? extends Animal>
使用了 通配符的超类型限定(Upper Bounded Wildcard) ,这种限定方式在泛型中限制了集合的写入操作。List<? extends Animal>
表示这是一个可以存储 Animal
或其子类型(如 Cat
、Dog
)的列表,但具体是哪种子类型是未知的。编译器无法确定 pets
的实际类型是 List<Cat>
、List<Dog>
还是 List<Animal>
,因此为了保证类型安全,编译器会禁止向这种列表中添加任何非 null
的元素。
具体原因:
-
类型不确定性:
-
List<? extends Animal>
可能是List<Cat>
、List<Dog>
或List<Animal>
。 -
如果你尝试添加一个
Cat
,而pets
实际上是List<Dog>
,就会导致类型不匹配,破坏类型安全。
-
-
编译器的保护机制:
- 为了避免运行时出现
ClassCastException
,编译器直接禁止向List<? extends Animal>
中添加任何元素(除了null
)。
- 为了避免运行时出现
如果你需要向列表中添加元素,应该使用 确定的具体类型 或者 通配符的下限限定(Lower Bounded Wildcard)。
- 使用具体类型:
如果你知道列表的具体类型是 Cat
,可以直接使用 List<Cat>
:
java
List<Cat> cats = new ArrayList<>();
cats.add(new Cat()); // 允许
- 使用下限限定(
<? super T>
):
如果你希望列表可以接受 Animal
及其父类型(如 Object
),可以使用 List<? super Cat>
:
java
List<? super Cat> pets = new ArrayList<>();
pets.add(new Cat()); // 允许,因为 Cat 是 Animal 的子类型
pets.add(new Animal()); // 编译错误,因为 Animal 是 Cat 的父类型
通配符的子类型(下限)限定
语法<? super T>
含义 :表示类型是 T
或其父类型。
特点:
-
可以向其中写入
T
类型的数据。 -
只能从中读取
Object
类型的数据,因为具体类型未知。
使用场景 :
当你需要向一个集合中写入数据,并且希望代码能够处理 T
及其父类型的集合时。
java
public void addDog(List<? super Dog> dogs) {
dogs.add(new Dog()); // 可以安全地添加 Dog 对象
}
java
List<Animal> animals = new ArrayList<>();
List<Dog> dogs = new ArrayList<>();
List<Object> objects = new ArrayList<>();
addDog(animals); // 可以处理 Animal 列表
addDog(dogs); // 可以处理 Dog 列表
addDog(objects); // 可以处理 Object 列表
为什么用 <? super T>
:
-
方法可以接受
Dog
及其父类型的列表(如List<Animal>
或List<Object>
)。 -
可以向列表中添加
Dog
类型的对象。
虽然允许写入元素,但不允许写入T的父类元素
java
public void addDog(List<? super Dog> dogs) {
dogs.add(new Dog()); // 安全,因为 Dog 是 ? super Dog 的子类型
dogs.add(new Animal()); // 编译错误,因为 Animal 是 Dog 的父类型
}
在Java泛型中,List<? super Dog>
表示一个可以接受 Dog
类型或其任何父类型的列表。虽然这种通配符限定允许你向列表中添加 Dog
类型的对象,但它并不允许添加 Dog
的父类型对象(例如 Animal
)。这就是为什么在 dogs.add(new Animal());
这行代码会报错。
原因分析
-
类型安全性:
-
List<? super Dog>
表示列表的元素类型是Dog
或其父类型(例如Animal
或Object
)。 -
你可以向这样的列表中添加
Dog
类型的对象,因为Dog
是? super Dog
的子类型,符合类型安全。 -
但是,你不能添加
Animal
类型的对象,因为Animal
是Dog
的父类型,而? super Dog
并不保证列表的具体类型是Animal
。列表的实际类型可能是Dog
或Object
,因此添加Animal
可能会导致类型不安全。
-
-
编译器限制:
-
编译器无法确定
List<? super Dog>
的具体类型是什么。它只知道这个列表可以接受Dog
或其父类型的对象。 -
由于
Animal
是Dog
的父类型,编译器无法保证Animal
类型的对象可以安全地添加到列表中。因此,编译器会报错以避免潜在的类型安全问题。
-
对比超类型限定和子类型限定
特性 | 超类型限定 (<? extends T> ) |
子类型限定 (<? super T> ) |
---|---|---|
类型范围 | T 或其子类型 |
T 或其父类型 |
读取数据 | 安全,可以视为 T 类型 |
不安全,只能视为 Object |
写入数据 | 不安全,只能添加 null |
安全,可以添加 T 类型 |
适用场景 | 需要从集合中读取数据 | 需要向集合中写入数据 |
<T extends Comparable<? super T>>
和 <T extends Comparable>比较
在大多数情况下,推荐使用 <T extends Comparable<? super T>>
,因为它提供了更大的灵活性和类型安全性。
-
<T extends Comparable<? super T>>
:更灵活,允许T
与其父类进行比较。 -
<T extends Comparable>
:限制更严格,要求T
直接实现Comparable
。
相同点
-
类型约束 :两者都要求
T
实现Comparable
接口。 -
泛型使用 :都用于限制泛型类型参数
T
的范围。
不同点
-
灵活性:
-
<T extends Comparable<? super T>>
:允许T
实现Comparable
接口,且Comparable
的类型参数可以是T
或其父类。这意味着T
可以与自身或其父类进行比较,提供了更大的灵活性。 -
<T extends Comparable>
:要求T
直接实现Comparable
接口,且类型参数必须是T
自身,限制更严格。
-
-
类型安全性:
-
<T extends Comparable<? super T>>
:更灵活且类型安全,允许T
与其父类进行比较,减少了类型转换的需要。 -
<T extends Comparable>
:类型安全性较低,可能需要进行类型转换,增加了运行时错误的风险。
-
例如以下场景,假设两个类
java
class Animal implements Comparable<Animal> {
public int compareTo(Animal other) {
// 实现比较逻辑
return 0;
}
}
class Dog extends Animal {
// Dog 继承了 Animal 的 compareTo 方法
}
对于<T extends Comparable<? super T>>
:
java
public static <T extends Comparable<? super T>> void sort(List<T> list) {
// 可以接受 List<Animal> 或 List<Dog>
}
这里 T
可以是 Animal
或 Dog
,因为 Dog
继承了 Animal
的 compareTo
方法。
对于<T extends Comparable>
:
java
public static <T extends Comparable> void sort(List<T> list) {
// 只能接受 List<Animal>,不能接受 List<Dog>
}
这里 T
必须是直接实现 Comparable
的类型,如 Animal
,而不能是 Dog
,因为 Dog
没有直接实现 Comparable
。
无界通配符
通配符捕获(Wildcard Capture)
Java 泛型中的一个概念,用于处理泛型类型中通配符(?
)的具体类型推断问题。通配符本身表示未知类型,但在某些情况下,编译器需要推断出通配符的具体类型以便进行类型安全的操作。这种推断过程称为"通配符捕获"。
- 背景
通配符类型(如 List<?>
)本身是只读的,不能直接修改。如果需要修改通配符类型的对象,必须通过通配符捕获来推断出具体类型,从而保证类型安全。
- 示例
java
public void swap(List<?> list, int i, int j) {
// 通配符捕获:编译器推断出通配符的具体类型
swapHelper(list, i, j);
}
// 辅助方法:用于捕获通配符的具体类型
private <T> void swapHelper(List<T> list, int i, int j) {
T temp = list.get(i);
list.set(i, list.get(j));
list.set(j, temp);
}
解释:
-
swap
方法:-
参数
List<?>
是一个通配符类型,表示未知类型的列表。 -
由于
List<?>
不能直接修改(只能读取为Object
),我们需要一个辅助方法来处理具体类型。
-
-
swapHelper
方法:-
这是一个泛型方法,类型参数
T
被用来捕获List<?>
中的具体类型。 -
编译器会推断出
T
是List<?>
中实际的元素类型,从而允许安全地操作列表。
-
-
通配符捕获:
-
在调用
swapHelper
时,编译器会"捕获"List<?>
中的具体类型,并将其作为T
传递给swapHelper
。 -
这样,
swapHelper
就可以安全地操作列表元素。
-
List
和 List<?>的区别
1. List
(原始类型,Raw Type)
-
定义 :
List
是一个没有指定类型参数的泛型类,称为原始类型(Raw Type)。 -
特点:
-
可以存储任意类型的对象(类似于
List<Object>
)。 -
失去了泛型的类型检查,编译器不会对元素的类型进行约束。
-
使用时需要手动进行类型转换,容易引发运行时异常(如
ClassCastException
)。
-
-
**如下,**类型不安全,容易引发运行时错误
javaList list = new ArrayList(); list.add("Hello"); list.add(123); // 可以存储任意类型 String str = (String) list.get(0); // 需要强制类型转换 Integer num = (Integer) list.get(1); // 需要强制类型转换
2. List<?>
(无界通配符类型,Unbounded Wildcard)
-
定义 :
List<?>
表示一个未知类型的泛型列表,称为无界通配符类型。 -
特点:
-
可以接受任何类型的
List
,例如List<String>
、List<Integer>
等。 -
不能向其中添加任何元素(除了
null
),因为类型未知。 -
只能从中读取元素,且读取的元素类型是
Object
。
-
-
如下, 通常用于编写通用方法,可以接受任意类型的
List
java
List<?> list = new ArrayList<String>();
list.add(null); // 只能添加 null
Object obj = list.get(0); // 读取的元素类型是 Object