泛型是Java语言中一项强大的特性,它提供了编译时类型安全检查机制,并消除了强制类型转换的需要。本指南将系统性地介绍Java泛型的各个方面,包括基本概念、高级用法、类型擦除机制以及最佳实践。
一、泛型基础
1. 为什么需要泛型
在没有泛型之前,Java使用Object类型来实现”通用”编程,这种方式存在两个主要问题:
- 类型不安全:需要强制类型转换,容易在运行时抛出ClassCastException
- 代码可读性差:无法从代码中直接看出集合中存储的元素类型
非泛型示例:
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0); // 需要强制类型转换
list.add(10); // 编译通过,但运行时可能出错
泛型示例:
List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0); // 自动类型推断
list.add(10); // 编译错误
2. 泛型类
泛型类是在类名后面添加类型参数声明的类:
public class Box<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
}
使用示例:
Box<Integer> integerBox = new Box<>();
integerBox.set(10);
Integer i = integerBox.get(); // 无需强制类型转换
Box<String> stringBox = new Box<>();
stringBox.set("Hello");
String s = stringBox.get();
3. 泛型方法
泛型方法是在方法返回类型前声明类型参数的方法:
public class Util {
public static <K,V> boolean compare(Pair<K,V> p1, Pair<K,V> p2) {
return p1.getKey().equals(p2.getKey()) &&
p1.getValue().equals(p2.getValue());
}
}
class Pair<K,V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
// getters and setters
}
使用示例:
Pair<Integer,String> p1 = new Pair<>(1,"apple");
Pair<Integer,String> p2 = new Pair<>(2,"pear");
boolean same = Util.<Integer,String>compare(p1,p2);
// 类型推断可以省略类型参数
boolean same = Util.compare(p1,p2);
二、泛型高级特性
1. 类型通配符
1.1 无界通配符
<?>
表示未知类型,适用于以下情况:
- 方法实现不依赖类型参数
- 只使用Object类提供的功能
public static void printList(List<?> list) {
for (Object elem : list) {
System.out.println(elem);
}
}
1.2 上界通配符
<? extends T>
表示T或T的子类型:
public static double sumOfList(List<? extends Number> list) {
double sum = 0.0;
for (Number num : list) {
sum += num.doubleValue();
}
return sum;
}
1.3 下界通配符
<? super T>
表示T或T的父类型:
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}
2. 泛型与继承
泛型类或接口之间不存在继承关系:
List<Object> list = new ArrayList<String>(); // 编译错误
但可以使用通配符建立关系:
List<? extends Number> list = new ArrayList<Integer>(); // 正确
3. 类型擦除
Java泛型是通过类型擦除实现的,编译器在编译时去掉类型参数信息:
- 无界类型参数(
<T>
)擦除为Object - 有界类型参数(
<T extends Number>
)擦除为上界(Number) - 泛型方法中的类型参数也会被擦除
示例:
public class Node<T> {
private T data;
private Node<T> next;
public Node(T data, Node<T> next) {
this.data = data;
this.next = next;
}
public T getData() { return data; }
}
擦除后:
public class Node {
private Object data;
private Node next;
public Node(Object data, Node next) {
this.data = data;
this.next = next;
}
public Object getData() { return data; }
}
三、泛型约束与限制
1. 不能实例化类型参数
public static <E> void append(List<E> list) {
E elem = new E(); // 编译错误
list.add(elem);
}
解决方案:使用Class对象或工厂方法
public static <E> void append(List<E> list, Class<E> cls) throws Exception {
E elem = cls.newInstance();
list.add(elem);
}
2. 不能创建泛型数组
List<Integer>[] arrayOfLists = new List<Integer>[10]; // 编译错误
解决方案:使用通配符类型
List<?>[] arrayOfLists = new List<?>[10];
3. 静态上下文中不能使用类型参数
public class MobileDevice<T> {
private static T os; // 编译错误
public static T getOS() { // 编译错误
return os;
}
}
4. 不能抛出或捕获泛型类实例
// 编译错误
try {
// ...
} catch (SomeException<Integer> e) {
// ...
}
// 编译错误
class MathException<T> extends Exception { /* ... */ }
四、泛型最佳实践
1. 泛型命名约定
- E – Element (集合中使用)
- K – Key (映射键)
- V – Value (映射值)
- N – Number (数字)
- T – Type (通用类型)
- S,U,V etc. – 第二、第三、第四类型
2. 优先使用泛型方法
当方法操作的是参数化类型时,优先考虑将其定义为泛型方法而非使用Object类型:
// 不推荐
public static List merge(List l1, List l2) {
List result = new ArrayList();
result.addAll(l1);
result.addAll(l2);
return result;
}
// 推荐
public static <T> List<T> merge(List<? extends T> l1, List<? extends T> l2) {
List<T> result = new ArrayList<>();
result.addAll(l1);
result.addAll(l2);
return result;
}
3. 合理使用通配符
PECS原则(Producer Extends, Consumer Super):
- 当参数化类型表示生产者(只读)时,使用
<? extends T>
- 当参数化类型表示消费者(只写)时,使用
<? super T>
// 生产者 - 只从中读取元素
public static <T> void copy(List<? extends T> src, List<? super T> dest) {
for (T item : src) {
dest.add(item);
}
}
4. 避免原生类型
总是使用泛型类型,避免使用原生类型(不带类型参数的泛型类):
List list = new ArrayList(); // 原生类型 - 不推荐
List<String> list = new ArrayList<>(); // 泛型类型 - 推荐
五、泛型实际应用示例
1. 泛型缓存系统
public class Cache<K,V> {
private final Map<K,V> cache = new HashMap<>();
public void put(K key, V value) {
cache.put(key, value);
}
public V get(K key) {
return cache.get(key);
}
public void remove(K key) {
cache.remove(key);
}
public void clear() {
cache.clear();
}
public int size() {
return cache.size();
}
}
2. 泛型DAO接口
public interface GenericDao<T, ID> {
T findById(ID id);
List<T> findAll();
ID save(T entity);
void update(T entity);
void delete(T entity);
void deleteById(ID id);
}
// 实现示例
public class UserDao implements GenericDao<User, Long> {
@Override
public User findById(Long id) {
// 实现查找逻辑
}
// 其他方法实现...
}
3. 泛型工具类
public class CollectionUtils {
// 安全的类型转换
public static <T> List<T> castList(List<?> list, Class<T> type) {
if (list == null) {
return null;
}
for (Object o : list) {
if (o != null && !type.isInstance(o)) {
throw new ClassCastException("List contains element of type " +
o.getClass() + " instead of " + type);
}
}
@SuppressWarnings("unchecked")
List<T> result = (List<T>) list;
return result;
}
// 合并多个集合
@SafeVarargs
public static <T> List<T> mergeLists(List<? extends T>... lists) {
List<T> result = new ArrayList<>();
for (List<? extends T> list : lists) {
result.addAll(list);
}
return result;
}
}
六、Java 7+对泛型的改进
1. 钻石操作符(<>)
Java 7引入了钻石操作符,允许在构造泛型对象时省略类型参数:
// Java 6及以前
List<String> list = new ArrayList<String>();
// Java 7+
List<String> list = new ArrayList<>();
2. try-with-resources中的泛型
// 泛型类实现AutoCloseable
class Resource<T> implements AutoCloseable {
private T resource;
public Resource(T resource) {
this.resource = resource;
}
public T get() {
return resource;
}
@Override
public void close() {
if (resource instanceof Closeable) {
try {
((Closeable)resource).close();
} catch (IOException e) {
// 处理异常
}
}
}
}
// 使用示例
try (Resource<InputStream> res = new Resource<>(new FileInputStream("test.txt"))) {
InputStream is = res.get();
// 使用输入流
}
3. 改进的类型推断
Java 8进一步改进了泛型方法调用时的类型推断:
// Java 7需要指定类型参数
List<String> list = Collections.<String>emptyList();
// Java 8可以自动推断
List<String> list = Collections.emptyList();
七、总结
Java泛型是类型安全的强大工具,正确使用泛型可以:
- 提高代码的类型安全性,减少运行时ClassCastException
- 消除强制类型转换,使代码更清晰
- 实现更通用的算法和数据结构
- 提高代码的可重用性和可读性
关键要点:
- 理解类型擦除机制及其限制
- 掌握通配符的使用(PECS原则)
- 遵循泛型最佳实践
- 合理利用Java 7+对泛型的改进
通过本指南的学习,你应该能够熟练地在项目中使用泛型,编写出更安全、更灵活的Java代码。