Java 泛型不可能的赋值?

huangapple go评论73阅读模式
英文:

Java generics impossible assignment?

问题

每次我认为我对泛型有了更好的理解(可以不编译就能回答问题),我就会遇到一个打破这一理论的示例。以下是一个非常简单的示例:

static void consumer(List<? super List<String>> param) {
    System.out.println(param);
}

还有两个调用:

public static void main(String[] args) {
    List<String> list = List.of("123");
    consumer(list);
    consumer(List.of("123"));
}

对我来说,这两个调用都不应该通过编译。String 不是 List 的超类型。然而,第二个调用通过了编译。但是让我们假设这是因为编译器可以推断出某种类型。当然,这样的类型是不存在的,而且它会在运行时失败,对吗?对吗?不对。它居然可以正常工作。因此,有人可以给我的生活带来一些理智吗?

英文:

Every time I think I understand generics better (and can answer without compiling), I get to an example where this theory breaks. Here is a very simple example:

static void consumer(List&lt;? super List&lt;String&gt;&gt; param) {
    System.out.println(param);
}

And two invocations:

public static void main(String[] args) {
    List&lt;String&gt; list = List.of(&quot;123&quot;);
    consumer(list);
    consumer(List.of(&quot;123&quot;));
}

To me, none of the invocations should compile. A String is not a supertype of List. Still, the second one compiles. But let's suppose that this happens because the compiler could infer some type here. Of course such a type does not exist and it will fail at runtime, right? Right? Nope. It just works. As such, can someone bring some sanity to my life please?

答案1

得分: 9

啊,该死!

javac --debug=verboseResolution=all Sandbox.java

显示consumer(List.of("123"))被编译为:

实例化签名:(Object)List
目标类型:List<? super List>

英文:

Ah darn!

javac  --debug=verboseResolution=all Sandbox.java

shows that consumer(List.of(&quot;123&quot;)) is compiled to:

instantiated signature: (Object)List&lt;Object&gt;
target-type: List&lt;? super List&lt;String&gt;&gt;

答案2

得分: 2

如果您想要一个更加 "功能性" 的解释,您必须考虑一下 param 是什么。

通过应用 PECS,注意它因此是一个消费者类型为 List&lt;String&gt;List

  • 兼容的消费者是那些可以消费 List&lt;String&gt; 或其任何父类型的消费者;
  • 当应用于 List 时,它意味着您可以对其调用带有 List&lt;String&gt; 任何父类型的 add()。因此,List&lt;Object&gt; 是兼容的。

这就是为什么 consumer() 可以用 List&lt;Object&gt; 调用,但不能用 List&lt;String&gt; 调用(因为 String 不是 List&lt;String&gt; 的超类型)。

由于 List.of(…) 在声明时总是可以与 List&lt;Object&gt; 匹配,因此它接受第二个调用。

请注意,在 consumer() 方法内部,您将永远无法从 param 中检索到 List&lt;String&gt;(即无法将其用作生产者)。您只能将新元素添加到其中(即将其用作消费者) - 的确,您可以将 List&lt;String&gt; 添加到 List&lt;Object&gt; 中(尽管在这种特殊情况下,List.of() 生成的是不可变列表,因此在运行时会失败)。

英文:

If you want a more “functional” explanation, you have to consider what param is.

By applying PECS, notice that it is thus a List that is a consumer of List&lt;String&gt;.

  • Compatible consumers are those which can consume List&lt;String&gt; or any of its parent types;
  • When applied to List, it means a list on which you can call add() with any parent type of List&lt;String&gt;. A List&lt;Object&gt; is thus compatible.

This is why consumer() can be called with a List&lt;Object&gt; but not a List&lt;String&gt; (String is not a supertype of List&lt;String&gt;).

Since List.of(…) can always match List&lt;Object&gt; at declaration time, it accepts the second call.

Note that from inside the consumer() method, you will never be able to retrieve a List&lt;String&gt; from param (i.e. use it as a producer). You can only add new ones into it (i.e. use it as a consumer) – and indeed, you can add a List&lt;String&gt; into a List&lt;Object&gt; (although in this particular case, List.of() produces an immutable list, so it will fail at runtime).

huangapple
  • 本文由 发表于 2020年10月23日 11:50:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/64493612.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定

  • 开发者交流平台

    本页二维码