Java泛型编程全面指南


泛型是Java语言中一项强大的特性,它提供了编译时类型安全检查机制,并消除了强制类型转换的需要。本指南将系统性地介绍Java泛型的各个方面,包括基本概念、高级用法、类型擦除机制以及最佳实践。

一、泛型基础

1. 为什么需要泛型

在没有泛型之前,Java使用Object类型来实现”通用”编程,这种方式存在两个主要问题:

  1. 类型不安全:需要强制类型转换,容易在运行时抛出ClassCastException
  2. 代码可读性差:无法从代码中直接看出集合中存储的元素类型

非泛型示例

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泛型是类型安全的强大工具,正确使用泛型可以:

  1. 提高代码的类型安全性,减少运行时ClassCastException
  2. 消除强制类型转换,使代码更清晰
  3. 实现更通用的算法和数据结构
  4. 提高代码的可重用性和可读性

关键要点:

  • 理解类型擦除机制及其限制
  • 掌握通配符的使用(PECS原则)
  • 遵循泛型最佳实践
  • 合理利用Java 7+对泛型的改进

通过本指南的学习,你应该能够熟练地在项目中使用泛型,编写出更安全、更灵活的Java代码。

,

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注