泛型、类型参数和通配符

2022-09-02 04:40:09

我试图理解java泛型,它们似乎很难理解。例如,这很好...

public class Main {

    public static void main(String[] args) {
        List<?> list = null;
        method(list);
    }

    public static <T> void method(List<T> list) { }
}

...就像这个...

public class Main {

    public static void main(String[] args) {
        List<List<?>> list = null;
        method(list);
    }

    public static <T> void method(List<T> list) { }
}

...而这个...

public class Main {

    public static void main(String[] args) {
        List<List<List<?>>> list = null;
        method(list);
    }

    public static <T> void method(List<List<T>> list) { }
}

...但这不会编译:

public class Main {

    public static void main(String[] args) {
        List<List<?>> list = null;
        method(list);
    }

    public static <T> void method(List<List<T>> list) { }
}

有人可以用简单的语言解释发生了什么吗?


答案 1

对于泛型类型,要理解的主要事情是它们不是协变的。

因此,虽然您可以执行此操作:

final String string = "string";
final Object object = string;

以下内容将无法编译:

final List<String> strings = ...
final List<Object> objects = strings;

这是为了避免规避泛型类型的情况:

final List<String> strings = ...
final List<Object> objects = strings;
objects.add(1);
final String string = strings.get(0); <-- oops

因此,逐个浏览您的示例

1

你的泛型方法采用 一个 , 你传入一个 ;这(本质上)是一个. 可以分配给类型,编译器很高兴。List<T>List<?>List<Object>TObject

2

您的泛型方法是相同的,您传入了 . 可以分配给类型,编译器再次高兴。List<List<?>>TList<?>

3

这基本上与具有另一级嵌套的 2 相同。 仍然是类型。TList<?>

4

这是它去一个小梨形的地方,也是我从上面的观点进来的地方。

您的泛型方法采用 .您传入 .现在,由于泛型类型不是协变的,因此不能分配给 .List<List<T>>List<List<?>>List<?>List<T>

实际的编译器错误 (Java 8) 是:

必需:发现:原因:无法推断类型变量(参数不匹配; 无法转换为java.util.List<java.util.List<T>>java.util.List<java.util.List<?>>Tjava.util.List<java.util.List<?>>java.util.List<java.util.List<T>>)

基本上,编译器会告诉您,由于必须推断外部列表中嵌套的类型,因此它找不到要分配的。TList<T>

让我们更详细地看一下:

List<?>某种未知类型的 a - 它可以是 a 或 a ;我们可以从它作为 ,但我们不能添加。因为否则我们会遇到我提到的协方差问题。ListList<Integer>List<String>getObject

List<List<?>>是某种未知类型的 a - 它可能是 a 或 .在案例 1 中,可以分配到并且只允许对通配符列表执行操作。在案例 4 中,这无法完成 - 主要是因为没有泛型构造来防止到外部 。ListListList<List<Integer>>List<List<String>>TObjectaddaddList

如果编译器在第二种情况下要分配给 ,那么类似下面的情况将是可能的:TObject

final List<List<Integer>> list = ...
final List<List<?>> wildcard = list;
wildcard.add(Arrays.asList("oops"));

因此,由于协方差,不可能安全地将 a 赋给任何其他泛型。List<List<Integer>>List


答案 2