Java 的泛型在英文里表示为 Generics。
一、泛型的概念与作用
泛型(Generics)是 Java 5 引入的一种类型安全机制,它允许在定义类、接口或方法时使用类型参数(Type Parameters)通过参数化类型(Parameterized Type)来指定操作的数据类型。通过泛型,集合类可以明确指定其存储元素的类型,从而在编译期就进行类型检查,避免运行时的ClassCastException
异常。
二、背景:Java 泛型出现前的痛点
JAVA推出泛型以前,程序员可以构建一个元素类型为Object的集合,该集合能够存储任意的数据类型对象,而在使用该集合的过程中,需要程序员明确知道存储每个元素的数据类型,否则很容易引发ClassCastException异常。
在 Java 5 之前,集合类(如 ArrayList
)通过 Object
类型存储元素。由于 Object
是所有类的父类,集合可以存储任意类型的对象。但这种设计存在以下问题:
- 类型不安全:集合中可能混入不同类型的对象,取出时需要手动进行强制类型转换,若转换错误会引发
ClassCastException
。 - 编译器无法检查类型:编译阶段无法检测到类型错误,错误只能在运行时暴露。
- 代码可读性差:集合中存储的元素类型不明确,开发者需要手动维护类型信息。
1.示例代码(无泛型时的写法):
package Generics;
import java.util.ArrayList;
public class Generics_test {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("java");
list.add(100);
list.add(true);
for (int i = 0; i < list.size(); i++) {
Object o = list.get(i);
String str=(String)o;
System.out.println(str);
}
}
}
运行结果:
看下面图,可以知道在编译时,代码不会报错
2.示例代码(无泛型时的写法):
import java.util.ArrayList;
public class NonGenericExample {
public static void main(String[] args) {
ArrayList list = new ArrayList(); // 使用原始类型(无泛型)
// 向集合中添加不同类型的数据
list.add("Hello"); // String 类型
list.add(123); // Integer 类型
// 取出元素时需强制类型转换
String str = (String) list.get(0); // 正确
int num = (int) list.get(1); // 正确
// 如果误操作,可能会引发 ClassCastException
Object obj = list.get(1);
String errorStr = (String) obj; // 运行时抛出 ClassCastException
}
}
运行结果:
问题分析:
list
中存储了String
和Integer
两种类型的数据。- 在取出元素时,若开发者误将
Integer
转换为String
,程序在运行时会抛出ClassCastException
。 - 编译器无法在编译阶段发现这种错误,只能等到运行时才能暴露问题。
三、泛型的解决方案
引入泛型后,集合类可以通过指定类型参数(如 <String>
)来限制集合中存储的数据类型。这样,编译器会在编译阶段检查类型安全性,避免运行时错误。
泛型作用:
1.编译期间检查类型
2.减少了数据类型转换
1.示例代码(使用泛型):
这个例子对应上面(二.1)
package Generics;
import java.util.ArrayList;
public class Generics_test {
public static void main(String[] args) {
ArrayList<String> strList = new ArrayList<>();
strList.add("a");
strList.add("b");
strList.add("c");
for (int i = 0; i < strList.size(); i++) {
String s = strList.get(i);
System.out.println(s);
}
}
}
运行结果:
2.示例代码(使用泛型):
这个例子对应上面(二.2)
package Generics;
import java.util.ArrayList;
public class GenericExample {
public static void main(String[] args) {
// 使用泛型指定集合只能存储 String 类型
ArrayList<String> list = new ArrayList<>();
// 编译器会阻止插入非 String 类型的元素
list.add("Hello"); // 正确
// list.add(123); // 编译报错:类型不匹配
// 取出元素时无需强制类型转换
String str = list.get(0); // 直接获取 String 类型
System.out.println(str); // 输出: Hello
}
}
运行结果:
四、常用类型参数通配符
符号 | 含义 | 示例场景 |
---|---|---|
E | Element(元素) | 集合框架(如 List<E> 、Set<E> )中表示元素类型。 |
T | Type(类型) | 泛型类 / 方法的通用类型参数(如 Box<T> )。 |
K | Key(键) | Map<K, V> 中的键类型。 |
V | Value(值) | Map<K, V> 中的值类型。 |
N | Number(数值) | 限制为 Number 及其子类(如 Integer 、Double )。 |
? | 未知类型(通配符) | 表示不确定的类型(如 List<?> 可接受任意类型的列表)。 |
S , U , V | 多类型参数 | 用于需要多个泛型类型的场景(如 Pair<T, U> )。 |
1.补充说明
-
?
(通配符):
用于泛型方法或变量声明中,表示未知类型。例如:
public void printList(List<?> list) { ... } // 可接受任意类型的 List
-
上限通配符
<? extends T>
:
表示类型必须是T
或其子类(如List<? extends Number>
可接受List<Integer>
)。 -
下限通配符
<? super T>
:
表示类型必须是T
或其父类(如List<? super Integer>
可接受List<Number>
)。 -
多类型参数:
例如,Map.Entry<K, V>
定义了键值对的泛型类型,Pair<T, U>
可用于存储两个不同类型的值。
2. 常见错误示例
-
错误用法:
List<?> list = new ArrayList<>();
list.add("apple"); // 错误!无法向 `List<?>` 添加元素(类型不确定)
这里补条链接
- 正确用法:
List<String> list = new ArrayList<>();
list.add("apple"); // 正确,明确指定类型为 String
3.注意:其实什么字母都可以
Java 泛型中的类型参数名称(如 T
、E
、K
)只是约定俗成的标识符,实际上可以使用任何合法的标识符(如 A
、MyType
等)。不过,遵循约定能提高代码的可读性。以下是详细说明:
⑴.类型参数名称的合法性
Java 泛型中,类型参数名称可以是任意合法的标识符,但通常使用单个大写字母(如 T
、E
、K
)或简短的大写单词缩写(如 KEY
、VALUE
)。例如:
// 合法但不推荐(可读性差)
public class Box<X> { ... }
// 合法且常用(遵循约定)
public class Box<T> { ... }
⑵.为什么需要约定?
- 提高可读性:看到
E
能立刻联想到集合元素,K
和V
对应键值对。 - 避免混淆:如果使用复杂名称(如
MyCustomType
),会让泛型代码显得冗长。
示例对比
不遵循约定(合法但难读)
public class Pair<FirstType, SecondType> {
private FirstType first;
private SecondType second;
// ...
}
遵循约定(简洁清晰)
public class Pair<T, U> {
private T first;
private U second;
// ...
}
⑶.多类型参数的命名
当泛型类 / 方法需要多个类型参数时,推荐使用有意义的字母或缩写:
// 合法且清晰
public class Triple<F, S, T> {
private F first;
private S second;
private T third;
}
// 也可以使用完整单词(不推荐,过于冗长)
public class Triple<FirstElement, SecondElement, ThirdElement> { ... }