【问题标题】:Java type variance, consumer of generic typeJava 类型差异,泛型类型的消费者
【发布时间】:2015-12-02 11:04:52
【问题描述】:

我很难理解 Java 中的方差是如何工作的。

在下面的例子中,我定义了一个函数test,它接受一个Consumer。该函数的定义没有逆变,所以我希望Consumer<Object> 不是Consumer<Pair<Animal, Animal>> 的子类型。然而,代码编译,并且测试接受 lambda Variance:::superAction

我错过了什么?

import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;

import java.util.function.Consumer;

public class Variance {

  public static void main(String[] args) {
    test(Variance::exactMatchAction);
    test(Variance::superAction);
  }

  private static void exactMatchAction(Pair<Animal, Animal> pair) {
    System.out.println(pair.getLeft().getClass().getName());
  }

  private static void superAction(Object obj) {
    System.out.println(obj.getClass().getName());
  }

  private static void test(Consumer<Pair<Animal, Animal>> action) {
    action.accept(ImmutablePair.of(new Animal(), new Animal()));
    action.accept(ImmutablePair.of(new Dog(), new Dog()));
  }

  static class Animal {  }

  static class Dog extends Animal {  }
}

编辑:根据@Thielo 的评论,引用superAction 被取消为Consumer&lt;Pair&lt;Animal, Animal&gt;&gt; 而不是Consumer&lt;Object&gt;

给出test 方法的正确类型是这样的:

void test(Consumer<? super Pair<? extends Animal, ? extends Animal>>)

这种类型将允许我们将Consumer&lt;Object&gt; 传递给test,还允许我们使用Pair&lt;Dog, Dog&gt; 等参数调用消费者,而不仅仅是Pair&lt;Animal, Animal&gt;

作为后续问题,使用此更新的测试类型,它将不再接受像 void exactMatchAction&lt;Pair&lt;Animal, Animal&gt;&gt; 这样的方法引用,只接受 void exactMatchAction&lt;Pair&lt;? extends Animal, ? extends Animal&gt;&gt;。这是为什么呢?

【问题讨论】:

  • 据我所知没有警告。
  • 不确定这是如何实现的,但它确实有意义。对象的消费者也可以消费对。如果您将该参数更改为字符串,您确实会收到错误,对吧?
  • 真的,我不知道。但我的猜测是这与@FunctionalInterface 的处理方式有关。它可能不关心接口本身的类型参数,只关心它们在方法中的引用方式。所以Object -&gt; void 方法可能可以用作Pair&lt;&gt; -&gt; void,因为如果它可以消费任何对象,那么当然可以消费一对。
  • 根据我对方差的理解,Java 中的泛型类型在默认情况下应该是不变的。要使Consumer&lt;Object&gt; 成为Consumer&lt;Pair&lt;L, R&gt;&gt; 的子类型,我们必须将test 的声明修改为void test(Consumer&lt;? super Pair&lt;Animal, Animal&gt;)
  • 你没有得到Consumer&lt;Object&gt;。您的 lambda 是根据您在该行中的需要键入的。所以它仍然是Consumer&lt;Pair&lt;Animal, Animal&gt;&gt;。如果您在不同的上下文中使用相同的方法引用,您可能会得到 Consumer&lt;Number&gt; 或其他任何内容。

标签: java contravariance


【解决方案1】:

方法引用表达式(例如您的Variance::superAction)是poly 表达式 (JLS8, 15.13)。多边形表达式的类型可能会受到表达式的目标类型 (JLS8, 15.3) 的影响,这是该上下文中预期的类型 (JLS8, 5),即Consumer&lt;Pair&lt;Animal, Animal&gt;&gt;,在您的情况下.

详细信息在 JLS8, 15.13.2 中有详细说明。基本思想是对Consumer等功能接口类型进行特殊处理。具体来说,方法类型只需要congruent到函数类型(即Pair&lt;Animal, Animal&gt; -&gt; void--注意Consumer已经从这里的类型考虑中消失了),通过“identif[ying ] 对应于引用的单个编译时声明”(并以void 作为返回类型)。在这里,“识别”声明的概念可以追溯到 15.12.2,并且基本上描述了方法重载解析过程。换句话说,该语言现在采用Consumer&lt;Pair&lt;Animal, Animal&gt;&gt;.accept()(即Pair&lt;Animal, Animal&gt;)所期望的函数参数,并检查是否可以用它调用方法引用(这解决了在有多个具有相同名称的静态方法的情况下的重载) .

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多