Java 泛型
2022/1/1约 1247 字大约 4 分钟
Java 泛型
Java 泛型是 Java 5 引入的一种机制,允许在类、接口和方法中使用类型参数,从而提高代码的类型安全性和可读性。通过泛型,可以实现一种“编译时的类型检查”,避免运行时的类型转换错误。
为什么需要泛型?
在没有泛型的情况下,集合等数据结构中存储的对象类型不确定,通常使用 Object
类型。这种方式会导致两个问题:
- 类型不安全:可以存储任何对象,可能在操作时出现错误。
- 类型转换麻烦:取出元素时需要手动强制类型转换。
// 没有泛型的代码
import java.util.ArrayList;
public class NoGenericsDemo {
public static void main(String[] args) {
ArrayList list = new ArrayList(); // 没有指定类型的集合
list.add("Hello");
list.add(123); // 运行时不会报错
for (Object obj : list) {
// 必须手动强制转换,容易出错
String str = (String) obj; // 运行时会抛出 ClassCastException
System.out.println(str);
}
}
}
泛型的基本用法
泛型通过在代码中显式指定类型,解决了上述问题。
import java.util.ArrayList;
public class GenericsDemo {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>(); // 指定集合中只能存储 String 类型
list.add("Hello");
// list.add(123); // 编译时就会报错,防止类型错误
for (String str : list) {
// 不需要强制转换
System.out.println(str);
}
}
}
什么是类型参数?
类型参数是 Java 泛型中用于表示数据类型的“占位符”。它是泛型的核心概念之一,允许程序员在编写类、接口或方法时暂时不指定数据类型,而在实际使用时再确定具体的数据类型。
类型参数的作用
- 占位符作用:
类型参数相当于一个“变量”,它代表某种未知的数据类型。编写代码时,类型参数用来定义某些类或方法中可以操作的类型,但并不具体说明是Integer
、String
还是其他类型。 - 提升代码灵活性和通用性:
使用类型参数后,代码可以处理多种数据类型,而无需为每种类型分别编写代码。 - 提供类型安全性:
类型参数允许在编译阶段检查类型,防止类型不匹配的错误。
类型参数的形式
类型参数通常用大写字母表示,以区分于普通变量或类型名称。以下是一些常见的类型参数符号及其约定:
符号 | 意义 |
---|---|
T | Type,表示一般的类型 |
E | Element,表示集合中的元素类型 |
K | Key,表示键的类型 |
V | Value,表示值的类型 |
N | Number,表示数字类型 |
当然,类型参数的名字没有固定规则,也可以使用其他名称(如 A
、B
),但遵守这些约定能提高代码的可读性。
泛型的定义
1. 泛型类
定义格式:
class 类名<类型参数> {
// 类的成员可以使用类型参数
}
使用格式:
类名<具体类型> 对象名 = new 类名<>();
示例:
class Box<T> { // T 是类型参数
private T item;
public void set(T item) {
this.item = item;
}
public T get() {
return item;
}
}
// 使用
Box<String> stringBox = new Box<>();
stringBox.set("Hello");
System.out.println(stringBox.get()); // 输出:Hello
2. 泛型接口
定义格式:
interface 接口名<类型参数> {
// 接口的方法可以使用类型参数
}
使用格式:
- 实现接口时可以指定具体类型,也可以保留类型参数(即实现泛型接口的类仍是泛型类)。
示例:
interface Pair<K, V> {
K getKey();
V getValue();
}
class PairImpl<K, V> implements Pair<K, V> { // 泛型类
private K key;
private V value;
public PairImpl(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
}
// 使用
Pair<String, Integer> pair = new PairImpl<>("Age", 25);
System.out.println(pair.getKey() + ": " + pair.getValue()); // 输出:Age: 25
3. 泛型方法
定义格式:
<类型参数> 返回类型 方法名(参数列表) {
// 方法体可以使用类型参数
}
使用格式:
- 调用泛型方法时可以显式指定类型,也可以让编译器推断类型。
示例:
public class GenericsExample {
public static <T> void printArray(T[] array) { // 泛型方法
for (T element : array) {
System.out.println(element);
}
}
public static void main(String[] args) {
String[] strArray = {"Hello", "World"};
Integer[] intArray = {1, 2, 3};
printArray(strArray); // 推断 T 为 String
printArray(intArray); // 推断 T 为 Integer
}
}
4. 通配符的使用
通配符格式:
无界通配符:
?
表示任意类型。List<?> list;
上界通配符:
? extends 类型
表示该泛型的类型是指定类型或其子类。List<? extends Number> list;
下界通配符:
? super 类型
表示该泛型的类型是指定类型或其父类。List<? super Integer> list;
示例:
public void printList(List<?> list) { // 无界通配符
for (Object obj : list) {
System.out.println(obj);
}
}
public void processNumbers(List<? extends Number> list) { // 上界通配符
for (Number num : list) {
System.out.println(num.doubleValue());
}
}
public void addIntegers(List<? super Integer> list) { // 下界通配符
list.add(10); // 只能添加 Integer 或其子类
}