Java泛型详解:尖括号<>、通配符?与类型参数T
下面这个表格汇总了它们的核心区别,帮助您快速建立整体印象。
| 特性 | 尖括号 <> |
通配符 ? |
类型参数 T |
|---|---|---|---|
| 角色定位 | 语法符号:声明和使用泛型的标识。 | 类型实参 :在代码中表示一个未知的、具体的类型。 | 类型形参 :在定义 泛型类、接口、方法时使用的占位符。 |
| 主要用途 | 1. 定义泛型类/接口/方法。 2. 指定具体类型参数(泛型类型的实例化)。 | 1. 定义接收未知类型的方法参数,增强API灵活性。 2. 实现泛型类型的协变 (<? extends T>)与逆变 (<? super T>)。 |
在泛型类、接口或方法内部,作为一个具体的类型来使用,可用于声明变量、参数、返回类型,保证类型一致性。 |
| 核心要义 | "我是泛型"的声明符。 | "我不关心具体类型"的灵活符 ,主要用于使用端(方法参数、局部变量)。 | "一个待定的具体类型"的占位符 ,主要用于定义端(泛型类、泛型方法)。 |
📐 尖括号 <>:泛型的基石
尖括号 <> 是泛型最直观的语法符号,用于参数化类型。
- 声明泛型 :在定义类、接口或方法时,在名称后使用
<>来声明一个或多个类型参数。 - 使用泛型 :在实例化泛型类或调用泛型方法时,在名称后使用
<>来传入具体的类型参数。
java
// 1. 声明一个泛型类
public class Box<T> { // <T> 声明了一个类型参数 T
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
// 2. 使用泛型类,为类型参数 T 指定具体的类型 String
Box<String> stringBox = new Box<>(); // 尖括号内指定具体类型
stringBox.setContent("Hello, Generics!");
String value = stringBox.getContent(); // 无需强制类型转换,类型安全
// 3. 泛型方法:<T> 位于返回值之前
public <T> T getFirstElement(List<T> list) {
return list.get(0);
}
❓ 通配符 ?:灵活的未知类型
通配符 ? 用于表示未知类型 。它主要在使用泛型(尤其是作为方法参数)时提供灵活性,允许你接受更广泛的类型。它有三种形式:
1. 无界通配符 <?>
表示可以匹配任何类型。
java
// 可以接受任何类型的List,但无法向list中添加非null元素
public void printList(List<?> list) {
for (Object elem : list) { // 读取出的元素是Object类型
System.out.println(elem);
}
// list.add(new Object()); // 编译错误!不知道具体类型,添加不安全
list.add(null); // 这是唯一允许的添加操作
}
2. 上界通配符 <? extends T>
表示匹配 T 类型或其子类型 (T 可以是类或接口)。它使得泛型具有协变性。
java
// 可以接受List<Number>, List<Integer>, List<Double>等
public double sumOfList(List<? extends Number> list) {
double sum = 0.0;
for (Number num : list) { // 可以安全地读取为Number
sum += num.doubleValue();
}
// list.add(new Integer(1)); // 编译错误!可能是List<Double>,添加Integer不安全
return sum;
}
3. 下界通配符 <? super T>
表示匹配 T 类型或其父类型 ,直至 Object。它使得泛型具有逆变性。
java
// 可以接受List<Object>, List<Number>, List<Integer>(如果T是Integer)
public void addNumbers(List<? super Integer> list) {
list.add(1); // 可以安全地添加Integer及其子类
list.add(2);
// Integer num = list.get(0); // 编译错误!读取出的类型不确定,只能是Object
Object obj = list.get(0); // 可以读取为Object
}
🔤 类型参数 T,E,K,V:泛型的占位符
T 是最常用的类型参数占位符,通常代表 Type。
命名规范(虽非强制,但强烈建议遵循以增强可读性):
T:代表 Type,通用的任意类型。E:代表 Element,常用于集合中的元素类型。K,V:代表 Key 和 Value,常用于映射(键值对)。N:代表 Number,常用于数值类型。S,U:当需要多个类型参数时,可作为第二个、第三个占位符。
java
// 泛型类定义
public class Container<T> { // T 是类型参数
private T value;
public T getValue() { return value; }
public void setValue(T value) { this.value = value; }
}
// 泛型方法定义
public <T> T getFirst(List<T> list) { // 方法自己的类型参数 T
return list.get(0);
}
// 使用
Container<String> stringContainer = new Container<>(); // T 被绑定为 String
Integer first = getFirst(Arrays.asList(1, 2, 3)); // T 被推断为 Integer
💡 核心辨析:T 与 ? 的根本区别
这是理解泛型的关键。虽然都表示"类型",但它们的角色和用途有本质不同:
| 方面 | 类型参数 T |
通配符 ? |
|---|---|---|
| 角色 | 定义者 :用于定义泛型类、接口、方法。 | 使用者 :用于使用泛型结构,作为参数、变量类型。 |
| 可操作性 | 你可以将 T 当作一个具体的类型来使用(声明变量、返回值、调用方法)。 |
你不能用 ? 来声明变量类型(如 ? x; 是错误的),它只是一个占位符。 |
| 类型一致性 | 可以确保多个地方类型一致 。例如:<T> void test(List<T> dest, List<T> src) 保证两个List的元素类型相同。 |
无法保证类型一致性。例如:void test(List<?> dest, List<?> src) 两个List的元素类型可以不同。 |
| 多重限定 | 支持:<T extends Number & Comparable<T>>。 |
不支持。 |
| 下界限定 | 不支持 T super SomeClass。 |
支持 ? super SomeClass。 |
简单总结 :T 用于"定义什么类型",而 ? 用于"接受未知类型"。
💎 总结与最佳实践
- 语法基础 :尖括号
<>是泛型的语法标志。 - 定义泛型 :当需要定义 一个可重用的泛型类、接口或方法时,使用类型参数
T(或E,K,V)。它提供了类型安全和一致性。 - 使用泛型 :当需要使用 泛型结构,且方法的逻辑不依赖于具体类型 时,使用通配符
?(尤其是上界<? extends T>和下界<? super T>),这能极大提高API的灵活性。记住 PECS 原则。 - 避免使用原始类型 :不要使用没有类型参数的泛型类(如
List list),这会失去类型安全优势。# Java泛型详解:尖括号<>、通配符?与类型参数T
下面这个表格汇总了它们的核心区别,帮助您快速建立整体印象。
| 特性 | 尖括号 <> |
通配符 ? |
类型参数 T |
|---|---|---|---|
| 角色定位 | 语法符号:声明和使用泛型的标识。 | 类型实参 :在代码中表示一个未知的、具体的类型。 | 类型形参 :在定义 泛型类、接口、方法时使用的占位符。 |
| 主要用途 | 1. 定义泛型类/接口/方法。 2. 指定具体类型参数(泛型类型的实例化)。 | 1. 定义接收未知类型的方法参数,增强API灵活性。 2. 实现泛型类型的协变 (<? extends T>)与逆变 (<? super T>)。 |
在泛型类、接口或方法内部,作为一个具体的类型来使用,可用于声明变量、参数、返回类型,保证类型一致性。 |
| 核心要义 | "我是泛型"的声明符。 | "我不关心具体类型"的灵活符 ,主要用于使用端(方法参数、局部变量)。 | "一个待定的具体类型"的占位符 ,主要用于定义端(泛型类、泛型方法)。 |
📐 尖括号 <>:泛型的基石
尖括号 <> 是泛型最直观的语法符号,用于参数化类型。
- 声明泛型 :在定义类、接口或方法时,在名称后使用
<>来声明一个或多个类型参数。 - 使用泛型 :在实例化泛型类或调用泛型方法时,在名称后使用
<>来传入具体的类型参数。
java
// 1. 声明一个泛型类
public class Box<T> { // <T> 声明了一个类型参数 T
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
// 2. 使用泛型类,为类型参数 T 指定具体的类型 String
Box<String> stringBox = new Box<>(); // 尖括号内指定具体类型
stringBox.setContent("Hello, Generics!");
String value = stringBox.getContent(); // 无需强制类型转换,类型安全
// 3. 泛型方法:<T> 位于返回值之前
public <T> T getFirstElement(List<T> list) {
return list.get(0);
}
❓ 通配符 ?:灵活的未知类型
通配符 ? 用于表示未知类型 。它主要在使用泛型(尤其是作为方法参数)时提供灵活性,允许你接受更广泛的类型。它有三种形式:
1. 无界通配符 <?>
表示可以匹配任何类型。
java
// 可以接受任何类型的List,但无法向list中添加非null元素
public void printList(List<?> list) {
for (Object elem : list) { // 读取出的元素是Object类型
System.out.println(elem);
}
// list.add(new Object()); // 编译错误!不知道具体类型,添加不安全
list.add(null); // 这是唯一允许的添加操作
}
2. 上界通配符 <? extends T>
表示匹配 T 类型或其子类型 (T 可以是类或接口)。它使得泛型具有协变性。
java
// 可以接受List<Number>, List<Integer>, List<Double>等
public double sumOfList(List<? extends Number> list) {
double sum = 0.0;
for (Number num : list) { // 可以安全地读取为Number
sum += num.doubleValue();
}
// list.add(new Integer(1)); // 编译错误!可能是List<Double>,添加Integer不安全
return sum;
}
3. 下界通配符 <? super T>
表示匹配 T 类型或其父类型 ,直至 Object。它使得泛型具有逆变性。
java
// 可以接受List<Object>, List<Number>, List<Integer>(如果T是Integer)
public void addNumbers(List<? super Integer> list) {
list.add(1); // 可以安全地添加Integer及其子类
list.add(2);
// Integer num = list.get(0); // 编译错误!读取出的类型不确定,只能是Object
Object obj = list.get(0); // 可以读取为Object
}
🔤 类型参数 T,E,K,V:泛型的占位符
T 是最常用的类型参数占位符,通常代表 Type。
命名规范(虽非强制,但强烈建议遵循以增强可读性):
T:代表 Type,通用的任意类型。E:代表 Element,常用于集合中的元素类型。K,V:代表 Key 和 Value,常用于映射(键值对)。N:代表 Number,常用于数值类型。S,U:当需要多个类型参数时,可作为第二个、第三个占位符。
java
// 泛型类定义
public class Container<T> { // T 是类型参数
private T value;
public T getValue() { return value; }
public void setValue(T value) { this.value = value; }
}
// 泛型方法定义
public <T> T getFirst(List<T> list) { // 方法自己的类型参数 T
return list.get(0);
}
// 使用
Container<String> stringContainer = new Container<>(); // T 被绑定为 String
Integer first = getFirst(Arrays.asList(1, 2, 3)); // T 被推断为 Integer
💡 核心辨析:T 与 ? 的根本区别
这是理解泛型的关键。虽然都表示"类型",但它们的角色和用途有本质不同:
| 方面 | 类型参数 T |
通配符 ? |
|---|---|---|
| 角色 | 定义者 :用于定义泛型类、接口、方法。 | 使用者 :用于使用泛型结构,作为参数、变量类型。 |
| 可操作性 | 你可以将 T 当作一个具体的类型来使用(声明变量、返回值、调用方法)。 |
你不能用 ? 来声明变量类型(如 ? x; 是错误的),它只是一个占位符。 |
| 类型一致性 | 可以确保多个地方类型一致 。例如:<T> void test(List<T> dest, List<T> src) 保证两个List的元素类型相同。 |
无法保证类型一致性。例如:void test(List<?> dest, List<?> src) 两个List的元素类型可以不同。 |
| 多重限定 | 支持:<T extends Number & Comparable<T>>。 |
不支持。 |
| 下界限定 | 不支持 T super SomeClass。 |
支持 ? super SomeClass。 |
简单总结 :T 用于"定义什么类型",而 ? 用于"接受未知类型"。
💎 总结与最佳实践
- 语法基础 :尖括号
<>是泛型的语法标志。 - 定义泛型 :当需要定义 一个可重用的泛型类、接口或方法时,使用类型参数
T(或E,K,V)。它提供了类型安全和一致性。 - 使用泛型 :当需要使用 泛型结构,且方法的逻辑不依赖于具体类型 时,使用通配符
?(尤其是上界<? extends T>和下界<? super T>),这能极大提高API的灵活性。记住 PECS 原则。 - 避免使用原始类型 :不要使用没有类型参数的泛型类(如
List list),这会失去类型安全优势。