JAVA|泛型
1. 泛型的作用
- 把运行时异常提前到编译期:避免程序运行时出现类型转换异常(ClassCastException),在编译阶段就检查类型是否匹配,提升程序安全性。
- 示例:未使用泛型时,List 可存储任意类型元素,取出时需强制转换,若类型不匹配,运行时报错;使用泛型后,编译时就限制只能存储指定类型元素,避免转换异常。
- 避免强制类型转换:泛型会自动推断元素类型,取出元素时无需手动强制转换,简化代码,提升可读性和开发效率。
- 统一数据类型:明确集合或类中存储的元素类型,规范代码,减少类型混乱,便于团队协作和后期维护。
示例对比(未使用泛型 vs 使用泛型):
未使用泛型:
ArrayList list = new ArrayList();
list.add(“abc”);
list.add(123); // 可存储任意类型
String s = (String) list.get(0); // 需强制转换,可能报错
使用泛型:
ArrayList list = new ArrayList<>();
list.add(“abc”);
// list2.add(123); // 编译报错:不允许存储非String类型
String s = list.get(0); // 无需转换,编译期检查类型
2. 泛型的核心规则
- 泛型只能使用引用类型,不能使用基本数据类型(如 int、char、double 等)。
- 正确写法:ArrayList list = new ArrayList<>();(使用包装类)
- 错误写法:ArrayList list = new ArrayList<>();(编译报错)
- 泛型不具备继承性:即使 A 是 B 的子类,List 也不是 List 的子类,不能相互赋值。
- 错误示例:ArrayList intList = new ArrayList<>(); ArrayList numList = intList; // 编译报错
- 编译后泛型会被类型擦除:JVM 运行时,泛型信息会被擦除,只保留原始类型(如 List 擦除后变为 List),泛型仅在编译阶段起作用。
- 注意:不能通过 instanceof 判断泛型类型(如 if (list instanceof List) 编译报错),只能判断原始类型(if (list instanceof List))。
- 泛型参数可以自定义标识符:常用 T(Type)、E(Element)、K(Key)、V(Value),标识符无关紧要,仅代表“占位符”,规范使用可提升可读性。
3. 泛型的分类
3.1 泛型类
-
定义:在类的声明时,指定泛型参数,该泛型参数可在类的成员变量、方法中使用,使类可以适配多种数据类型,实现类的复用。
-
语法格式:
// 类名后加 <泛型参数>,可多个参数(如 <T, K>) class 类名<T> { // 泛型成员变量 private T data; // 泛型方法(非静态) public T getData() { return data; } public void setData(T data) { this.data = data; } } -
使用方式:创建类的对象时,指定泛型的具体类型(若不指定,默认是 Object 类型,失去泛型作用)。
// 定义泛型类 class MyGenericClass<T> { private T data; public T getData() { return data; } public void setData(T data) { this.data = data; } // 静态方法不能使用类的泛型参数 // public static T getStaticData() { return data; } // 编译报错 } // 测试泛型类 public class GenericClassDemo { public static void main(String[] args) { // 指定T为String类型 MyGenericClass<String> strObj = new MyGenericClass<>(); strObj.setData("Hello 泛型"); String strData = strObj.getData(); System.out.println(strData); // 输出:Hello 泛型 // 指定T为Integer类型 MyGenericClass<Integer> intObj = new MyGenericClass<>(); intObj.setData(12345); Integer intData = intObj.getData(); System.out.println(intData); // 输出:12345 } } -
注意:静态方法中不能使用类的泛型参数(因为静态方法属于类,类加载时泛型参数还未指定具体类型)。
3.2 泛型方法
-
定义:在方法的声明时,单独指定泛型参数,该泛型参数仅作用于当前方法,与类的泛型无关(可用于普通类或泛型类)。
-
语法格式:
// 方法返回值前加 <泛型参数>,声明该方法是泛型方法 public <T> T 方法名(T t) { // 方法体,可使用泛型参数 T return t; } -
使用方式:调用方法时,无需手动指定泛型类型,JVM 会自动根据传入的参数类型推断泛型类型。
// 调用泛型方法,传入 String 类型,自动推断 T 为 String String str = genericMethod("abc"); // 传入 Integer 类型,自动推断 T 为 Integer Integer num = genericMethod(123);` -
优势:灵活度高,一个方法可处理多种类型的参数,无需重载多个方法。
3.3 泛型接口
-
定义:在接口的声明时,指定泛型参数,该泛型参数可在接口的方法中使用,使接口可以适配多种数据类型。
-
语法格式:
// 接口名后加 <泛型参数> interface 接口名<T> { // 泛型抽象方法 T method(T t); } -
实现方式(两种):
-
实现接口时,指定泛型的具体类型:
// 实现接口时,指定 T 为 String 类型 class MyImpl implements MyGenericInterface<String> { @Override public String method(String t) { return t; } } -
实现接口时,不指定泛型类型,将泛型传递给实现类(实现类变为泛型类):
// 实现类保留泛型参数 T,与接口泛型一致 class MyImpl<T> implements MyGenericInterface<T> { @Override public T method(T t) { return t; } } // 使用时指定泛型类型 MyImpl<Integer> impl = new MyImpl<>(); impl.method(123);
-
4. 泛型通配符
4.1 核心作用:
当需要接收多种泛型类型的集合(或对象)时,使用通配符 ? 表示“任意类型”,解决泛型不具备继承性的痛点。
4.2 三种通配符类型:
-
无界通配符 ?:表示任意类型,可接收任何泛型类型的对象,相当于
? extends Object。- 示例:public void printList(List<?> list) { … } // 可接收 List、List 等任意 List
- 注意:无界通配符的集合,只能读取元素(读取为 Object 类型),不能添加元素(无法确定添加的元素类型是否匹配)。
-
上界通配符?extends E:表示 “E 及其子类”,只能读取 E 类型或其子类型的元素,不能添加元素(除了 null)。
- 核心场景:适用于 “只读” 场景,限制集合的元素类型为 E 或其子类。
- 示例:
List<? extends Number>可接收List<Integer>、List<Double>等,但不能添加任何非 null 元素。
-
下界通配符?super E:表示 “E 及其父类”,可以添加 E 类型或其子类型的元素,读取时只能按 Object 类型读取。
- 核心场景:适用于 “只写” 场景,限制集合的元素类型为 E 或其父类。
- 示例:
List<? super Integer>可接收List<Number>、List<Object>,可添加 Integer 类型元素。
4.3 代码示例:
import java.util.ArrayList;
import java.util.List;
public class GenericWildcardDemo {
public static void main(String[] args) {
// 准备测试数据
List<String> strList = new ArrayList<>();
strList.add("Java");
strList.add("泛型");
List<Integer> intList = new ArrayList<>();
intList.add(10);
intList.add(20);
List<Number> numList = new ArrayList<>();
numList.add(3.14);
numList.add(100);
// 1. 无界通配符测试
printAnyList(strList); // 输出:Java 泛型
printAnyList(intList); // 输出:10 20
// 2. 上界通配符测试(? extends Number)
printNumberList(intList); // 输出:数字元素:10 数字元素:20
printNumberList(numList); // 输出:数字元素:3.14 数字元素:100
// printNumberList(strList); // 编译报错:String不是Number的子类
// 3. 下界通配符测试(? super Integer)
addInteger(numList); // 向numList添加Integer元素
System.out.println(numList); // 输出:[3.14, 100, 999]
List<Object> objList = new ArrayList<>();
addInteger(objList); // 向objList添加Integer元素
System.out.println(objList); // 输出:[999]
}
// 1. 无界通配符:接收任意类型的List
public static void printAnyList(List<?> list) {
for (Object obj : list) {
System.out.print(obj + " ");
}
System.out.println();
// list.add("test"); // 编译报错:无界通配符不能添加元素(除null)
list.add(null); // 唯一允许的添加操作
}
// 2. 上界通配符:只能接收Number及其子类的List
public static void printNumberList(List<? extends Number> list) {
for (Number num : list) {
System.out.println("数字元素:" + num);
}
// list.add(10); // 编译报错:上界通配符不能添加元素(除null)
}
// 3. 下界通配符:只能接收Integer及其父类的List
public static void addInteger(List<? super Integer> list) {
list.add(999); // 可添加Integer类型元素
// list.add(3.14); // 编译报错:Double不是Integer的子类
// 读取时只能按Object类型
for (Object obj : list) {
System.out.println("读取元素:" + obj);
}
}
}
更多推荐



所有评论(0)