【问题标题】:capture#1-of ? super C interpreted as C in generic interface捕获#1-of ?超级 C 在泛型接口中解释为 C
【发布时间】:2018-12-26 21:15:12
【问题描述】:

给定以下类:

class A { public A a() {return new A();}; };
class B extends A { public B b() {return new B();} };
class C extends B { public C c() {return new C();} };
class D extends C { public D d() {return new D();} };

我想写一些UnaryOperators 可以接受 A、B 和 C 的实例,但不能接受 D。

所以我选择声明引用类型是UnaryOperator<? super C>。由于我使用的是super,这意味着它也可以接受Object 的实例。

UnaryOperator<? super C> op1 = arg -> arg.a(); // does not compile, arg could be Object
UnaryOperator<? super C> op2 = arg -> arg.b(); // does not compile, arg could be Object
UnaryOperator<? super C> op3 = arg -> arg.c(); // does compile
UnaryOperator<? super C> op4 = arg -> arg.d(); // this is not expected to compile

为什么op1会提示代码编译失败

Type mismatch: cannot convert from A to C

op2 使代码编译失败并显示消息

Type mismatch: cannot convert from B to C,

但是op3 编译得很好,让我调用一个仅在 C 中可用的方法?

【问题讨论】:

  • 为什么要这样做?不接受 D 的实例违反了en.wikipedia.org/wiki/Liskov_substitution_principle
  • op1 = (A arg) -&gt; arg.a(); op2 = (B arg) -&gt; arg.b() ...
  • 这只是为了更好地理解 java 编译器如何在涉及泛型的 lambda 表达式中推断类型。你对替代原则是正确的。只是因为据我所知,这种行为是极端情况,我想知道这种行为是否有正当理由。
  • 您遇到的问题是 D 都是 C,但 A 是 Object,您无法在层次结构中定义范围供编译器检查,您需要添加运行时检查。

标签: java generics java-8 functional-programming bounded-wildcard


【解决方案1】:

为什么op1会导致代码编译失败...?

当泛型函数接口通过通配符参数化时,有不同的实例可以满足通配符并产生不同的函数类型。例如,每个 1:

UnaryOperator<C> (function type C -> C);
UnaryOperator<B> (function type B -> B);
UnaryOperator<A> (function type A -> A);
UnaryOperator<Object> (function type Object -> Object);

UnaryOperator&lt;? super C&gt;

有时,可以从上下文(例如 lambda 的参数类型)中知道预期的函数类型。其他时候,需要选择一个:

UnaryOperator<? super C> op1 = (A arg) -> arg.a();
                                ^

UnaryOperator<? super C> op2 = (B arg) -> arg.b();
                                ^
...

如果不选择一个,则使用边界2

UnaryOperator<? super C> op = arg -> arg.c(); // valid

其中C 是一个界限,所以在这种情况下,lambda 表达式是一个UnaryOperator&lt;C&gt;


1 - JLS 9.9. Function Types - 最后一段。
2 - JLS 15.27.3. Type of a Lambda Expression - 如果 T 是通配符参数化的函数接口类型并且 lambda 表达式是隐式类型的,那么地面目标类型是非通配符参数化T

【讨论】:

    【解决方案2】:

    @Oleksandr 告诉你为什么它无法编译。我会告诉你为什么你不能用UnaryOperator&lt;? super C&gt;来只接受A, B, C而拒绝D


    UnaryOperator&lt;? super C&gt; 不代表函数接受? super C。 这意味着UnaryOperator&lt;T&gt; 中的通用T? super C

    假设您有一个实例 UnaryOperator&lt;? super C&gt; uo

    • uo.apply(new A()) 有意义吗?否,因为UnaryOperator&lt;B&gt; 可以分配给UnaryOperator&lt;? super C&gt;,但不能接受A
    • uo.apply(new B()) 有意义吗?否,因为UnaryOperator&lt;C&gt; 可以分配给UnaryOperator&lt;? super C&gt;,但不能接受B

    那么uo可以接受什么?答案是C,因为只有C 可以分配给任何? super C

    所以您的UnaryOperator&lt;? super C&gt; 实际上与UnaryOperator&lt;C&gt; 相同。虽然您的 IDE 可能会告诉您它接受 Object,但它不会接受不可分配给 C 的对象。

    【讨论】:

      猜你喜欢
      • 2013-01-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多