Java-泛型

Java-泛型

_
本文内容由 AI 辅助生成。

一、什么是泛型?

泛型(Generics) 是一种“参数化类型”的机制。
它允许你在定义类、接口或方法时,使用一个或多个类型参数(如 TEKV 等),在使用时再指定具体类型。

目的

  • 编译期类型安全检查

  • 避免强制类型转换

  • 提高代码复用性


二、泛型的基本用法

1. 泛型类(Generic Class)

// 定义一个泛型类 Box<T>
// T 是类型参数,代表任意引用类型(不能是基本类型)
public class Box<T> {
    // 成员变量 value 的类型由 T 决定
    private T value;

    // 设置值,参数类型为 T
    public void set(T value) {
        this.value = value;
    }

    // 获取值,返回类型为 T
    public T get() {
        return value;
    }
}

使用示例:

public class Main {
    public static void main(String[] args) {
        // 创建一个只能存放 String 的 Box
        Box<String> stringBox = new Box<>();
        stringBox.set("Hello, Generics!");

        // 无需强制转换,直接获取 String 类型
        String content = stringBox.get();
        System.out.println(content); // 输出: Hello, Generics!

        // 尝试放入 Integer 会编译报错
        // stringBox.set(123); // ❌ 编译错误:Integer 不能赋值给 String
    }
}

2. 泛型接口(Generic Interface)

// 定义一个泛型接口,用于比较两个同类型对象
public interface Comparable<T> {
    // compareTo 方法接收一个 T 类型参数,返回 int
    int compareTo(T other);
}

实现示例:

// 实现 Comparable 接口,指定 T 为 Person
class Person implements Comparable<Person> {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 实现 compareTo 方法,按年龄比较
    @Override
    public int compareTo(Person other) {
        return Integer.compare(this.age, other.age);
    }

    @Override
    public String toString() {
        return name + "(" + age + ")";
    }
}

3. 泛型方法(Generic Method)

注意:泛型方法的类型参数在返回值前声明,与类是否泛型无关。

public class GenericMethodExample {

    // <T> 表示这是一个泛型方法,T 是类型参数
    // 该方法可以接受任意类型的两个参数并判断是否相等
    public static <T> boolean isEqual(T a, T b) {
        // 如果两个都为 null,认为相等
        if (a == null && b == null) return true;
        // 如果其中一个为 null,不相等
        if (a == null || b == null) return false;
        // 否则调用 equals 比较
        return a.equals(b);
    }

    // 带上界约束的泛型方法:T 必须是 Number 或其子类
    public static <T extends Number> double add(T a, T b) {
        // 利用 Number 的 doubleValue() 方法统一处理
        return a.doubleValue() + b.doubleValue();
    }
}

调用示例:

public class Main {
    public static void main(String[] args) {
        // 自动类型推断:编译器知道 T 是 String
        boolean same = GenericMethodExample.isEqual("Java", "Java");
        System.out.println(same); // true

        // 显式指定类型(较少用)
        boolean same2 = GenericMethodExample.<Integer>isEqual(10, 20);
        System.out.println(same2); // false

        // 调用带边界的方法
        double sum = GenericMethodExample.add(3.14, 2); // 2 自动装箱为 Integer
        System.out.println(sum); // 5.14
    }
}

4. 多个类型参数

// 定义一个键值对类,支持任意类型的 key 和 value
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> score = new Pair<>("张三", 95);
System.out.println(score); // (张三, 95)

三、泛型边界(Bounds)

1. 上界(Upper Bound):<T extends SomeClass>

// T 必须是 Number 的子类(如 Integer, Double 等)
public static <T extends Number> void printDoubleValue(T number) {
    // 因为 T 是 Number 的子类,所以可以安全调用 doubleValue()
    System.out.println("数值的 double 形式: " + number.doubleValue());
}

⚠️ 注意:extends 在这里表示“继承或实现”,可用于类或接口。

2. 多重边界(Multiple Bounds)

// T 必须同时继承 Cloneable 并实现 Comparable<T>
public static <T extends Cloneable & Comparable<T>> void process(T obj) {
    // 可以调用 clone() 和 compareTo()
    System.out.println("对象可比较且可克隆");
}

🔸 多重边界中,类必须写在前面,接口用 & 连接。


四、通配符(Wildcards)

通配符 ? 表示“未知类型”,常用于方法参数,增强灵活性。

1. 无界通配符 <?>

// 接受任何类型的 List
public static void printList(List<?> list) {
    // 只能读取为 Object(最安全的公共父类)
    for (Object item : list) {
        System.out.println(item);
    }
    // 不能添加元素(除了 null)
    // list.add("test"); // ❌ 编译错误
}

2. 上界通配符 <? extends T> —— 生产者(Producer)

// 接受 List<Number>、List<Integer>、List<Double> 等
public static double sumOfList(List<? extends Number> list) {
    double sum = 0.0;
    for (Number num : list) { // 安全:所有元素都是 Number 或其子类
        sum += num.doubleValue();
    }
    return sum;
}

✅ 可读(因为知道是 Number 子类)
❌ 不可写(不知道具体类型,无法保证类型安全)

3. 下界通配符 <? super T> —— 消费者(Consumer)

// 接受 List<Object>、List<Number>、List<Integer> 等(只要能容纳 Integer)
public static void addIntegers(List<? super Integer> list) {
    // 可以安全添加 Integer(因为列表至少能存 Integer 或其父类)
    list.add(100);
    list.add(200);
    // 但读取时只能得到 Object
    Object obj = list.get(0);
}

✅ 可写(因为知道能容纳 T)
❌ 读取类型受限(只能是 Object)


PECS 原则(记住这个口诀!)

场景

通配符

口诀

从集合读取数据(生产数据)

<? extends T>

Producer → Extends

向集合写入数据(消费数据)

<? super T>

Consumer → Super


五、泛型的限制(重要!)

1. 类型擦除(Type Erasure)

Java 泛型只在编译期存在,运行时会被擦除为原始类型(Raw Type)。

List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();

// 运行时两者是同一个类!
System.out.println(list1.getClass() == list2.getClass()); // true

🔍 后果:无法通过反射直接获取泛型的实际类型(除非通过字段/方法签名保留的 ParameterizedType)。

2. 不能使用基本类型

List<int> numbers; // ❌ 编译错误
List<Integer> numbers; // ✅ 正确,使用包装类

3. 不能创建泛型数组

T[] arr = new T[10]; // ❌ 编译错误:无法实例化类型参数

// 替代方案:使用 Object 数组 + 强制转换(需谨慎)
@SuppressWarnings("unchecked")
T[] createArray(int size) {
    return (T[]) new Object[size];
}

4. 静态成员不能使用类的类型参数

public class Box<T> {
    private T value;

    // ❌ 错误:静态上下文不能引用非静态类型参数 T
    // private static T defaultValue;

    // ✅ 正确:静态方法可以有自己的泛型参数
    public static <E> E getDefault(E defaultValue) {
        return defaultValue;
    }
}

5. 不能抛出或捕获泛型异常

// ❌ 泛型类不能继承 Throwable
class MyException<T> extends Exception { } // 编译错误

// ❌ 不能 catch 泛型类型
try {
    // ...
} catch (SomeGenericException<T> e) { } // 错误

六、高级应用示例

1. 利用泛型 + 反射创建实例(需传入 Class)

public class InstanceFactory {
    // 通过 Class<T> 在运行时创建 T 的实例
    public static <T> T createInstance(Class<T> clazz) throws Exception {
        return clazz.getDeclaredConstructor().newInstance();
    }
}

// 使用
Person p = InstanceFactory.createInstance(Person.class);

2. 泛型与函数式接口(Java 8+)

// Function 接口本身就是泛型的
Function<String, Integer> strToLength = String::length;
Integer len = strToLength.apply("Generics"); // 8

3. 获取泛型实际类型(通过反射)

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;

public class TypeReference<T> {
    protected final Type type;

    protected TypeReference() {
        // 获取当前匿名子类的泛型父类信息
        Type superClass = getClass().getGenericSuperclass();
        this.type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
    }

    public Type getType() {
        return type;
    }
}

// 使用
TypeReference<List<String>> ref = new TypeReference<List<String>>() {};
System.out.println(ref.getType()); // java.util.List<java.lang.String>

💡 这种技巧被 Jackson、Gson 等 JSON 库广泛使用。


七、总结表格

特性

说明

类型安全

编译期检查,防止非法类型赋值

消除强转

无需 (String) list.get(0)

代码复用

一套逻辑适配多种类型

类型擦除

运行时无泛型信息(兼容旧版本)

通配符灵活

?, ? extends T, ? super T 适应不同场景

PECS 原则

Producer-Extends, Consumer-Super


八、最佳实践建议

  1. 优先使用泛型,尤其是在集合、工具类中。

  2. 避免使用原始类型(Raw Type),如 List 而不是 List<String>

  3. 合理使用通配符提升 API 灵活性。

  4. 不要过度设计泛型,保持代码可读性。

  5. 理解类型擦除,避免在运行时依赖泛型类型。

财务概念释义-借贷记账法 2026-01-07
Java 设计模式:单例模式(Singleton Pattern) 2026-01-07

评论区

© 2026 何歡囍