Java泛型梳理
一、 泛型是什么?为什么需要它? (The “What” and “Why”)
1.1 核心概念:
泛型(Generics)是 JDK 5 引入的一个核心特性,它允许在定义类、接口或方法时使用类型参数(Type Parameter)。在使用时(如声明变量、创建实例时)再指定具体的类型实参(Type Argument)。简单说,就是参数化类型。
1.2 主要目的与优势(解决什么问题):
- 类型安全(Type Safety):在编译期检查类型,将运行时的 ClassCastException 转移到了编译期。这是最重要的优点。
- 没有泛型前:
List 只持有一个 Object 类型的引用,取出的元素需要手动强制转换,容易出错。
- 有泛型后:
List<String> 明确告知编译器它只持有 String,如果存入 Integer,编译器会直接报错。
- 消除强制转换(Eliminates Casts):代码更简洁,可读性更强。
- 没有泛型:
String s = (String) list.get(0);
- 有泛型:
String s = list.get(0); // 自动转换
- 代码复用(Code Reusability):可以编写更通用、更灵活的代码。例如,一个
Box<T> 类可以存放任何类型的对象,而不需要为每种类型(BoxString, BoxInteger)都写一个单独的类。
1.3 解决的核心问题
- 类型安全问题:将运行时ClassCastException转为编译期错误
- 代码简洁性问题:消除繁琐的类型强制转换
- 代码复用问题:编写真正通用的算法和数据结构
1.4 类型参数命名约定
- E - Element(集合元素)
- K - Key(键)
- V - Value(值)
- N - Number(数字)
- T - Type(类型)
- S, U, V - 第二、第三、第四类型
二、泛型使用详解
2.1 泛型类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class Container<T> { private T value; public Container(T value) { this.value = value; } public T getValue() { return value; } public void setValue(T value) { this.value = value; } }
public class Pair<K, V> { private K key; private V value; public Pair(K key, V value) { this.key = key; this.value = value; } }
|
2.2 泛型接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public interface Generator<T> { T next(); }
public class StringGenerator implements Generator<String> { @Override public String next() { return "Generated String"; } }
public class GenericGenerator<T> implements Generator<T> { private Class<T> type; public GenericGenerator(Class<T> type) { this.type = type; } @Override public T next() { try { return type.newInstance(); } catch (Exception e) { throw new RuntimeException(e); } } }
|
2.3 泛型方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public class ArrayUtils { public static <T> T getMiddle(T[] array) { return array[array.length / 2]; } public static <T> List<T> makeList(T... args) { List<T> result = new ArrayList<>(); for (T item : args) { result.add(item); } return result; } public static <T extends Comparable<T>> T max(Collection<T> coll) { if (coll.isEmpty()) return null; T candidate = coll.iterator().next(); for (T element : coll) { if (element.compareTo(candidate) > 0) { candidate = element; } } return candidate; } }
|
三、通配符与边界深入解析
3.1 通配符类型系统
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public void processElements(List<?> list) { for (Object element : list) { System.out.println(element); } }
public static double sumOfList(List<? extends Number> list) { double sum = 0.0; for (Number number : list) { sum += number.doubleValue(); } return sum; }
public static void addNumbers(List<? super Integer> list) { for (int i = 1; i <= 5; i++) { list.add(i); } }
|
3.2 PECS原则深度解析
Producer-Extends, Consumer-Super原则的实际应用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public static <T> void copy(List<? super T> dest, List<? extends T> src) { for (int i = 0; i < src.size(); i++) { dest.set(i, src.get(i)); } }
public class Collections { public static <T> void copy(List<? super T> dest, List<? extends T> src) { } public static <T> T max(Collection<? extends T> coll, Comparator<? super T> comp) { } }
|
3.3 通配符捕获
1 2 3 4 5 6 7 8 9 10 11
| public void swap(List<?> list, int i, int j) { swapHelper(list, i, j); }
private <E> void swapHelper(List<E> list, int i, int j) { E temp = list.get(i); list.set(i, list.get(j)); list.set(j, temp); }
|
四、类型擦除机制深度分析
4.1 擦除规则详解
无界类型参数:擦除为Object
1 2 3 4
| class Box<T> { T value; }
class Box { Object value; }
|
有界类型参数:擦除为第一个边界
1 2 3 4
| class NumberBox<T extends Number & Comparable<T>> { T value; }
class NumberBox { Number value; }
|
4.2 桥方法机制
编译器通过生成桥方法保持多态性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class Node<T> { public T data; public void setData(T data) { this.data = data; } }
class MyNode extends Node<Integer> { public void setData(Integer data) { super.setData(data); } }
class MyNode extends Node { public void setData(Integer data) { super.setData(data); } public void setData(Object data) { setData((Integer) data); } }
|
4.3 泛型与数组的深层问题
1 2 3 4 5 6 7 8
| List<String>[] stringLists = new List<String>[1];
List<Integer> intList = Arrays.asList(42); Object[] objects = stringLists; objects[0] = intList; String s = stringLists[0].get(0);
|
五、高级特性与模式
5.1 自限定类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public abstract class Comparable<T extends Comparable<T>> implements java.lang.Comparable<T> { @Override public int compareTo(T other) { return 0; } }
class Student extends Comparable<Student> { private String name; @Override public int compareTo(Student other) { return this.name.compareTo(other.name); } }
|
5.2 递归类型边界
1 2 3 4 5 6 7 8 9
| public static <T extends Comparable<T>> T max(Collection<T> coll) { T candidate = null; for (T element : coll) { if (candidate == null || element.compareTo(candidate) > 0) { candidate = element; } } return candidate; }
|
5.3 类型安全的异构容器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class TypeSafeContainer { private Map<Class<?>, Object> container = new HashMap<>(); public <T> void put(Class<T> type, T instance) { container.put(Objects.requireNonNull(type), type.cast(instance)); } public <T> T get(Class<T> type) { return type.cast(container.get(type)); } }
TypeSafeContainer container = new TypeSafeContainer(); container.put(String.class, "Hello"); container.put(Integer.class, 42);
String s = container.get(String.class); Integer i = container.get(Integer.class);
|
六、实战中的泛型应用
6.1 构建类型安全的Builder模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public class NutritionFacts { private final int servingSize; private final int servings; private final int calories; public static class Builder<T extends Builder<T>> { private int servingSize; private int servings; private int calories = 0; public Builder(int servingSize, int servings) { this.servingSize = servingSize; this.servings = servings; } public T calories(int val) { calories = val; return self(); } protected T self() { return (T) this; } public NutritionFacts build() { return new NutritionFacts(this); } } protected NutritionFacts(Builder<?> builder) { servingSize = builder.servingSize; servings = builder.servings; calories = builder.calories; } }
|
6.2 泛型DAO模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public interface GenericDao<T, ID> { T findById(ID id); List<T> findAll(); T save(T entity); void delete(T entity); void deleteById(ID id); }
public abstract class AbstractJpaDao<T, ID> implements GenericDao<T, ID> { private final Class<T> persistentClass; protected AbstractJpaDao(Class<T> persistentClass) { this.persistentClass = persistentClass; } @Override public T findById(ID id) { return getEntityManager().find(persistentClass, id); } }
public class UserDao extends AbstractJpaDao<User, Long> { public UserDao() { super(User.class); } }
|
七、高频面试题深度解析
7.1 类型擦除相关
Q: Java泛型是如何实现的?有什么优缺点?
A: 通过类型擦除实现,在编译期进行类型检查,运行时擦除类型信息。优点是向后兼容,JVM无需修改;缺点是运行时类型信息丢失,某些高级特性无法实现。
Q: 如何绕过类型擦除获取泛型信息?
A: 通过反射获取ParameterizedType:
1 2 3 4 5 6 7 8 9 10 11
| public class GenericTypeResolver { public static Class<?> getGenericType(Class<?> clazz) { Type genericSuperclass = clazz.getGenericSuperclass(); if (genericSuperclass instanceof ParameterizedType) { Type[] actualTypeArguments = ((ParameterizedType) genericSuperclass).getActualTypeArguments(); return (Class<?>) actualTypeArguments[0]; } return Object.class; } }
|
7.2 通配符应用场景
Q: List<? extends Number> 和 List<T extends Number> 的区别?
A: 前者是通配符用法,用于变量声明;后者是类型参数声明,用于类或方法定义。通配符更灵活但限制更多。
7.3 设计模式中的泛型应用
Q: 如何使用泛型实现类型安全的工厂模式?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public interface Factory<T> { T create(); }
public class StringFactory implements Factory<String> { @Override public String create() { return new String(); } }
public class FactoryCreator { private Map<Class<?>, Factory<?>> factories = new HashMap<>(); public <T> void registerFactory(Class<T> type, Factory<T> factory) { factories.put(type, factory); } @SuppressWarnings("unchecked") public <T> T create(Class<T> type) { Factory<T> factory = (Factory<T>) factories.get(type); if (factory == null) { throw new IllegalArgumentException("No factory registered for " + type); } return factory.create(); } }
|
八、性能考量与最佳实践
8.1 泛型对性能的影响
- 编译期:泛型代码生成更多字节码(桥方法等)
- 运行期:类型擦除后与普通代码性能几乎相同
- 内存使用:无额外开销,与使用Object+强制转换相同
8.2 最佳实践
- 优先使用泛型方法而不是将整个类泛型化
- 避免使用原始类型,失去类型安全优势
- 合理使用通配符提高API灵活性
- 注意类型擦除的影响,不要依赖运行时类型信息
- 使用@SuppressWarnings(“unchecked”)要谨慎,确保确实安全