跳到主要内容
Java 泛型与通配符:原理及实战应用 | 极客日志
Java java
Java 泛型与通配符:原理及实战应用 Java 泛型利用类型参数化在编译期确保类型安全,避免运行时 ClassCastException。核心机制是类型擦除,运行时泛型信息被移除。泛型类、接口和方法提升代码复用性。通配符 ? extends T 适用于只读场景(生产者),? super T 适用于只写场景(消费者),遵循 PECS 原则。需注意泛型数组创建限制及静态方法中泛型参数的使用规范。
RefactorPro 发布于 2026/3/21 更新于 2026/5/5 5 浏览Java 泛型与通配符:原理及实战应用
1.1 本章学习目标与重点
💡 掌握泛型的核心概念与设计初衷,理解泛型的编译期检查机制。
💡 熟练使用泛型类、泛型接口和泛型方法,解决数据类型安全问题。
💡 理解通配符(?)、上界通配符(? extends T)和下界通配符(? super T)的使用场景。
⚠️ 本章重点是 泛型的擦除机制 和 通配符的灵活运用 ,这是提升代码通用性和安全性的关键。
1.2 泛型的核心概念与设计初衷
1.2.1 为什么需要泛型
在没有泛型的 JDK 5 之前,集合类只能存储 Object 类型的对象。获取元素时需要强制类型转换,这会带来两个严重问题:
类型不安全 :可以向集合中添加任意类型的对象,运行时可能抛出 ClassCastException。
代码臃肿 :频繁的强制类型转换会让代码可读性和维护性变差。
💡 泛型的出现就是为了解决这些问题,它的核心思想是 将类型参数化 。在定义类、接口或方法时,用一个标识符表示类型,使用时再指定具体类型。
泛型可以在编译期 进行类型检查,避免运行时的类型转换异常,同时简化代码。
1.2.2 泛型的核心优势
编译期类型安全 :编译器会检查集合中元素的类型,不允许添加错误类型的对象。
消除强制类型转换 :获取元素时无需手动转换类型,代码更简洁。
代码复用性高 :一套泛型代码可以适配多种数据类型,无需重复编写。
✅ 核心结论:泛型是一种编译期技术 ,它的作用是在编译阶段保证类型安全,运行时泛型信息会被擦除。
1.3 泛型的三种使用方式
1.3.1 泛型类
💡 泛型类是在类的定义时声明类型参数,语法格式为 class 类名<T>。其中 T 是类型参数,可以是任意标识符,常用的有 T(Type)、E(Element)、K(Key)、V(Value)。
代码实操:自定义泛型集合类
我们实现一个简单的泛型顺序表 GenericArrayList,支持任意类型的数据存储:
public class GenericArrayList <T> {
private Object[] elementData;
private int size;
private static final int DEFAULT_CAPACITY = 10 ;
{
elementData = [DEFAULT_CAPACITY];
}
{
(initialCapacity < ) {
( + initialCapacity);
}
elementData = [initialCapacity];
}
{
(size == elementData.length) {
grow();
}
elementData[size++] = t;
}
T {
(index < || index >= size) {
( + index);
}
(T) elementData[index];
}
{
elementData.length;
oldCapacity + (oldCapacity >> );
elementData = java.util.Arrays.copyOf(elementData, newCapacity);
}
{
size;
}
{
GenericArrayList<String> strList = <>();
strList.add( );
strList.add( );
strList.get( );
System.out.println( + str);
GenericArrayList<Integer> intList = <>();
intList.add( );
intList.add( );
intList.get( );
System.out.println( + num);
}
}
public
GenericArrayList
()
new
Object
public
GenericArrayList
(int initialCapacity)
if
0
throw
new
IllegalArgumentException
"初始容量不能为负数:"
new
Object
public
void
add
(T t)
if
@SuppressWarnings("unchecked")
public
get
(int index)
if
0
throw
new
IndexOutOfBoundsException
"索引越界:"
return
private
void
grow
()
int
oldCapacity
=
int
newCapacity
=
1
public
int
size
()
return
public
static
void
main
(String[] args)
new
GenericArrayList
"Java"
"泛型"
String
str
=
0
"String 集合元素:"
new
GenericArrayList
1
2
Integer
num
=
1
"Integer 集合元素:"
String 集合元素:Java Integer 集合元素:2
泛型类的类型参数不能是基本数据类型,只能是引用数据类型。如果需要存储基本类型,需使用对应的包装类,如 Integer 代替 int。
泛型类的静态方法中不能使用类的泛型参数 ,因为静态方法属于类,而泛型参数是在创建对象时指定的。
1.3.2 泛型接口 💡 泛型接口的定义与泛型类类似,语法格式为 interface 接口名<T>。实现泛型接口时,可以指定具体类型,也可以继续使用泛型参数。
代码实操:自定义泛型迭代器接口
public interface GenericIterator <T> {
boolean hasNext () ;
T next () ;
}
public class StringIterator implements GenericIterator <String> {
private String[] array;
private int index;
public StringIterator (String[] array) {
this .array = array;
this .index = 0 ;
}
@Override
public boolean hasNext () {
return index < array.length;
}
@Override
public String next () {
return array[index++];
}
}
public class ArrayIterator <T> implements GenericIterator <T> {
private T[] array;
private int index;
public ArrayIterator (T[] array) {
this .array = array;
this .index = 0 ;
}
@Override
public boolean hasNext () {
return index < array.length;
}
@Override
public T next () {
return array[index++];
}
}
public class GenericIteratorTest {
public static void main (String[] args) {
String[] strArray = {"A" , "B" , "C" };
GenericIterator<String> strIterator = new StringIterator (strArray);
while (strIterator.hasNext()) {
System.out.println(strIterator.next());
}
Integer[] intArray = {1 , 2 , 3 };
GenericIterator<Integer> intIterator = new ArrayIterator <>(intArray);
while (intIterator.hasNext()) {
System.out.println(intIterator.next());
}
}
}
1.3.3 泛型方法 💡 泛型方法是在方法声明时指定类型参数,它可以定义在普通类中,也可以定义在泛型类中。语法格式为 public <T> 返回值类型 方法名 (T 参数)。
泛型方法的核心特点是 类型参数由方法的调用者决定 ,与类的泛型参数无关。
代码实操:泛型工具方法封装 import java.util.Arrays;
public class GenericMethodUtil {
public static <T> GenericArrayList<T> arrayToList (T[] array) {
GenericArrayList<T> list = new GenericArrayList <>();
for (T t : array) {
list.add(t);
}
return list;
}
public static <T> void swap (T[] array, int i, int j) {
if (i < 0 || i >= array.length || j < 0 || j >= array.length) {
throw new IndexOutOfBoundsException ("索引越界" );
}
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
public static void main (String[] args) {
String[] strArray = {"Java" , "泛型" , "方法" };
GenericArrayList<String> strList = GenericMethodUtil.arrayToList(strArray);
System.out.println("数组转集合大小:" + strList.size());
Integer[] intArray = {10 , 20 , 30 , 40 };
System.out.println("交换前数组:" + Arrays.toString(intArray));
GenericMethodUtil.swap(intArray, 1 , 3 );
System.out.println("交换后数组:" + Arrays.toString(intArray));
}
}
数组转集合大小:3 交换前数组:[10, 20, 30, 40] 交换后数组:[10, 40, 30, 20]
💡 技巧:泛型方法可以配合可变参数使用,进一步提升灵活性。例如:
public static <T> GenericArrayList<T> createList (T... elements) {
GenericArrayList<T> list = new GenericArrayList <>();
for (T t : elements) {
list.add(t);
}
return list;
}
GenericArrayList<Integer> list = GenericMethodUtil.createList(1 , 2 , 3 , 4 );
1.4 泛型的高级特性
1.4.1 泛型的擦除机制 💡 泛型是编译期特性 ,在运行时 JVM 会擦除泛型信息,这个过程称为类型擦除 。
类型擦除的规则:
泛型类、接口和方法的类型参数会被擦除为其上限类型 ,如果没有指定上限,则擦除为 Object。
编译器会在必要的地方插入强制类型转换代码,保证运行时的类型正确性。
例如,我们定义的 GenericArrayList<String> 在运行时会被擦除为 GenericArrayList<Object>。获取元素时,编译器会自动插入 (String) 强制类型转换代码。
类型擦除的验证 通过反射可以验证类型擦除的存在,因为反射是运行时机制,可以获取到擦除后的类型信息:
import java.lang.reflect.Field;
public class GenericErasureTest {
public static void main (String[] args) throws NoSuchFieldException {
GenericArrayList<String> strList = new GenericArrayList <>();
Field field = GenericArrayList.class.getDeclaredField("elementData" );
field.setAccessible(true );
Class<?> type = field.getType().getComponentType();
System.out.println("底层数组的类型:" + type.getName());
}
}
⚠️ 注意事项:类型擦除会导致泛型不支持重载 。例如,以下两个方法在编译后会变成相同的签名,编译器会报错:
public void method (GenericArrayList<String> list) {}
public void method (GenericArrayList<Integer> list) {}
1.4.2 泛型的上下界限定 💡 在定义泛型时,可以通过 extends 关键字限制类型参数的上限,通过 super 关键字限制类型参数的下限(仅在通配符中使用)。
上限限定 的语法:<T extends 类型>,表示 T 必须是该类型的子类或本身。
多个上限 的语法:<T extends 类型 1 & 类型 2>,表示 T 必须同时实现多个接口(类只能有一个,且放在最前面)。
代码实操:泛型上限限定 我们实现一个泛型方法,用于计算数组中元素的总和,要求元素类型必须是 Number 的子类:
public class GenericBoundsTest {
public static <T extends Number > double sum (T[] array) {
double sum = 0.0 ;
for (T t : array) {
sum += t.doubleValue();
}
return sum;
}
public static void main (String[] args) {
Integer[] intArray = {1 , 2 , 3 , 4 , 5 };
System.out.println("Integer 数组总和:" + sum(intArray));
Double[] doubleArray = {1.1 , 2.2 , 3.3 };
System.out.println("Double 数组总和:" + sum(doubleArray));
}
}
Integer 数组总和:15.0 Double 数组总和:6.6
1.5 通配符的使用与场景
1.5.1 通配符(?)的基本使用 💡 通配符 ? 表示任意类型 ,它可以用来解决泛型的类型不兼容 问题。
例如,GenericArrayList<Object> 不能接收 GenericArrayList<String> 对象,因为泛型是不协变 的。此时可以使用通配符 GenericArrayList<?> 来接收任意类型的泛型实例。
代码实操:通配符的基本应用 public class WildcardBasicTest {
public static void printList (GenericArrayList<?> list) {
for (int i = 0 ; i < list.size(); i++) {
System.out.println("元素:" + list.get(i));
}
}
public static void main (String[] args) {
GenericArrayList<String> strList = new GenericArrayList <>();
strList.add("Hello" );
strList.add("Wildcard" );
GenericArrayList<Integer> intList = new GenericArrayList <>();
intList.add(100 );
intList.add(200 );
printList(strList);
printList(intList);
}
}
元素:Hello 元素:Wildcard 元素:100 元素:200
⚠️ 注意事项:使用无界通配符 <?> 的集合,只能读取元素 ,不能添加元素(除了 null)。因为编译器无法确定集合的具体类型,添加任何类型的元素都可能破坏类型安全。
1.5.2 上界通配符(? extends T) 💡 上界通配符 ? extends T 表示任意 T 的子类或 T 本身 。它的核心作用是 限定读取的上限 ,适合只读 的场景。
代码实操:上界通配符的应用 我们实现一个方法,用于获取泛型集合中的最大值,要求元素类型必须实现 Comparable 接口:
public class WildcardExtendsTest {
public static <T extends Comparable <T>> T getMax (GenericArrayList<? extends T> list) {
if (list.size() == 0 ) {
return null ;
}
T max = list.get(0 );
for (int i = 1 ; i < list.size(); i++) {
T current = list.get(i);
if (current.compareTo(max) > 0 ) {
max = current;
}
}
return max;
}
public static void main (String[] args) {
GenericArrayList<Integer> intList = new GenericArrayList <>();
intList.add(5 );
intList.add(12 );
intList.add(3 );
System.out.println("Integer 集合最大值:" + getMax(intList));
GenericArrayList<String> strList = new GenericArrayList <>();
strList.add("Apple" );
strList.add("Banana" );
strList.add("Orange" );
System.out.println("String 集合最大值:" + getMax(strList));
}
}
Integer 集合最大值:12 String 集合最大值:Orange
✅ 核心结论:上界通配符 ? extends T 适合读取数据 的场景,它可以接收 T 及其子类的泛型实例。
1.5.3 下界通配符(? super T) 💡 下界通配符 ? super T 表示任意 T 的父类或 T 本身 。它的核心作用是 限定写入的下限 ,适合只写 的场景。
代码实操:下界通配符的应用 我们实现一个方法,用于向泛型集合中添加指定类型的元素:
public class WildcardSuperTest {
public static void addIntegers (GenericArrayList<? super Integer> list) {
list.add(1 );
list.add(2 );
list.add(3 );
}
public static void main (String[] args) {
GenericArrayList<Integer> intList = new GenericArrayList <>();
addIntegers(intList);
System.out.println("Integer 集合大小:" + intList.size());
GenericArrayList<Number> numList = new GenericArrayList <>();
addIntegers(numList);
System.out.println("Number 集合大小:" + numList.size());
}
}
Integer 集合大小:3 Number 集合大小:3
✅ 核心结论:下界通配符 ? super T 适合写入数据 的场景,它可以接收 T 及其父类的泛型实例。
1.5.4 通配符的使用原则:PECS 原则 💡 PECS 原则是通配符使用的黄金法则,全称是 Producer Extends, Consumer Super 。
Producer Extends :如果泛型对象是生产者 (提供数据,只读),使用 ? extends T。
Consumer Super :如果泛型对象是消费者 (消费数据,只写),使用 ? super T。
读取数据的 getMax 方法,使用 ? extends T。
写入数据的 addIntegers 方法,使用 ? super T。
既读又写的场景,不使用通配符,直接使用具体的泛型类型。
1.6 泛型的常见问题与解决方案
1.6.1 泛型数组的创建问题 ⚠️ 不能直接创建泛型数组,例如 new T[10] 会编译报错。因为类型擦除后,JVM 无法确定数组的具体类型。
解决方案 :
创建 Object 数组,然后强制类型转换。
使用反射创建泛型数组。
T[] array = (T[]) new Object [10 ];
public static <T> T[] createArray(Class<T> clazz, int size) {
return (T[]) java.lang.reflect.Array.newInstance(clazz, size);
}
String[] strArray = createArray(String.class, 5 );
1.6.2 泛型与异常的问题 ⚠️ 泛型不能用于异常类的定义和捕获,例如 class GenericException<T> extends Exception 会编译报错。
原因 :异常的处理是运行时机制,而泛型是编译期机制,类型擦除后无法区分不同的泛型异常类型。
解决方案 :如果需要传递泛型数据,可以将泛型类型作为异常类的成员变量。
public class GenericException extends Exception {
private Object data;
public <T> GenericException(T data) {
this .data = data;
}
@SuppressWarnings("unchecked")
public <T> T getData () {
return (T) data;
}
}
1.6.3 泛型与静态方法的问题 ⚠️ 泛型类的静态方法不能使用类的泛型参数,因为静态方法属于类,在类加载时就已经确定,而泛型参数是在创建对象时指定的。
解决方案 :将静态方法定义为泛型方法 ,使用自己的类型参数。
public class GenericClass <T> {
public static <E> void method (E e) {}
}
1.7 实战案例:泛型集合工具类封装
1.7.1 需求分析 💡 封装一个通用的泛型集合工具类 GenericCollectionUtil,提供以下功能:
集合去重:去除泛型集合中的重复元素,保留插入顺序。
集合过滤:根据自定义规则过滤集合中的元素。
集合转换:将一种类型的集合转换为另一种类型的集合。
1.7.2 代码实现 import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.function.Predicate;
import java.util.function.Function;
public class GenericCollectionUtil {
public static <T> List<T> distinct (List<T> list) {
if (list == null || list.isEmpty()) {
return new ArrayList <>();
}
return new ArrayList <>(new LinkedHashSet <>(list));
}
public static <T> List<T> filter (List<T> list, Predicate<T> predicate) {
if (list == null || list.isEmpty() || predicate == null ) {
return new ArrayList <>();
}
List<T> result = new ArrayList <>();
for (T t : list) {
if (predicate.test(t)) {
result.add(t);
}
}
return result;
}
public static <T, R> List<R> convert (List<T> list, Function<T, R> function) {
if (list == null || list.isEmpty() || function == null ) {
return new ArrayList <>();
}
List<R> result = new ArrayList <>();
for (T t : list) {
R r = function.apply(t);
result.add(r);
}
return result;
}
public static void main (String[] args) {
List<Integer> numList = new ArrayList <>();
numList.add(1 );
numList.add(2 );
numList.add(1 );
numList.add(3 );
List<Integer> distinctList = GenericCollectionUtil.distinct(numList);
System.out.println("去重后的集合:" + distinctList);
List<Integer> evenList = GenericCollectionUtil.filter(distinctList, num -> num % 2 == 0 );
System.out.println("过滤后的偶数集合:" + evenList);
List<String> strList = GenericCollectionUtil.convert(evenList, String::valueOf);
System.out.println("转换后的字符串集合:" + strList);
}
}
去重后的集合:[1, 2, 3] 过滤后的偶数集合:[2] 转换后的字符串集合:[2]
1.7.3 案例总结 ✅ 这个泛型工具类充分利用了泛型的特性,实现了对任意类型集合的通用操作。通过结合函数式接口(Predicate、Function),进一步提升了代码的灵活性和复用性。在实际开发中,这样的工具类可以大幅减少重复代码,提升开发效率。
1.8 本章总结
泛型的核心是类型参数化 ,它在编译期保证类型安全,运行时会发生类型擦除。
泛型的三种使用方式:泛型类、泛型接口、泛型方法,其中泛型方法的灵活性最高。
泛型的上下界限定可以限制类型参数的范围,提升代码的安全性。
通配符分为无界通配符(?)、上界通配符(? extends T)和下界通配符(? super T),遵循 PECS 原则使用。
泛型的常见问题包括泛型数组创建、泛型与异常、泛型与静态方法,需要通过特定方案解决。
泛型可以大幅提升代码的复用性和安全性,是 JAVA 进阶开发的必备技能。
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online