23 泛型——类型安全的参数化编程

目录

🟡 23 泛型------类型安全的参数化编程

更新日期 :2026年5月 | Java入门到精通系列 · 第三阶段·核心进阶

© 版权声明:本文为原创技术文章,转载请联系作者并注明出处。



一、为什么需要泛型

1.1 没有泛型的时代

java 复制代码
// Java 5之前:没有泛型
import java.util.ArrayList;
import java.util.List;

public class WithoutGenerics {
    public static void main(String[] args) {
        List list = new ArrayList();  // 可以放任何类型
        list.add("Hello");
        list.add(123);
        list.add(new Object());

        // 取出时需要强制类型转换
        String s = (String) list.get(0);  // OK
        String s2 = (String) list.get(1); // ClassCastException!

        // 编译器无法检查类型安全
    }
}

1.2 泛型的好处

java 复制代码
// Java 5+:使用泛型
public class WithGenerics {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();  // 只能放String
        list.add("Hello");
        // list.add(123);  // 编译错误!类型安全

        String s = list.get(0);  // 无需强制转换

        // 编译期类型检查,运行期更安全
    }
}
好处 说明
类型安全 编译期检查类型,避免ClassCastException
消除强转 不需要手动类型转换
代码复用 一份代码适用于多种类型
文档化 类型参数本身就是文档

二、泛型类

2.1 基本泛型类

java 复制代码
// 单类型参数
public class Box<T> {
    private T content;

    public Box(T content) {
        this.content = content;
    }

    public T getContent() {
        return content;
    }

    public void setContent(T content) {
        this.content = content;
    }

    @Override
    public String toString() {
        return "Box{" + content + "}";
    }
}

// 使用
Box<String> stringBox = new Box<>("Hello");
String value = stringBox.getContent();  // 无需强转

Box<Integer> intBox = new Box<>(42);
int num = intBox.getContent();  // 自动拆箱

2.2 多类型参数

java 复制代码
// 键值对
public class Pair<K, V> {
    private K key;
    private V value;

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public K getKey() { return key; }
    public V getValue() { return value; }

    public void setKey(K key) { this.key = key; }
    public void setValue(V value) { this.value = value; }

    @Override
    public String toString() {
        return "(" + key + ", " + value + ")";
    }
}

// 使用
Pair<String, Integer> nameAge = new Pair<>("Alice", 25);
Pair<Integer, Boolean> idActive = new Pair<>(1001, true);

2.3 实际应用:通用响应类

java 复制代码
// API统一响应封装
public class ApiResponse<T> {
    private int code;
    private String message;
    private T data;
    private long timestamp;

    private ApiResponse(int code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
        this.timestamp = System.currentTimeMillis();
    }

    public static <T> ApiResponse<T> ok(T data) {
        return new ApiResponse<>(200, "success", data);
    }

    public static <T> ApiResponse<T> error(String message) {
        return new ApiResponse<>(500, message, null);
    }

    public static <T> ApiResponse<T> error(int code, String message) {
        return new ApiResponse<>(code, message, null);
    }

    // getters...
}

// 使用
ApiResponse<User> response = ApiResponse.ok(new User("Alice", 25));
ApiResponse<List<Product>> listResponse = ApiResponse.ok(products);
ApiResponse<?> errorResponse = ApiResponse.error("服务器错误");

2.4 泛型类的继承

java 复制代码
// 泛型父类
public abstract class Repository<T, ID> {
    private Map<ID, T> store = new HashMap<>();

    public void save(ID id, T entity) {
        store.put(id, entity);
    }

    public T findById(ID id) {
        return store.get(id);
    }

    public List<T> findAll() {
        return new ArrayList<>(store.values());
    }
}

// 具体子类(指定类型)
public class UserRepository extends Repository<User, String> {
    public List<User> findByName(String name) {
        return findAll().stream()
                .filter(u -> u.getName().equals(name))
                .collect(Collectors.toList());
    }
}

// 保持泛型的子类
public abstract class CacheRepository<T, ID> extends Repository<T, ID> {
    private Map<ID, T> cache = new HashMap<>();

    @Override
    public T findById(ID id) {
        return cache.computeIfAbsent(id, super::findById);
    }
}

三、泛型接口

3.1 基本泛型接口

java 复制代码
// 比较器接口
public interface Comparator<T> {
    int compare(T o1, T o2);
}

// 实现方式1:指定具体类型
public class StudentAgeComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return Integer.compare(o1.getAge(), o2.getAge());
    }
}

// 实现方式2:保持泛型
public class ReverseComparator<T> implements Comparator<T> {
    private final Comparator<T> original;

    public ReverseComparator(Comparator<T> original) {
        this.original = original;
    }

    @Override
    public int compare(T o1, T o2) {
        return original.compare(o2, o1);  // 反转
    }
}

3.2 实际应用:DAO模式

java 复制代码
// 泛型DAO接口
public interface GenericDao<T, ID> {
    T findById(ID id);
    List<T> findAll();
    void save(T entity);
    void update(T entity);
    void delete(ID id);
    boolean exists(ID id);
}

// 具体实现
public class UserDao implements GenericDao<User, Long> {
    @Override
    public User findById(Long id) {
        // 数据库查询逻辑
        return null;
    }

    @Override
    public List<User> findAll() {
        return Collections.emptyList();
    }

    @Override
    public void save(User entity) {
        // 保存逻辑
    }

    @Override
    public void update(User entity) {
        // 更新逻辑
    }

    @Override
    public void delete(Long id) {
        // 删除逻辑
    }

    @Override
    public boolean exists(Long id) {
        return findById(id) != null;
    }
}

四、泛型方法

4.1 基本泛型方法

java 复制代码
public class GenericMethods {
    // 泛型方法:<T>声明在返回类型之前
    public static <T> void printArray(T[] array) {
        for (T element : array) {
            System.out.print(element + " ");
        }
        System.out.println();
    }

    // 泛型方法返回泛型类型
    public static <T> T getFirst(List<T> list) {
        if (list == null || list.isEmpty()) {
            return null;
        }
        return list.get(0);
    }

    // 多类型参数
    public static <K, V> Map<K, V> zipToMap(K[] keys, V[] values) {
        if (keys.length != values.length) {
            throw new IllegalArgumentException("keys和values长度必须一致");
        }
        Map<K, V> map = new HashMap<>();
        for (int i = 0; i < keys.length; i++) {
            map.put(keys[i], values[i]);
        }
        return map;
    }

    public static void main(String[] args) {
        // 使用泛型方法
        Integer[] nums = {1, 2, 3, 4, 5};
        String[] strs = {"A", "B", "C"};
        printArray(nums);  // 类型推断
        printArray(strs);

        List<String> names = List.of("Alice", "Bob", "Charlie");
        String first = getFirst(names);  // 推断为String

        String[] keys = {"name", "age", "city"};
        String[] values = {"Alice", "25", "Beijing"};
        Map<String, String> person = zipToMap(keys, values);
        System.out.println(person);
    }
}

4.2 静态泛型方法

java 复制代码
public class CollectionUtils {
    // 注意:静态方法必须声明自己的类型参数
    public static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
        List<T> result = new ArrayList<>();
        for (T item : list) {
            if (predicate.test(item)) {
                result.add(item);
            }
        }
        return result;
    }

    public static <T> List<T> distinct(List<T> list) {
        return new ArrayList<>(new LinkedHashSet<>(list));
    }

    public static <T extends Comparable<T>> T max(List<T> list) {
        if (list == null || list.isEmpty()) {
            throw new IllegalArgumentException("列表不能为空");
        }
        T max = list.get(0);
        for (T item : list) {
            if (item.compareTo(max) > 0) {
                max = item;
            }
        }
        return max;
    }

    // 使用示例
    public static void main(String[] args) {
        List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        List<Integer> evens = filter(numbers, n -> n % 2 == 0);
        System.out.println("偶数: " + evens);  // [2, 4, 6, 8, 10]

        Integer max = max(numbers);
        System.out.println("最大值: " + max);  // 10
    }
}

五、类型通配符

5.1 无界通配符 <?>

java 复制代码
// ? 表示未知类型
public static void printList(List<?> list) {
    for (Object item : list) {
        System.out.println(item);
    }
}

// 使用
List<String> strings = List.of("A", "B");
List<Integer> integers = List.of(1, 2);
printList(strings);  // OK
printList(integers); // OK

// List<?> vs List<Object>
List<?> unknownList = new ArrayList<String>();  // OK
// List<Object> objectList = new ArrayList<String>();  // 编译错误!

5.2 上界通配符 <? extends T>

java 复制代码
// ? extends Number:Number或其子类
public static double sum(List<? extends Number> list) {
    double total = 0;
    for (Number num : list) {
        total += num.doubleValue();  // 可以读取为Number
    }
    return total;
}

// 使用
List<Integer> ints = List.of(1, 2, 3);
List<Double> doubles = List.of(1.5, 2.5, 3.5);
System.out.println(sum(ints));     // 6.0
System.out.println(sum(doubles));  // 7.5

// 限制:不能添加元素(除了null)
public static void addNumber(List<? extends Number> list) {
    // list.add(1);        // 编译错误!不知道具体类型
    // list.add(1.0);      // 编译错误!
    list.add(null);         // OK,null是所有类型的合法值
}

5.3 下界通配符 <? super T>

java 复制代码
// ? super Integer:Integer或其父类
public static void addIntegers(List<? super Integer> list) {
    list.add(1);
    list.add(2);
    list.add(3);
    // 可以添加Integer及其子类
}

// 使用
List<Number> numbers = new ArrayList<>();
addIntegers(numbers);  // OK,Number是Integer的父类
System.out.println(numbers);  // [1, 2, 3]

List<Object> objects = new ArrayList<>();
addIntegers(objects);  // OK,Object是Integer的祖先
System.out.println(objects);  // [1, 2, 3]

// 限制:读取只能是Object
public static void readFromList(List<? super Integer> list) {
    Object obj = list.get(0);  // 只能读取为Object
    // Integer num = list.get(0);  // 编译错误!
}

5.4 PECS原则(Producer Extends, Consumer Super)

java 复制代码
// PECS原则:生产者用extends,消费者用super

// 生产者(提供数据):用 extends
public static <T> void copy(List<? extends T> src, List<? super T> dest) {
    for (T item : src) {  // 从src读取(生产者)
        dest.add(item);    // 向dest写入(消费者)
    }
}

// 实际应用
List<Integer> source = List.of(1, 2, 3);
List<Number> destination = new ArrayList<>();
copy(source, destination);  // Integer extends Number
System.out.println(destination);  // [1, 2, 3]

// Collections.copy的签名
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
    // src是生产者(extends),dest是消费者(super)
}

5.5 通配符对比表

通配符 含义 使用场景
<?> 未知类型 Object 不能写 只读操作
<? extends T> T的子类 T 不能写 生产者(读取)
<? super T> T的父类 Object T 消费者(写入)

六、泛型约束

6.1 有界类型参数

java 复制代码
// T必须是Comparable的实现类
public static <T extends Comparable<T>> T findMax(List<T> list) {
    T max = list.get(0);
    for (T item : list) {
        if (item.compareTo(max) > 0) {
            max = item;
        }
    }
    return max;
}

// 多重约束:T必须同时满足多个条件
public static <T extends Comparable<T> & Serializable> void process(T item) {
    // T必须同时实现Comparable和Serializable
    System.out.println(item.compareTo(item));
}

6.2 常见约束示例

java 复制代码
// 约束为Number的子类
public class MathUtils {
    public static <T extends Number> double average(List<T> numbers) {
        double sum = 0;
        for (T num : numbers) {
            sum += num.doubleValue();
        }
        return sum / numbers.size();
    }
}

// 约束为Enum的子类
public static <T extends Enum<T>> T parseEnum(Class<T> enumType, String name) {
    return Enum.valueOf(enumType, name);
}

// 实际应用:Builder模式中的类型约束
public abstract class Builder<T extends Builder<T>> {
    protected String name;

    @SuppressWarnings("unchecked")
    public T withName(String name) {
        this.name = name;
        return (T) this;  // 返回子类类型
    }

    public abstract T self();
}

public class UserBuilder extends Builder<UserBuilder> {
    private int age;

    public UserBuilder withAge(int age) {
        this.age = age;
        return this;
    }

    @Override
    public UserBuilder self() {
        return this;
    }

    public User build() {
        return new User(name, age);
    }
}

// 链式调用
User user = new UserBuilder()
        .withName("Alice")
        .withAge(25)
        .build();

七、类型擦除

7.1 什么是类型擦除?

Java的泛型是编译期特性,在运行时会被擦除(Type Erasure)。

java 复制代码
// 编译前(源代码)
List<String> strings = new ArrayList<>();
List<Integer> integers = new ArrayList<>();

// 编译后(字节码)
List strings = new ArrayList();
List integers = new ArrayList();

// 运行时无法区分
System.out.println(strings.getClass() == integers.getClass());  // true

7.2 类型擦除的规则

java 复制代码
// 规则1:无界泛型擦除为Object
public class Box<T> {
    private T value;
    // 编译后:
    // private Object value;
}

// 规则2:有界泛型擦除为上界
public class NumberBox<T extends Number> {
    private T value;
    // 编译后:
    // private Number value;
}

// 规则3:调用泛型方法时插入强制转换
List<String> list = new ArrayList<>();
list.add("Hello");
String s = list.get(0);
// 编译后:
// list.add("Hello");
// String s = (String) list.get(0);  // 插入强转

7.3 类型擦除的限制

java 复制代码
// 限制1:不能使用基本类型
// List<int> list;  // 编译错误!必须用List<Integer>

// 限制2:不能instanceof泛型类型
public <T> void check(Object obj) {
    // if (obj instanceof T) {}  // 编译错误!

    // 解决方案:传递Class对象
}

// 限制3:不能创建泛型数组
// T[] array = new T[10];  // 编译错误!

// 解决方案:使用Array.newInstance
public <T> T[] createArray(Class<T> componentType, int size) {
    @SuppressWarnings("unchecked")
    T[] array = (T[]) Array.newInstance(componentType, size);
    return array;
}

// 限制4:不能在catch中使用泛型异常
// catch (T e) {}  // 编译错误!

7.4 桥接方法

java 复制代码
// 泛型方法重写时的桥接方法
public interface Processor<T> {
    void process(T item);
}

public class StringProcessor implements Processor<String> {
    @Override
    public void process(String item) {
        System.out.println("Processing: " + item);
    }
    // 编译器生成桥接方法:
    // public void process(Object item) {
    //     process((String) item);
    // }
}

八、泛型的高级用法

8.1 递归类型约束

java 复制代码
// 常见于Builder模式和枚举中
public abstract class Enum<E extends Enum<E>> {
    private final String name;
    private final int ordinal;

    protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }

    public final int compareTo(E o) {
        // 自己和自己比较
    }
}

// Builder模式
public abstract class Builder<T extends Builder<T>> {
    @SuppressWarnings("unchecked")
    public T self() {
        return (T) this;
    }
}

8.2 泛型与反射

java 复制代码
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public abstract class GenericDao<T> {
    private final Class<T> entityClass;

    @SuppressWarnings("unchecked")
    public GenericDao() {
        // 通过反射获取泛型的实际类型
        Type genericSuperclass = getClass().getGenericSuperclass();
        ParameterizedType paramType = (ParameterizedType) genericSuperclass;
        this.entityClass = (Class<T>) paramType.getActualTypeArguments()[0];
    }

    public T newInstance() throws InstantiationException, IllegalAccessException {
        return entityClass.newInstance();
    }

    public Class<T> getEntityClass() {
        return entityClass;
    }
}

// 使用
public class UserDao extends GenericDao<User> {
    // entityClass = User.class
}

// 运行时获取泛型信息
UserDao dao = new UserDao();
System.out.println(dao.getEntityClass());  // class User

九、常见面试题解析

面试题1:泛型的类型擦除是什么?

:Java泛型是编译期特性,编译后类型参数会被擦除。无界泛型擦除为Object,有界泛型擦除为上界。运行时无法获取泛型的实际类型参数。

面试题2:List<String>List<Integer>在运行时有区别吗?

:没有区别。由于类型擦除,两者在运行时都是ListList<String>.class == List<Integer>.class为true。

面试题3:<? extends T><? super T>的区别?

特征 <? extends T> <? super T>
读取 T类型 Object类型
写入 不能写入 T类型
场景 生产者 消费者
记忆 上界通配符 下界通配符

面试题4:为什么不能创建泛型数组?

java 复制代码
// 因为数组在运行时知道元素类型,泛型在运行时被擦除
// 如果允许,会导致类型安全问题
T[] array = new T[10];  // 假设T=String
Object[] objArray = array;
objArray[0] = 123;  // 运行时不会报错(因为实际是Object[])
String s = array[0];  // ClassCastException!

// 正确做法
T[] array = (T[]) new Object[10];  // 有unchecked警告
// 或者使用Class<T>创建

十、总结与下篇预告

本篇核心要点

要点 说明
泛型类 class Box<T>,类型参数化
泛型方法 <T> void method(T t),方法级泛型
泛型接口 interface Repository<T, ID>
通配符 <?><? extends T><? super T>
PECS原则 Producer Extends, Consumer Super
类型擦除 编译期特性,运行时擦除
有界约束 <T extends Comparable<T>>

🤔 互动问题

  1. 为什么Java选择类型擦除而不是具体化泛型(reified generics)?
  2. Kotlin的泛型和Java的泛型有什么区别?
  3. Class<T>在泛型编程中有什么重要作用?

📖 下篇预告

下一篇我们将学习**《IO流:字节流与字符流》**,深入了解Java的IO体系结构、InputStream/OutputStream、Reader/Writer的使用。


参考资料

相关推荐
hoho_121 小时前
如何替换jar包中依赖的其他jar
java·pycharm·jar
码语智行1 小时前
接口请求处理流程
java
我命由我123452 小时前
Kotlin 开发 - Kotlin 反引号转义关键字
android·java·开发语言·java-ee·kotlin·android jetpack·android runtime
艾利克斯冰2 小时前
Java设计模式-工厂方法模式
java
中草药z2 小时前
【RAG】工程化实战:全链路原理复盘 + 方案选型 + 实战高阶玩法
java·深度学习·机器学习·阿里云·rag·springai
学计算机的计算基2 小时前
MySQL 性能调优面试复习:Explain、索引、慢查询、缓存和架构优化
java·数据库·笔记·mysql
Hillain2 小时前
软件设计师设计模式
java·开发语言·经验分享·笔记·算法·设计模式·软考
影寂ldy2 小时前
C# 泛型方法
java·前端·c#
摇滚侠2 小时前
Spring 零基础入门到进阶 IOC 概述 11 - 13
java·后端·spring