Java 泛型参数中通配符 ? 和 Object 的差异

内容纲要

概述

面向对象的设计有个基本原则:里氏代换原则,简单理解为凡是接受父类的场合,都可以接受其子类。

Java 是一种面向对象的语言,也许很多同学并没有听说过所谓的里氏代换原则,但不妨碍大家应用这样的思维去进行开发,并形成了某种固有的认知——凡是需要父类的场合,我都可以传入子类。

正是这种思维定式,导致我们在理解泛型参数时遇到困难

泛型参数不适用里氏代换原则

如果你要传入的参数本身是个泛型参数,例如 List<Object>Map<String, String> 这类,那么目标泛型要求什么参数类型,你就要声明什么类型传给他,声明成子类后再传入是不行的,最典型的就是泛型参数为 Object 的时候

看个例子

public static final void print(List<Object> list) {
    System.out.println(list);
}

public void test() {
    List<Integer> intList = Arrays.asList(1,2,3,4);
    // 类型 List<Integer> 和泛型参数 List<Object> 不一致, 编译不通过
    print(intList);

    List<Object> objList = Arrays.asList(1,2,3,4);
    // 编译通过
    print(objList);
}

由于里氏代换原则的思维定式存在,有些同学想当然的认为 List<Integer> 也是 List<Object>,实际上这种场景不适合里氏代换原则,泛型要的是 List<Object>,就只能传 List<Object>

泛型参数 ? 不表示任何类型

在使用 ? 作为泛型参数时,可以理解为 ? 是个通配符,任何类型都 ok,但本身不表示特定类型,这是它和 Object 的最大区别,如果用里氏代换原则来套这种场景,会觉得 ? 更像是所有类的父类,那不就表示 Object 类了吗?不能这样想,使用泛型时,抛开里氏代换的思维定式。

看个例子

public static final void print(List<?> list) {
    System.out.println(list);
}

public void test() {
    List<Integer> intList = Arrays.asList(1,2,3,4);
    // 编译通过
    print(intList);

    List<Object> objList = Arrays.asList(1,2,3,4);
    // 编译通过
    print(objList);

    List<String> strList = Arrays.asList("tom","jerry","mike");
    // 编译通过
    print(strList);
}

可以发现,? 是来者不拒的

? 参数进阶

对于声明为 ? 的泛型,要注意:你没有办法定义一个类型为 ? 的对象,从而导致你无法调用那些需要传入泛型参数的方法,比如 Listadd(E e)Mapput(K key, V value) 这类的方法

public void test() {
    List<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);

    List<?> intList = list;
    // 编译不通过,参数 4 的 类型不是 ?
    intList.add(4);
    // 参数 0 不需要泛型化,该方法正常执行
    intList.remove(0);
}

结语

其实也很简单,我们在面对泛型时,从里氏代换的思维定式里脱身出来,泛型里的一些难以理解之处就很简单了

无责任联想一下,也许一个刚接触 Java 语言的小白,更容易掌握泛型呢

Java 泛型参数中通配符 ? 和 Object 的差异

发表回复

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

Scroll to top
粤ICP备2020114259号 粤公网安备44030402004258