一文搞懂java泛型

75 阅读3分钟

我们日常中几乎每天都会用到泛型,那么泛型的原理是怎么样的呢,从官网中可以看到,泛型的介绍有两大类,通配符和泛型方法

image.png

接下来我们来搞懂什么以下几个知识点:
1、 什么是类型擦除
2、常用的 ?, T, E, K, V, N的含义
3、上界通配符 < ?extends E>
4、下界通配符 < ?super E>
5、什么是PECS原则
6、通过一个案例来理解 ?和 T 和 Object 的区别

一:什么是类型擦除

我们说Java的泛型是伪泛型,那是因为泛型信息只存在于代码编译阶段,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程为类型擦除。

@Test
void testSomeMethod() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    // 定义long类型的集合
    ArrayList<Long> list = new ArrayList<>();
    list.add(1L);
    // 定义class对象,下面的方法就会把原始的long类型的的集合给擦除
    Class<? extends ArrayList> clazz = ArrayList.class;
    Method add = clazz.getDeclaredMethod("add", Object.class);
    add.invoke(list, "测试类型擦除");
    System.err.println(list);
}
返回结果: [1, 测试类型擦除]

二:常用的 ?, T, E, K, V, N的含义

对于常见的这几种类型其实没有什么特殊的含义,只不过是由一些命名规范,例如以下

-   T (Type) 具体的Java类
-   E (Element)在集合中使用,因为集合中存放的是元素
-   K V (key value) 分别代表java键值中的Key Value
-   N (Number)数值类型
-   ? 表示不确定的 Java 类型

三:上界通配符 < ?extends E>

举例: <? extends ArrayList> 主要用于只读操作(即“生产者”场景,Producer)。比如你只想从一个集合中读取元素,而不往里面添加。

import java.util.*;

public class Demo {
    public static void printNumbers(List<? extends Number> list) {
        for (Number num : list) {
            System.out.println(num);
        }
    }

    public static void main(String[] args) {
        List<Integer> intList = Arrays.asList(1, 2, 3);
        List<Double> doubleList = Arrays.asList(1.1, 2.2, 3.3);

        printNumbers(intList);     // OK
        printNumbers(doubleList);  // OK
    }
}

四:下界通配符 < ?super E>

举例 :<? super Test> 主要用于写入(添加)元素,也就是“消费者”场景(Consumer)。因为你知道集合至少能接受 T 类型及其子类。

import java.util.*;

public class Demo {
    public static void addIntegers(List<? super Integer> list) {
        list.add(1);       // OK
        list.add(100);     // OK
        // list.add(3.14); // 编译错误,Double 不是 Integer 的子类
    }

    public static void main(String[] args) {
        List<Number> numbers = new ArrayList<>();
        List<Object> objects = new ArrayList<>();

        addIntegers(numbers); // OK
        addIntegers(objects); // OK
    }
}

五:什么是PECS原则

Producer - extends(只读)
Consumer - super(可写)

六:通过一个案例来理解 ?和 T 和 Object 的区别

1. 使用 Object

public static void processObject(List<Object> list) {
    list.add("new item"); // ✅ 允许添加
    Object obj = list.get(0); // ✅ 返回 Object
    System.out.println("Object: " + obj);
}

2. 使用 <?>

public static void processWildcard(List<?> list) {
    // list.add("new item"); ❌ 不允许添加(除了 null)
    Object obj = list.get(0); // ✅ 可以读取为 Object
    System.out.println("Wildcard: " + obj);
}

3. 使用泛型 <T>

public static <T> void processGeneric(List<T> list) {
    T item = list.get(0); // ✅ 返回 T 类型
    list.add(item);       // ✅ 允许添加 T 类型
    System.out.println("Generic: " + item.getClass().getSimpleName());
}

注意:在 Java 中,泛型方法本身是不能仅凭类型参数的不同进行重载的。这是因为 Java 编译器在编译期间会进行类型擦除(Type Erasure) ,在字节码层面泛型信息是不存在的,因此如果两个方法在类型擦除后具有相同的签名,就会发生冲突。