2429 字
12 分钟

Java泛型编程详解

2026-02-02
浏览量 加载中...

Java泛型编程详解#

什么是泛型?#

泛型是Java 5引入的特性,它允许在定义类、接口和方法时使用类型参数,从而实现代码的复用和类型安全。

泛型的好处#

  1. 类型安全:在编译时检查类型,避免运行时的类型转换错误
  2. 代码复用:编写通用的代码,适用于不同类型
  3. 可读性:代码更加清晰,不需要显式的类型转换
  4. 性能优化:避免了运行时的类型检查和转换,提高性能

泛型的基本语法#

泛型类#

public class Box<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
// 使用
Box<String> stringBox = new Box<>();
stringBox.setValue("Hello");
String value = stringBox.getValue();
Box<Integer> integerBox = new Box<>();
integerBox.setValue(100);
Integer number = integerBox.getValue();

泛型接口#

public interface List<T> {
void add(T element);
T get(int index);
int size();
}
// 实现
public class ArrayList<T> implements List<T> {
// 实现代码
}

泛型方法#

public class GenericMethod {
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.print(element + " ");
}
System.out.println();
}
public static <T> T getFirstElement(T[] array) {
if (array != null && array.length > 0) {
return array[0];
}
return null;
}
}
// 使用
Integer[] intArray = {1, 2, 3, 4, 5};
GenericMethod.printArray(intArray);
String[] stringArray = {"Hello", "World"};
GenericMethod.printArray(stringArray);
Integer firstInt = GenericMethod.getFirstElement(intArray);
String firstString = GenericMethod.getFirstElement(stringArray);

类型参数命名约定#

  • T:Type(类型)
  • E:Element(元素)
  • K:Key(键)
  • V:Value(值)
  • N:Number(数字)
  • S, U, V:第二、第三、第四个类型参数

泛型的边界#

上界通配符#

上界通配符使用extends关键字,表示类型参数必须是指定类型的子类或本身。

public class GenericUpperBound {
public static double sumOfNumbers(List<? extends Number> numbers) {
double sum = 0.0;
for (Number number : numbers) {
sum += number.doubleValue();
}
return sum;
}
}
// 使用
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
double intSum = GenericUpperBound.sumOfNumbers(integers);
List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3);
double doubleSum = GenericUpperBound.sumOfNumbers(doubles);

下界通配符#

下界通配符使用super关键字,表示类型参数必须是指定类型的父类或本身。

public class GenericLowerBound {
public static void addNumbers(List<? super Integer> numbers) {
numbers.add(1);
numbers.add(2);
numbers.add(3);
}
}
// 使用
List<Object> objects = new ArrayList<>();
GenericLowerBound.addNumbers(objects);
List<Number> numbers = new ArrayList<>();
GenericLowerBound.addNumbers(numbers);
List<Integer> integers = new ArrayList<>();
GenericLowerBound.addNumbers(integers);

无界通配符#

无界通配符使用?表示,可以匹配任何类型。

public class GenericUnbounded {
public static void printList(List<?> list) {
for (Object element : list) {
System.out.print(element + " ");
}
System.out.println();
}
}
// 使用
List<Integer> integers = Arrays.asList(1, 2, 3);
GenericUnbounded.printList(integers);
List<String> strings = Arrays.asList("Hello", "World");
GenericUnbounded.printList(strings);

泛型的类型擦除#

什么是类型擦除?#

类型擦除是Java泛型的实现机制,它在编译时擦除了泛型类型信息,将泛型类型替换为其边界类型(如果没有指定边界,则替换为Object)。

类型擦除的过程#

  1. 编译时擦除类型参数
  2. 替换为边界类型(或Object)
  3. 插入必要的类型转换
  4. 生成桥接方法(用于继承泛型类或实现泛型接口)

类型擦除的影响#

  1. 运行时类型信息丢失:运行时无法获取泛型类型参数
  2. 不能使用基本类型作为类型参数:必须使用包装类
  3. 不能创建泛型数组:但可以声明泛型数组引用
  4. 不能使用instanceof检查泛型类型:只能检查原始类型
// 类型擦除的示例
public class Box<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
// 编译后擦除为
public class Box {
private Object value;
public void setValue(Object value) {
this.value = value;
}
public Object getValue() {
return value;
}
}

泛型的限制#

1. 不能使用基本类型作为类型参数#

// 错误
Box<int> intBox = new Box<>();
// 正确
Box<Integer> integerBox = new Box<>();

2. 不能创建泛型数组#

// 错误
List<String>[] stringLists = new List<String>[10];
// 正确
List<?>[] listArray = new List<?>[10];
List<String>[] stringLists = (List<String>[]) new List<?>[10];

3. 不能使用instanceof检查泛型类型#

// 错误
if (box instanceof Box<String>) {
// 代码
}
// 正确
if (box instanceof Box<?>) {
// 代码
}

4. 不能在静态上下文中使用类型参数#

public class Box<T> {
// 错误
private static T staticValue;
// 错误
public static T getStaticValue() {
return staticValue;
}
}

5. 不能创建类型参数的实例#

public class Box<T> {
// 错误
private T value = new T();
// 错误
public Box() {
value = new T();
}
}
// 解决方案:使用反射
public class Box<T> {
private T value;
public Box(Class<T> clazz) throws InstantiationException, IllegalAccessException {
value = clazz.newInstance();
}
}

6. 不能创建类型参数的数组#

public class Box<T> {
// 错误
private T[] array = new T[10];
// 解决方案:使用Object数组并进行类型转换
private Object[] array = new Object[10];
@SuppressWarnings("unchecked")
public T get(int index) {
return (T) array[index];
}
public void set(int index, T value) {
array[index] = value;
}
}

泛型的高级特性#

1. 泛型的继承#

// 泛型类的继承
public class NumberBox<T extends Number> extends Box<T> {
public double doubleValue() {
return getValue().doubleValue();
}
}
// 使用
NumberBox<Integer> integerBox = new NumberBox<>();
integerBox.setValue(100);
double value = integerBox.doubleValue();

2. 泛型的通配符捕获#

public class GenericCapture {
public static void reverse(List<?> list) {
reverseHelper(list);
}
private static <T> void reverseHelper(List<T> list) {
int size = list.size();
for (int i = 0; i < size / 2; i++) {
T temp = list.get(i);
list.set(i, list.get(size - 1 - i));
list.set(size - 1 - i, temp);
}
}
}

3. 泛型的类型推断#

Java 7+ 支持钻石操作符(<>),可以自动推断泛型类型。

// Java 7之前
Box<String> stringBox = new Box<String>();
// Java 7+
Box<String> stringBox = new Box<>(); // 自动推断类型

Java 8+ 支持方法引用和lambda表达式的类型推断。

4. 泛型的桥接方法#

当子类继承泛型父类或实现泛型接口时,编译器会生成桥接方法来保持类型擦除后的多态性。

public interface Comparable<T> {
int compareTo(T o);
}
public class String implements Comparable<String> {
@Override
public int compareTo(String o) {
// 实现
}
// 编译器生成的桥接方法
public int compareTo(Object o) {
return compareTo((String) o);
}
}

泛型的实际应用#

1. 集合框架#

Java的集合框架广泛使用泛型:

List<String> list = new ArrayList<>();
list.add("Hello");
String value = list.get(0);
Map<String, Integer> map = new HashMap<>();
map.put("key", 100);
Integer value = map.get("key");
Set<Integer> set = new HashSet<>();
set.add(1);
set.add(2);

2. 工具类#

public class Collections {
public static <T extends Comparable<? super T>> void sort(List<T> list) {
// 排序实现
}
public static <T> void shuffle(List<T> list) {
// 随机打乱实现
}
public static <T> T max(Collection<? extends T> coll, Comparator<? super T> comp) {
// 求最大值实现
}
}

3. 自定义泛型类和方法#

// 自定义泛型工具类
public class ArrayUtils {
public static <T> void swap(T[] array, int i, int j) {
if (array != null && i >= 0 && j >= 0 && i < array.length && j < array.length) {
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
public static <T> T[] copyOf(T[] original, int newLength) {
@SuppressWarnings("unchecked")
T[] copy = (T[]) new Object[newLength];
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy;
}
}

最佳实践#

  1. 使用有意义的类型参数名:使用T、E、K、V等约定的类型参数名,提高代码可读性。

  2. 指定合适的类型边界:使用extends和super指定类型边界,提高类型安全性。

  3. 优先使用泛型方法:对于不需要在类级别使用类型参数的情况,优先使用泛型方法。

  4. 使用通配符提高灵活性:在方法参数中使用通配符,提高方法的灵活性。

  5. 避免原始类型:避免使用原始类型(如List而不是List<?>),使用原始类型会失去泛型的类型安全优势。

  6. 使用@SuppressWarnings(“unchecked”):当不可避免需要类型转换时,使用@SuppressWarnings(“unchecked”)注解抑制警告。

  7. 考虑类型擦除的影响:编写泛型代码时,考虑类型擦除的影响,避免运行时错误。

  8. 使用Java 7+的钻石操作符:使用<>自动推断类型参数,简化代码。

  9. 为泛型类提供类型安全的工厂方法:提供类型安全的工厂方法,简化泛型类的创建。

  10. 测试泛型代码:编写测试用例,确保泛型代码在各种情况下都能正常工作。

常见陷阱#

  1. 原始类型的使用:使用原始类型(如List)而不是泛型类型(如List<?>),导致类型安全问题。

  2. 类型转换错误:在泛型代码中进行错误的类型转换,导致ClassCastException。

  3. 通配符使用不当:不正确使用上界和下界通配符,导致编译错误。

  4. 类型擦除的误解:不了解类型擦除的机制,导致运行时错误。

  5. 泛型数组的创建:尝试创建泛型数组,导致编译错误。

  6. 静态上下文中使用类型参数:在静态上下文中使用类型参数,导致编译错误。

  7. 类型参数的实例化:尝试创建类型参数的实例,导致编译错误。

  8. 过度使用泛型:过度使用泛型,导致代码复杂度过高。

总结#

泛型是Java编程中的重要特性,它提供了类型安全、代码复用和可读性等优势。通过使用泛型,我们可以编写更加灵活、安全和高效的代码。

本文介绍了Java泛型的基本语法、边界、类型擦除、限制和高级特性,以及泛型的实际应用和最佳实践。希望本文能够帮助你更好地理解和使用Java的泛型特性。

练习#

  1. 编写一个泛型类Pair<T, U>,用于存储两个不同类型的值。

  2. 编写一个泛型方法,用于反转任意类型的数组。

  3. 编写一个泛型接口,定义排序功能。

  4. 编写一个泛型类,实现栈(Stack)数据结构。

  5. 编写一个泛型方法,用于查找数组中的最大值。

  6. 编写一个泛型类,支持上界通配符,只能存储Number及其子类。

  7. 编写一个泛型方法,演示下界通配符的使用。

  8. 编写一个程序,演示类型擦除的影响。

  9. 编写一个程序,处理泛型的限制,如创建类型参数的实例。

  10. 编写一个程序,使用泛型集合框架,如List、Map、Set等。

通过这些练习,你将更加熟悉Java的泛型特性,为后续的学习做好准备。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

赞助
Java泛型编程详解
https://blog.vanilla.net.cn/posts/2026-02-05-java-generics/
作者
鹁鸪
发布于
2026-02-02
许可协议
CC BY-NC-SA 4.0

评论区

目录