2015-09-06 29 views
6

我很努力地理解Java中的差异是如何工作的。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>>。为什么是这样?

+0

据我所知,没有警告。 – asp

+0

不知道这是如何实现的,但它确实有道理。对象的消费者也可以消费对。如果你改变这个参数来表示一个字符串,你会得到一个错误,对吗? – Thilo

+0

真的,我不知道。但我的猜测是这与'@ FunctionalInterface'的处理方式有关。它可能不关心接口本身的类型参数,只是它们在方法中被引用的方式。所以'Object - > void'方法可以用作'Pair <> - > void',因为如果它可以消耗*任何对象*,那么当然可以消耗一对。 – ryachza

回答

0

方法参考表达式(如您Variance::superAction)是聚表达式(JLS8,15.13)。表达式的目标类型(JLS8,15.3)可能会影响poly表达式的类型,在您的情况下,该类型是该上下文中预期的类型(JLS8,5),即Consumer<Pair<Animal, Animal>>

细节在JLS8,15.13.2中详细说明。基本思想是对功能接口类型进行特殊处理,例如Consumer。具体来说,方法类型只需要全等到函数类型(即Pair<Animal, Animal> -> void - 注意Consumer已经从这里的类型考虑中消失了),这是通过“识别单个编译时声明参考“(并且具有void作为返回类型)。这里,“识别”一个声明的概念可以追溯到15.12.2,并且基本上描述了方法重载解析过程。换句话说,语言现在采用Consumer<Pair<Animal, Animal>>.accept()(即Pair<Animal, Animal>)预期的函数参数,并检查方法引用是否可以用该方法调用(如果存在多个具有相同名称的静态方法,则解析重载)。