2015-09-06 3 views
6

Я изо всех сил пытаюсь понять, как дисперсия работает на Java.Ошибка типа Java, потребитель родового типа

В следующем примере я определяю функцию test, которая принимает Consumer. Функция определена без контравариантности, поэтому я ожидаю, что Consumer<Object> не является подтипом Consumer<Pair<Animal, Animal>>. Тем не менее, код компилируется, и тест принимает лямбда 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 { } 
} 

Edit: Per @ комментарий 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) являются poly выражениями (JLS8, 15.13). На тип поли-выражения может влиять целевой тип выражения (JLS8, 15.3), который является типом, ожидаемым в этом контексте (JLS8, 5), то есть Consumer<Pair<Animal, Animal>>, в вашем случае.

Детали указаны в JLS8, 15.13.2. Основная идея заключается в том, что существует специальная обработка для типов функциональных интерфейсов, таких как Consumer. В частности, тип метода должен быть только конгруэнтным к типу функции (который равен Pair<Animal, Animal> -> void - обратите внимание, что Consumer исчез из рассмотрения типа здесь), что удовлетворяет «identif [ying] единому объявлению времени компиляции, соответствующему ссылка "(и имеющая void в качестве возвращаемого типа). Здесь понятие «идентификация» декларации восходит к 15.12.2 и в основном описывает процесс разрешения перегрузки метода. Другими словами, теперь язык принимает ожидаемые параметры функции для Consumer<Pair<Animal, Animal>>.accept() (т. Е. Pair<Animal, Animal>) и проверяет, можно ли вызвать ссылку на метод (это разрешает перегрузку в случае, если существует несколько статических методов с тем же именем).

Смежные вопросы