Java SE 泛型原理、语法与实践指南
作者:观望过往
泛型(Generics)是 Java SE 5 引入的核心特性,其本质是 “参数化类型”—— 允许在定义类、接口、方法时指定 “类型参数”,在使用时再明确具体类型(如List<String>、Map<Integer, User>)。它像 “类型的占位符”,既能保证编译时的类型安全(避免ClassCastException),又能减少重复代码(实现通用逻辑复用),是集合框架、工具类、框架开发的基础。
一、泛型的核心价值:为什么需要泛型?
在泛型出现之前,Java 通过Object类型实现 “通用” 逻辑,但存在类型不安全和代码冗余两大问题。泛型的核心目标就是解决这两个痛点。
1. 无泛型的痛点:以集合为例
(1)类型不安全(运行时抛异常)
ArrayList在 JDK 1.4 及以前的实现中,内部用Object[]存储元素,添加元素时不限制类型,取出时需强制转换,若类型不匹配,编译时无提示,运行时抛ClassCastException:
// 无泛型:ArrayList存储Object,可添加任意类型
import java.util.ArrayList;
public class NoGenericDemo {
public static void main(String\[] args) {
ArrayList list = new ArrayList();
list.add("Java"); // 添加String
list.add(123); // 添加Integer(编译不报错,隐藏风险)
list.add(new Object()); // 添加Object
// 取出元素:需强制转换为String,运行时出错
for (int i = 0; i < list.size(); i++) {
String str = (String) list.get(i); // 第2个元素是Integer,运行抛ClassCastException
System.out.println(str.length());
}
}
}问题本质:编译阶段无法校验元素类型,错误延迟到运行时,风险不可控。
(2)代码冗余(重复强制转换)
即使存储的是同一类型,每次取出都需强制转换,代码繁琐:
ArrayList list = new ArrayList();
list.add("Apple");
list.add("Banana");
// 每次取出都需强制转换为String,冗余且易出错
String apple = (String) list.get(0);
String banana = (String) list.get(1);2. 泛型的解决方案:类型参数化
通过泛型指定 “元素类型”,编译阶段即可校验类型合法性,避免强制转换:
// 有泛型:ArrayList\<String>指定元素只能是String
import java.util.ArrayList;
public class GenericDemo {
public static void main(String\[] args) {
ArrayList\<String> list = new ArrayList<>(); // 菱形语法,JDK 7+可省略右侧类型
list.add("Java"); // 合法:String类型
// list.add(123); // 编译报错:不允许添加Integer,类型安全校验
// list.add(new Object()); // 编译报错:类型不匹配
// 取出元素:无需强制转换,直接是String类型
for (String str : list) {
System.out.println(str.length()); // 安全调用String方法
}
}
}3. 泛型的核心价值总结
| 核心价值 | 说明 | 示例 |
|---|---|---|
| 编译时类型安全 | 限制集合 / 对象的元素类型,不允许添加不匹配类型,提前暴露错误 | ArrayList<String>不允许添加 Integer |
| 消除强制转换 | 使用时无需手动转换类型,代码简洁且避免ClassCastException | String str = list.get(0)(无需强转) |
| 代码复用 | 一套通用逻辑适配多种类型,无需为每种类型写重复代码(如泛型工具类) | GenericUtil.swap<T>(T[] arr, int i, int j)适配所有数组类型 |
| 清晰的代码意图 | 类型参数明确告知数据类型,代码可读性更高 | Map<Long, User>明确键是 Long,值是 User |
二、泛型的基本语法:类、接口、方法
泛型的使用场景分为三类:泛型类(含枚举)、泛型接口、泛型方法,每种场景的语法略有差异,但核心都是 “定义类型参数,使用时指定具体类型”。
1. 泛型类:类定义时指定类型参数
泛型类是 “包含类型参数的类”,在创建对象时需明确具体类型(如List<String>、HashMap<K, V>)。
(1)语法格式
// 定义泛型类:\<T1, T2, ...>中T1、T2是类型参数(占位符)
class 类名\<T1, T2, ...> {
// 1. 用类型参数定义成员变量
private T1 field1;
private T2 field2;
// 2. 用类型参数定义构造方法
public 类名(T1 field1, T2 field2) {
this.field1 = field1;
this.field2 = field2;
}
// 3. 用类型参数定义方法的返回值或参数
public T1 getField1() {
return field1;
}
public void setField2(T2 field2) {
this.field2 = field2;
}
}(2)类型参数命名规范(约定俗成)
为提高可读性,类型参数通常用单个大写字母表示,常见约定:
T:Type(通用类型,最常用);E:Element(集合元素类型,如List<E>);K:Key(键类型,如Map<K, V>);V:Value(值类型,如Map<K, V>);N:Number(数值类型,如Integer、Double);S、U、V:多个类型参数时的后续占位符(如Class<S, U>)。
(3)代码示例:自定义泛型类(Pair)
// 泛型类:存储一对不同类型的值(如键值对、名称-年龄等)
class Pair\<K, V> {
private K key;
private V value;
// 泛型构造方法
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
// 泛型方法(返回值为类型参数)
public K getKey() {
return key;
}
public V getValue() {
return value;
}
// 泛型方法(参数为类型参数)
public void setValue(V value) {
this.value = value;
}
// 普通方法:打印键值对
@Override
public String toString() {
return "Pair{" + "key=" + key + ", value=" + value + "}";
}
}
// 使用泛型类
public class GenericClassDemo {
public static void main(String\[] args) {
// 1. 创建Pair\<String, Integer>对象:key是String,value是Integer
Pair\<String, Integer> userPair = new Pair<>("张三", 20);
String userName = userPair.getKey(); // 无需强转,直接是String
Integer userAge = userPair.getValue(); // 无需强转,直接是Integer
System.out.println(userPair); // 输出:Pair{key=张三, value=20}
// 2. 创建Pair\<Long, User>对象:key是Long,value是自定义User类
User user = new User(1001, "李四");
Pair\<Long, User> userInfoPair = new Pair<>(1001L, user);
Long userId = userInfoPair.getKey();
User userInfo = userInfoPair.getValue();
System.out.println(userInfoPair); // 输出:Pair{key=1001, value=User{id=1001, name='李四'}}
// 3. 错误示例:添加不匹配类型
// userPair.setValue("25"); // 编译报错:需传入Integer类型,不能传String
}
}
// 自定义User类(用于示例)
class User {
private Long id;
private String name;
public User(Long id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "User{" + "id=" + id + ", name='" + name + "'}";
}
}2. 泛型接口:接口定义时指定类型参数
泛型接口与泛型类类似,在定义接口时指定类型参数,实现接口时需明确具体类型(或继续保留泛型)。
(1)语法格式
// 定义泛型接口
interface 接口名\<T1, T2, ...> {
// 1. 用类型参数定义抽象方法的返回值或参数
T1 method1(T2 param);
// 2. 用类型参数定义常量(JDK 1.8+允许接口有默认方法和静态方法)
default void method2(T1 param) {
// 默认方法实现
}
}(2)代码示例:自定义泛型接口(Converter)
// 泛型接口:定义“类型转换”行为,T是源类型,R是目标类型
interface Converter\<T, R> {
// 抽象方法:将T类型转换为R类型
R convert(T source);
// 默认方法:批量转换(JDK 1.8+)
default List\<R> convertList(List\<T> sourceList) {
List\<R> resultList = new ArrayList<>();
for (T source : sourceList) {
resultList.add(convert(source));
}
return resultList;
}
}
// 实现泛型接口:String→Integer转换器
class StringToIntegerConverter implements Converter\<String, Integer> {
@Override
public Integer convert(String source) {
// 实现String到Integer的转换(处理空值)
return source == null ? 0 : Integer.parseInt(source.trim());
}
}
// 测试泛型接口
public class GenericInterfaceDemo {
public static void main(String\[] args) {
// 1. 创建转换器实例
Converter\<String, Integer> converter = new StringToIntegerConverter();
// 2. 单个转换
Integer num = converter.convert("123");
System.out.println("单个转换结果:" + num); // 输出:123
// 3. 批量转换(调用默认方法)
List\<String> strList = Arrays.asList("456", "789", "1000");
List\<Integer> intList = converter.convertList(strList);
System.out.println("批量转换结果:" + intList); // 输出:\[456, 789, 1000]
// 4. 其他实现:如Integer→String转换器(匿名内部类)
Converter\<Integer, String> intToStringConverter = new Converter\<Integer, String>() {
@Override
public String convert(Integer source) {
return source == null ? "0" : "数值:" + source;
}
};
String str = intToStringConverter.convert(5);
System.out.println("Integer→String转换:" + str); // 输出:数值:5
}
}3. 泛型方法:方法定义时指定类型参数
泛型方法是 “包含类型参数的方法”,独立于泛型类 / 接口—— 即使在普通类中,也可定义泛型方法,调用时需明确类型(或由编译器自动推断)。
(1)语法格式
// 泛型方法:\<T1, T2, ...>放在返回值前,是方法的类型参数
修饰符 \<T1, T2, ...> 返回值类型 方法名(T1 param1, T2 param2, ...) {
// 方法体:使用类型参数
}(2)关键区别:泛型方法 vs 泛型类的方法
- 泛型类的方法:类型参数属于类,创建类对象时确定类型(如
Pair<K, V>的getKey()方法,K 的类型在new Pair<>()时确定); - 泛型方法:类型参数属于方法,调用方法时确定类型(或编译器推断),与类是否泛型无关(普通类也可定义泛型方法)。
(3)代码示例:自定义泛型工具方法
import java.util.Arrays;
// 普通类(非泛型类)中定义泛型方法
class GenericUtil {
// 泛型方法1:交换数组中两个位置的元素(适配所有类型的数组)
public static \<T> void swap(T\[] arr, int i, int j) {
// 校验参数合法性
if (arr == null || i < 0 || j < 0 || i >= arr.length || j >= arr.length) {
throw new IllegalArgumentException("参数非法!");
}
T temp = arr\[i];
arr\[i] = arr\[j];
arr\[j] = temp;
}
// 泛型方法2:获取数组中的第一个元素(适配所有类型的数组)
public static \<T> T getFirstElement(T\[] arr) {
if (arr == null || arr.length == 0) {
return null;
}
return arr\[0];
}
// 泛型方法3:带边界的泛型方法(后续“泛型边界”章节详解)
public static \<T extends Comparable\<T>> T getMax(T\[] arr) {
if (arr == null || arr.length == 0) {
return null;
}
T max = arr\[0];
for (T element : arr) {
if (element.compareTo(max) > 0) { // 调用Comparable接口方法
max = element;
}
}
return max;
}
}
// 测试泛型方法
public class GenericMethodDemo {
public static void main(String\[] args) {
// 1. 交换String数组元素(编译器自动推断T为String)
String\[] strArr = {"A", "B", "C", "D"};
GenericUtil.swap(strArr, 1, 3); // 交换索引1和3的元素
System.out.println("交换后String数组:" + Arrays.toString(strArr)); // 输出:\[A, D, C, B]
// 2. 交换Integer数组元素(显式指定T为Integer,也可省略)
Integer\[] intArr = {1, 2, 3, 4};
GenericUtil.\<Integer>swap(intArr, 0, 2); // 显式指定类型
System.out.println("交换后Integer数组:" + Arrays.toString(intArr)); // 输出:\[3, 2, 1, 4]
// 3. 获取数组第一个元素
Integer firstInt = GenericUtil.getFirstElement(intArr);
System.out.println("Integer数组第一个元素:" + firstInt); // 输出:3
// 4. 获取数组最大值(T需实现Comparable接口)
Integer\[] numArr = {5, 2, 9, 1};
Integer maxNum = GenericUtil.getMax(numArr);
System.out.println("数组最大值:" + maxNum); // 输出:9
// 错误示例:非Comparable类型无法调用getMax(编译报错)
// User\[] userArr = {new User(1L, "张三"), new User(2L, "李四")};
// GenericUtil.getMax(userArr); // 编译报错:User未实现Comparable\<User>
}
}三、泛型的核心特性:类型擦除(Type Erasure)
Java 的泛型是 “编译时泛型”—— 编译阶段会将泛型的 “类型参数” 擦除为 “原始类型”(Raw Type),运行时 JVM 不感知泛型类型,仅保留原始类型信息。这是 Java 泛型与 C++ 模板的核心区别(C++ 模板在编译时生成不同类型的代码)。
1. 类型擦除的规则
(1)无边界泛型:擦除为Object
若泛型类型参数无显式上界(如T),编译后擦除为Object:
// 泛型类:无边界
class Box\<T> {
private T value;
public T getValue() { return value; }
public void setValue(T value) { this.value = value; }
}
// 编译后擦除为原始类型(伪代码)
class Box {
private Object value;
public Object getValue() { return value; }
public void setValue(Object value) { this.value = value; }
}(2)有上界泛型:擦除为 “上界类型”
若泛型类型参数有显式上界(如T extends Number),编译后擦除为上界类型(Number):
// 泛型类:有上界(T必须是Number的子类)
class NumberBox\<T extends Number> {
private T value;
public T getValue() { return value; }
public void setValue(T value) { this.value = value; }
}
// 编译后擦除为原始类型(伪代码)
class NumberBox {
private Number value;
public Number getValue() { return value; }
public void setValue(Number value) { this.value = value; }
}(3)泛型方法的擦除
泛型方法的类型参数同样会擦除,无边界擦除为Object,有上界擦除为上界类型:
// 泛型方法:无边界
public static \<T> T getFirst(T\[] arr) { ... }
// 编译后擦除为(伪代码)
public static Object getFirst(Object\[] arr) { ... }
// 泛型方法:有上界
public static \<T extends Comparable\<T>> T getMax(T\[] arr) { ... }
// 编译后擦除为(伪代码)
public static Comparable getMax(Comparable\[] arr) { ... }2. 类型擦除的影响:桥方法(Bridge Method)
类型擦除可能导致 “重写方法签名不匹配”,JVM 会自动生成 “桥方法” 解决此问题。
代码示例:桥方法的生成
// 泛型接口:Comparable\<T>(JDK自带)
interface Comparable\<T> {
int compareTo(T o);
}
// 实现类:String实现Comparable\<String>
class String implements Comparable\<String> {
// 实现compareTo方法:参数是String
@Override
public int compareTo(String anotherString) {
// 字符串比较逻辑
return this.equals(anotherString) ? 0 : 1; // 简化逻辑
}
}
// 类型擦除后:
// Comparable接口的compareTo方法擦除为int compareTo(Object o)
// String的compareTo方法签名是int compareTo(String o),与擦除后的接口方法不匹配
// JVM自动生成桥方法(伪代码):
class String implements Comparable {
// 1. 桥方法:匹配擦除后的接口方法
public int compareTo(Object o) {
// 调用实际的compareTo(String)方法
return compareTo((String) o);
}
// 2. 实际实现的方法
public int compareTo(String anotherString) {
return this.equals(anotherString) ? 0 : 1;
}
}桥方法的作用:确保类型擦除后,子类仍能正确重写父类 / 接口的方法,避免多态失效。
3. 类型擦除的局限性
- 运行时无法获取泛型类型信息(如
new ArrayList<String>()运行时仅知道是ArrayList,不知道元素是String); - 无法实例化泛型类型的对象(如
new T()编译报错,因擦除后T是Object,无法确定具体类型); - 无法创建泛型类型的数组(如
new T[10]编译报错,因数组在运行时需知道具体类型)。
四、泛型的通配符:解决泛型类型的灵活性问题
泛型的 “类型参数” 是 “严格匹配” 的(如List<String>不是List<Object>的子类),但实际开发中常需 “灵活匹配多个类型”(如方法接收所有List子类)。泛型通配符(?)就是为解决此问题而生,分为无界通配符、上界通配符、下界通配符三类。
1. 无界通配符(?):匹配任意类型
无界通配符?表示 “任意类型”,常用于 “不关心泛型类型,仅使用原始类型方法” 的场景(如获取集合大小、判断是否为空)。
(1)语法格式
// 无界通配符:?表示任意类型 List\<?> list; // 可指向List\<String>、List\<Integer>、List\<User>等任意List
(2)代码示例:无界通配符的使用
import java.util.ArrayList;
import java.util.List;
public class UnboundedWildcardDemo {
// 方法:打印任意List的大小和元素(不关心元素类型)
public static void printList(List\<?> list) {
System.out.println("List大小:" + list.size());
for (Object obj : list) { // 只能用Object接收元素(因类型未知)
System.out.print(obj + " ");
}
System.out.println();
}
public static void main(String\[] args) {
// 1. List\<String>
List\<String> strList = new ArrayList<>();
strList.add("A");
strList.add("B");
printList(strList); // 合法:List\<String>匹配List\<?>
// 2. List\<Integer>
List\<Integer> intList = new ArrayList<>();
intList.add(1);
intList.add(2);
printList(intList); // 合法:List\<Integer>匹配List\<?>
// 3. 无界通配符的限制:无法添加非null元素(因类型未知,无法确定是否匹配)
List\<?> wildcardList = new ArrayList\<String>();
// wildcardList.add("C"); // 编译报错:无法确定类型,不允许添加
wildcardList.add(null); // 合法:null是任意类型的实例
}
}(3)核心特点:
- 优点:灵活匹配任意泛型类型,适合 “只读” 或 “不依赖元素类型” 的场景;
- 缺点:无法添加非
null元素(类型未知,避免添加不匹配类型),仅能通过Object接收元素。
2. 上界通配符(? extends T):匹配 T 及其子类
上界通配符? extends T表示 “任意继承自 T 的类型”(T 是上界),常用于 “读取元素” 的场景(如获取集合中元素的最大值,元素需是 T 的子类)。
(1)语法格式
// 上界通配符:? extends T,匹配T及其子类 List\<? extends Number> list; // 可指向List\<Integer>、List\<Double>、List\<Number>(Integer和Double是Number的子类)
(2)代码示例:上界通配符的使用
import java.util.ArrayList;
import java.util.List;
public class BoundedUpperWildcardDemo {
// 方法:计算List中所有数值的和(元素必须是Number的子类,如Integer、Double)
public static double sumList(List\<? extends Number> numberList) {
double sum = 0.0;
for (Number num : numberList) { // 可通过上界类型Number接收元素
sum += num.doubleValue(); // 调用Number的方法,安全
}
return sum;
}
public static void main(String\[] args) {
// 1. List\<Integer>(Integer extends Number)
List\<Integer> intList = new ArrayList<>();
intList.add(10);
intList.add(20);
System.out.println("Integer列表和:" + sumList(intList)); // 输出:30.0
// 2. List\<Double>(Double extends Number)
List\<Double> doubleList = new ArrayList<>();
doubleList.add(15.5);
doubleList.add(25.5);
System.out.println("Double列表和:" + sumList(doubleList)); // 输出:41.0
// 3. 上界通配符的限制:无法添加非null元素(除null外)
List\<? extends Number> upperList = new ArrayList\<Integer>();
// upperList.add(30); // 编译报错:无法确定具体是Number的哪个子类,避免添加不匹配类型
upperList.add(null); // 合法:null是任意类型的实例
}
}(3)核心特点:
- 优点:可通过上界类型安全读取元素(如
Number),适合 “只读” 场景; - 缺点:无法添加非
null元素(类型不确定,如List<? extends Number>可能是List<Integer>,添加Double会出错)。
3. 下界通配符(? super T):匹配 T 及其父类
下界通配符? super T表示 “任意 T 的父类型”(T 是下界),常用于 “写入元素” 的场景(如向集合中添加 T 类型的元素,父类集合可接收子类元素)。
(1)语法格式
// 下界通配符:? super T,匹配T及其父类 List\<? super Integer> list; // 可指向List\<Integer>、List\<Number>、List\<Object>(Number和Object是Integer的父类)
(2)代码示例:下界通配符的使用
import java.util.ArrayList;
import java.util.List;
public class BoundedLowerWildcardDemo {
// 方法:向List中添加多个Integer元素(List的类型必须是Integer或其父类)
public static void addIntegers(List\<? super Integer> list) {
list.add(1); // 合法:可添加Integer类型(下界类型)
list.add(2); // 合法:可添加Integer的子类(如Integer本身)
// list.add(3.5); // 编译报错:不能添加非Integer类型(如Double)
}
public static void main(String\[] args) {
// 1. List\<Integer>(Integer super Integer)
List\<Integer> intList = new ArrayList<>();
addIntegers(intList);
System.out.println("List\<Integer>添加后:" + intList); // 输出:\[1, 2]
// 2. List\<Number>(Number super Integer)
List\<Number> numberList = new ArrayList<>();
numberList.add(100.5); // 先添加一个Double
addIntegers(numberList);
System.out.println("List\<Number>添加后:" + numberList); // 输出:\[100.5, 1, 2]
// 3. List\<Object>(Object super Integer)
List\<Object> objectList = new ArrayList<>();
objectList.add("Hello"); // 先添加一个String
addIntegers(objectList);
System.out.println("List\<Object>添加后:" + objectList); // 输出:\[Hello, 1, 2]
// 4. 下界通配符的限制:读取元素只能用Object接收(因父类类型不确定)
for (Object obj : numberList) {
System.out.print(obj + " "); // 只能用Object接收
}
}
}(3)核心特点:
- 优点:可安全添加下界类型(如
Integer)及其子类的元素,适合 “写入” 场景; - 缺点:读取元素只能用
Object接收(父类类型不确定,无法通过更具体的类型接收)。
4. 通配符使用口诀:PECS 原则
为简化通配符的选择,业界总结出PECS 原则(Producer Extends, Consumer Super):
- Producer(生产者):若泛型对象仅用于 “提供元素”(读取),用
? extends T(上界通配符); - 例:
sumList(List<? extends Number> list)——list 是 “生产者”,提供 Number 类型元素用于求和。 - Consumer(消费者):若泛型对象仅用于 “接收元素”(写入),用
? super T(下界通配符); - 例:
addIntegers(List<? super Integer> list)——list 是 “消费者”,接收 Integer 类型元素。 - 既是生产者又是消费者:不用通配符,直接用具体类型(如
List<T>)。
五、泛型的限制与注意事项
Java 泛型受限于类型擦除,存在一些无法突破的限制,开发中需避免这些场景。
1. 限制 1:不能实例化泛型类型的对象
因类型擦除后泛型类型变为Object或上界类型,无法确定具体类型,故new T()编译报错:
class Box\<T> {
public Box() {
// T obj = new T(); // 编译报错:无法实例化泛型类型
// 解决方案:通过反射或传入Class对象
// T obj = clazz.newInstance(); // 需传入Class\<T> clazz参数
}
}2. 限制 2:不能用基本类型作为类型参数
泛型的类型参数必须是 “引用类型”(如Integer、String),不能是基本类型(如int、double),因类型擦除后会变为Object,而基本类型无法赋值给Object:
// List\<int> list = new ArrayList<>(); // 编译报错:不能用基本类型int List\<Integer> list = new ArrayList<>(); // 合法:用包装类Integer
3. 限制 3:不能创建泛型类型的数组
数组在运行时需知道具体类型,而泛型类型擦除后无法确定,故new T[10]编译报错:
class Box\<T> {
public void test() {
// T\[] arr = new T\[10]; // 编译报错:不能创建泛型数组
// 解决方案1:用Object数组,使用时强制转换
Object\[] arr = new Object\[10];
T element = (T) arr\[0];
// 解决方案2:用ArrayList替代数组(推荐)
List\<T> list = new ArrayList<>();
}
}4. 限制 4:泛型类不能继承 Throwable
泛型类无法继承Exception、Error等 Throwable 子类,因异常处理(try-catch)在编译时需确定类型,而泛型类型擦除后无法匹配:
// class GenericException\<T> extends Exception { } // 编译报错:泛型类不能继承Throwable
class MyException extends Exception { } // 合法:非泛型类可继承Exception5. 限制 5:静态方法不能引用类的泛型参数
类的泛型参数属于 “对象级”(创建对象时确定),而静态方法属于 “类级”(类加载时确定),二者生命周期不匹配,故静态方法不能直接引用类的泛型参数:
class Box\<T> {
// public static T getValue() { return null; } // 编译报错:静态方法不能引用类的泛型参数
// 解决方案:静态方法定义自己的泛型参数(泛型方法)
public static \<U> U getValue() { return null; } // 合法:静态泛型方法
}六、泛型的实际应用场景
泛型在 Java 开发中无处不在,核心应用场景包括集合框架、工具类、框架设计等。
1. 场景 1:集合框架(最典型应用)
Java 集合框架(List、Set、Map等)全部基于泛型实现,确保元素类型安全:
// List\<String>:元素只能是String
List\<String> names = new ArrayList<>();
names.add("Alice");
String name = names.get(0); // 无需强转
// Map\<Long, User>:键是Long,值是User
Map\<Long, User> userMap = new HashMap<>();
userMap.put(1001L, new User(1001L, "Bob"));
User user = userMap.get(1001L); // 无需强转2. 场景 2:泛型工具类(代码复用)
开发通用工具类(如排序、比较、转换工具)时,用泛型实现 “一套逻辑适配多种类型”:
// 泛型工具类:排序任意Comparable类型的数组
public class SortUtil {
public static \<T extends Comparable\<T>> void sort(T\[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr\[j].compareTo(arr\[j + 1]) > 0) {
T temp = arr\[j];
arr\[j] = arr\[j + 1];
arr\[j + 1] = temp;
}
}
}
}
}
// 使用:排序Integer数组和String数组
Integer\[] intArr = {3, 1, 2};
SortUtil.sort(intArr); // 输出:\[1, 2, 3]
String\[] strArr = {"C", "A", "B"};
SortUtil.sort(strArr); // 输出:\[A, B, C]3. 场景 3:框架设计(解耦与扩展)
主流框架(如 Spring、MyBatis)大量使用泛型实现灵活扩展,例如 MyBatis 的Mapper接口:
// 泛型接口:MyBatis Mapper,T是实体类,ID是主键类型
public interface BaseMapper\<T, ID> {
T selectById(ID id); // 根据主键查询
int insert(T entity); // 插入实体
int update(T entity); // 更新实体
int deleteById(ID id); // 根据主键删除
}
// 实现接口:UserMapper,T=User,ID=Long
public interface UserMapper extends BaseMapper\<User, Long> {
// 无需重复定义CRUD方法,直接继承泛型接口
List\<User> selectByUsername(String username); // 新增自定义方法
}七、泛型的常见误区与避坑指南
1. 误区 1:混淆泛型类型与原始类型
- 问题:
List list = new ArrayList<String>();(原始类型 List 接收泛型对象),编译时会有 “未检查的转换” 警告,且失去类型安全; - 解决:始终用泛型类型接收泛型对象(如
List<String> list = new ArrayList<>();),避免使用原始类型。
2. 误区 2:错误使用通配符导致添加元素失败
- 问题:
List<? extends Number> list = new ArrayList<Integer>(); list.add(1);(编译报错),误以为上界通配符可添加元素; - 原因:上界通配符
? extends Number可能指向List<Double>,添加Integer会类型不匹配; - 解决:添加元素用下界通配符(
? super T),如List<? super Integer> list = new ArrayList<>(); list.add(1);。
3. 误区 3:泛型方法的类型参数与类的类型参数重名
- 问题:
class Box\<T> {
// 泛型方法的类型参数T与类的T重名,导致混淆
public \<T> T getValue(T param) { return param; }
}- 影响:方法的
T会隐藏类的T,导致类的T无法在方法中使用; - 解决:泛型方法的类型参数用不同名称(如
U),避免重名,如public <U> U getValue(U param) { return param; }。
4. 误区 4:误以为泛型可实现 “运行时类型判断”
- 问题:
if (list instanceof List<String>) { ... }(编译报错),试图在运行时判断泛型类型; - 原因:类型擦除后,运行时
List<String>和List<Integer>都是List,无法区分; - 解决:若需判断元素类型,遍历集合检查每个元素的类型(如
if (list.get(0) instanceof String))。
八、总结:泛型的核心要点与实践建议
1. 核心要点
- 本质:参数化类型,编译时类型安全,运行时类型擦除;
- 语法:泛型类(
class A<T>)、泛型接口(interface B<T>)、泛型方法(<T> T method(T param)); - 通配符:PECS 原则(生产者
extends,消费者super),无界通配符用于只读且不关心类型; - 限制:不能实例化泛型对象、不能用基本类型、不能创建泛型数组、泛型类不能继承 Throwable。
2. 实践建议
- 优先使用泛型:定义集合、工具类时强制使用泛型,避免原始类型,确保类型安全;
- 合理选择通配符:读取用
extends,写入用super,既读又写用具体类型; - 泛型方法优先于泛型类:若仅方法需要通用逻辑,定义泛型方法(无需创建泛型类),提高代码复用;
- 避免过度泛型化:仅在需要适配多种类型时使用泛型,简单场景直接用具体类型,避免代码复杂度。
泛型是 Java 类型系统的重要扩展,掌握泛型的语法与原理,是编写类型安全、高复用代码的基础,也是后续学习集合框架、框架源码(如 Spring、MyBatis)的关键前提。
到此这篇关于Java SE 泛型原理、语法与实践详解的文章就介绍到这了,更多相关java se泛型内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
