【发布时间】: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<Pair<Animal, Animal>> 而不是Consumer<Object>。
给出test 方法的正确类型是这样的:
void test(Consumer<? super Pair<? extends Animal, ? extends Animal>>)
这种类型将允许我们将Consumer<Object> 传递给test,还允许我们使用Pair<Dog, Dog> 等参数调用消费者,而不仅仅是Pair<Animal, Animal>。
作为后续问题,使用此更新的测试类型,它将不再接受像 void exactMatchAction<Pair<Animal, Animal>> 这样的方法引用,只接受 void exactMatchAction<Pair<? extends Animal, ? extends Animal>>。这是为什么呢?
【问题讨论】:
-
据我所知没有警告。
-
不确定这是如何实现的,但它确实有意义。对象的消费者也可以消费对。如果您将该参数更改为字符串,您确实会收到错误,对吧?
-
真的,我不知道。但我的猜测是这与
@FunctionalInterface的处理方式有关。它可能不关心接口本身的类型参数,只关心它们在方法中的引用方式。所以Object -> void方法可能可以用作Pair<> -> void,因为如果它可以消费任何对象,那么当然可以消费一对。 -
根据我对方差的理解,Java 中的泛型类型在默认情况下应该是不变的。要使
Consumer<Object>成为Consumer<Pair<L, R>>的子类型,我们必须将test的声明修改为void test(Consumer<? super Pair<Animal, Animal>)。 -
你没有得到
Consumer<Object>。您的 lambda 是根据您在该行中的需要键入的。所以它仍然是Consumer<Pair<Animal, Animal>>。如果您在不同的上下文中使用相同的方法引用,您可能会得到Consumer<Number>或其他任何内容。
标签: java contravariance