Java 泛型知识点
在 Java 编程中,泛型(Generics)是一项至关重要的特性,它从 JDK 5 开始引入,彻底改变了代码的类型安全和复用性。泛型的核心思想是 “参数化类型”,即允许在定义类、接口和方法时,将类型作为参数传入,从而实现代码的通用化,同时在编译阶段就能检测出类型不匹配的错误,避免运行时异常。本文将从基础到进阶,全面拆解 Java 泛型的知识点。
一、泛型的核心价值:为什么需要泛型?
在泛型出现之前,Java 开发者通常使用Object类来实现通用代码。例如,要实现一个存储任意类型数据的集合,会这样写:
// 无泛型时代的集合
List list = new ArrayList();
list.add("Java"); // 存入String
list.add(123); // 存入Integer
list.add(new Object()); // 存入Object// 取值时必须强制类型转换
String str = (String) list.get(0); // 正常转换
Integer num = (Integer) list.get(1); // 正常转换
String error = (String) list.get(2); // 运行时异常:ClassCastException
这种写法存在两大问题:
- 类型不安全:集合可以存入任意类型的数据,编译器无法检查类型,只能在运行时通过强制转换暴露错误,增加了程序崩溃的风险。
- 代码冗余:每次取值都需要手动强制类型转换,代码繁琐且易出错。
泛型的出现正是为了解决这些问题,它带来了三大核心优势:
- 编译期类型检查:在定义集合时指定类型,编译器会自动拦截不符合类型的数据存入,从源头避免类型错误。
- 消除强制类型转换:取值时无需手动转换,编译器会根据泛型参数自动推断类型,代码更简洁。
- 代码复用:一套泛型代码可以适配多种数据类型,无需为不同类型重复编写逻辑(如不同类型的集合、工具类)。
二、泛型的基础语法:如何定义和使用泛型?
泛型的语法核心是 “类型参数”,通常用大写字母表示(如T、E、K、V等,遵循约定俗成的命名:T=Type,E=Element,K=Key,V=Value)。根据使用场景,泛型可分为泛型类、泛型方法和泛型接口三类。
(一)泛型类:将类型参数绑定到类上
泛型类是指在类定义时声明类型参数,使得类中的属性、方法参数或返回值可以使用该类型参数。其语法格式为:
// 定义泛型类:<类型参数> 放在类名后
class 类名<类型参数1, 类型参数2, ...> {// 类型参数可用于属性、方法参数、返回值private 类型参数1 属性名;public 类型参数1 方法名(类型参数2 参数名) {return 属性名;}
}
实例:实现一个通用的 “数据包装类”
假设需要一个类来包装不同类型的数据(如 String、Integer、User 对象),使用泛型类可以一次实现:
// 泛型类:T为类型参数,表示“任意类型”
class DataWrapper<T> {private T data; // 使用T作为属性类型// 构造器:参数类型为Tpublic DataWrapper(T data) {this.data = data;}// 方法:返回值类型为Tpublic T getData() {return data;}// 方法:参数类型为Tpublic void setData(T data) {this.data = data;}// 通用方法:打印数据(无需关心T的具体类型)public void printData() {System.out.println("包装的数据:" + data);}
}// 使用泛型类
public class GenericClassDemo {public static void main(String[] args) {// 1. 包装String类型:指定T为StringDataWrapper<String> stringWrapper = new DataWrapper<>("Hello Generics");String strData = stringWrapper.getData(); // 无需强制转换stringWrapper.printData(); // 输出:包装的数据:Hello Generics// 2. 包装Integer类型:指定T为IntegerDataWrapper<Integer> intWrapper = new DataWrapper<>(100);Integer intData = intWrapper.getData();intWrapper.printData(); // 输出:包装的数据:100// 3. 错误场景:存入不符合类型的数据会编译报错// intWrapper.setData("abc"); // 编译错误:不兼容的类型,无法将String转换为Integer}
}
注意:泛型类在创建对象时必须指定具体类型(如DataWrapper<String>),JDK 7 + 支持 “菱形语法”(new DataWrapper<>()),编译器会自动推断类型参数。
(二)泛型方法:将类型参数绑定到方法上
泛型方法是指在方法定义时单独声明类型参数,即使所在的类不是泛型类,也可以定义泛型方法。其语法格式为:
// 泛型方法:<类型参数> 放在返回值类型前
修饰符 <类型参数1, 类型参数2, ...> 返回值类型 方法名(参数列表) {// 方法体中使用类型参数
}
实例:实现一个通用的 “数组转集合” 方法
不同类型的数组(如String[]、Integer[])需要转换为集合时,泛型方法可以统一处理:
import java.util.ArrayList;
import java.util.List;public class GenericMethodDemo {// 泛型方法:T为类型参数,接收T[]数组,返回List<T>public static <T> List<T> arrayToList(T[] array) {List<T> list = new ArrayList<>();for (T element : array) {list.add(element);}return list;}public static void main(String[] args) {// 1. String数组转List<String>String[] strArray = {"Java", "Generics", "Demo"};List<String> strList = arrayToList(strArray);System.out.println("String列表:" + strList); // 输出:[Java, Generics, Demo]// 2. Integer数组转List<Integer>Integer[] intArray = {1, 2, 3, 4};List<Integer> intList = arrayToList(intArray);System.out.println("Integer列表:" + intList); // 输出:[1, 2, 3, 4]// 3. 自动推断类型:无需显式指定<T>(JDK 7+支持)// 若显式指定,写法为:List<Double> doubleList = GenericMethodDemo.<Double>arrayToList(doubleArray);Double[] doubleArray = {1.1, 2.2};List<Double> doubleList = arrayToList(doubleArray);}
}
泛型方法的关键是类型参数声明在方法返回值之前,这是它与泛型类中方法的核心区别(泛型类的方法使用的是类的类型参数)。
(三)泛型接口:将类型参数绑定到接口上
泛型接口与泛型类类似,在接口定义时声明类型参数,实现接口的类需要指定具体类型,或继续保留类型参数(成为泛型类)。其语法格式为:
// 定义泛型接口:<类型参数> 放在接口名后
interface 接口名<类型参数1, 类型参数2, ...> {// 类型参数可用于方法返回值或参数类型参数1 方法名(类型参数2 参数名);
}
实例:实现一个通用的 “数据处理器” 接口
假设需要处理不同类型的数据(如 String、User),定义泛型接口后,不同实现类可针对性处理:
// 泛型接口:T为待处理的数据类型
interface DataProcessor<T> {// 处理数据的方法:接收T类型,返回T类型(处理后结果)T process(T data);
}// 实现1:处理String类型(转为大写)
class StringProcessor implements DataProcessor<String> {@Overridepublic String process(String data) {return data.toUpperCase(); // 输入"hello",输出"HELLO"}
}// 实现2:处理Integer类型(加10)
class IntegerProcessor implements DataProcessor<Integer> {@Overridepublic Integer process(Integer data) {return data + 10; // 输入20,输出30}
}// 使用泛型接口
public class GenericInterfaceDemo {public static void main(String[] args) {DataProcessor<String> stringProcessor = new StringProcessor();System.out.println(stringProcessor.process("hello")); // 输出:HELLODataProcessor<Integer> integerProcessor = new IntegerProcessor();System.out.println(integerProcessor.process(20)); // 输出:30}
}
注意:若实现类不想指定具体类型,可继续保留泛型参数(如class UserProcessor<T> implements DataProcessor<T>),此时实现类也是泛型类。
三、泛型的进阶特性:边界、通配符与类型擦除
掌握基础语法后,还需要理解泛型的进阶特性,包括泛型边界(限制类型参数的范围)、通配符(灵活处理未知类型)和类型擦除(泛型的底层实现原理)。
(一)泛型边界:限制类型参数的范围
默认情况下,泛型的类型参数可以是任意类(如T可以是String、Integer、User等)。但有时需要限制类型参数的范围(如仅允许 “Number 的子类” 或 “实现了 Serializable 接口的类”),这就需要用到泛型边界,通过extends关键字实现(注意:泛型中extends既表示 “继承类”,也表示 “实现接口”)。
语法格式:
// 单个边界:T必须是BoundClass的子类或实现类
class 类名<T extends BoundClass> { ... }// 多个边界:T必须同时满足所有边界(类在前,接口在后,用&分隔)
class 类名<T extends BoundClass & BoundInterface1 & BoundInterface2> { ... }
实例 1:限制类型参数为 “Number 的子类”
假设需要一个计算 “数值总和” 的工具类,仅允许处理数字类型(如 Integer、Double),可限制T extends Number:
// 泛型类:T必须是Number的子类(如Integer、Double)
class NumberSumCalculator<T extends Number> {private List<T> numbers;public NumberSumCalculator(List<T> numbers) {this.numbers = numbers;}// 计算总和:利用Number的doubleValue()方法(所有子类都实现了该方法)public double calculateSum() {double sum = 0.0;for (T num : numbers) {sum += num.doubleValue(); // 若没有边界限制,num无法调用doubleValue()}return sum;}
}// 使用
public class GenericBoundDemo1 {public static void main(String[] args) {// 1. 处理Integer列表List<Integer> intList = List.of(1, 2, 3);NumberSumCalculator<Integer> intCalculator = new NumberSumCalculator<>(intList);System.out.println("Integer总和:" + intCalculator.calculateSum()); // 输出:6.0// 2. 处理Double列表List<Double> doubleList = List.of(1.5, 2.5, 3.5);NumberSumCalculator<Double> doubleCalculator = new NumberSumCalculator<>(doubleList);System.out.println("Double总和:" + doubleCalculator.calculateSum()); // 输出:7.5// 3. 错误场景:传入非Number类型(如String)会编译报错// List<String> strList = List.of("1", "2");// NumberSumCalculator<String> strCalculator = new NumberSumCalculator<>(strList); // 编译错误}
}
实例 2:多个边界(类 + 接口)
限制类型参数同时是 “User 的子类” 且 “实现了 Comparable 接口”:
class User {private String name;// 省略构造器、getter、setter
}// 泛型类:T必须是User的子类,且实现Comparable接口
class UserSorter<T extends User & Comparable<T>> {public void sort(List<T> users) {users.sort((u1, u2) -> u1.compareTo(u2)); // 因T实现Comparable,可调用compareTo()}
}
(二)通配符:灵活处理未知的泛型类型
当需要处理 “未知类型的泛型” 时(如接收任意类型的List),可以使用通配符(?)。通配符分为三种:无界通配符、上界通配符、下界通配符。
1. 无界通配符(?):表示 “任意类型”
适用于仅需要读取泛型对象,且不关心具体类型的场景(如打印任意List的内容)。
// 无界通配符:接收任意类型的List
public static void printList(List<?> list) {for (Object obj : list) {System.out.print(obj + " ");}System.out.println();
}// 使用
public class WildcardDemo1 {public static void main(String[] args) {List<String> strList = List.of("A", "B", "C");List<Integer> intList = List.of(1, 2, 3);List<User> userList = List.of(new User("Alice"), new User("Bob"));// 同一方法处理不同类型的ListprintList(strList); // 输出:A B C printList(intList); // 输出:1 2 3 printList(userList); // 输出:User{name='Alice'} User{name='Bob'} }
}
注意:无界通配符的List<?>与List<Object>不同:
- List<Object>可以存入任意类型的数据(如list.add("a")、list.add(1));
- List<?>表示 “未知类型的 List”,编译器无法确定类型,因此不允许添加非 null 的数据(仅允许list.add(null)),只能读取(读取到的是Object类型)。
2. 上界通配符(? extends 边界):表示 “边界的子类或实现类”
适用于需要读取泛型对象,且类型范围有限制的场景(如读取 “Number 子类的 List”)。
// 上界通配符:接收Number子类的List(如Integer、Double)
public static double sumNumberList(List<? extends Number> list) {double sum = 0.0;for (Number num : list) {sum += num.doubleValue(); // 因类型是Number子类,可调用doubleValue()}return sum;
}// 使用
public class WildcardDemo2 {public static void main(String[] args) {List<Integer> intList = List.of(1, 2, 3);List<Double> doubleList = List.of(1.5, 2.5);System.out.println(sumNumberList(intList)); // 输出:6.0System.out.println(sumNumberList(doubleList));// 输出:4.0// 错误场景:传入非Number子类的List(如String)会编译报错// List<String> strList = List.of("1", "2");// sumNumberList(strList); // 编译错误}
}
注意:上界通配符List<? extends Number>同样不允许添加数据(例如,无法确定 List 是Integer还是Double,添加1或1.5都可能出错),仅支持读取。
3. 下界通配符(? super 边界):表示 “边界的父类或超类”
适用于需要向泛型对象中添加数据,且类型范围有限制的场景(如向 “Integer 父类的 List” 中添加 Integer)。
// 下界通配符:接收Integer父类的List(如Integer、Number、Object)
public static void addIntegers(List<? super Integer> list) {list.add(10); // 允许添加Integer类型(因父类List可接收子类对象)
</doubaocanvas>