2015-09-06 13 views
6

Staram się zrozumieć, jak działa wariancja w Javie.Wariancja typu Java, konsument rodzaju generycznego

W poniższym przykładzie definiuję funkcję test, która zajmuje Consumer. Funkcja jest zdefiniowana bez kontrawariancji, więc oczekiwałbym, że Consumer<Object> nie jest podtypem Consumer<Pair<Animal, Animal>>. Jednak kod kompiluje i test akceptuje lambda Variance:::superAction.

Czego mi brakuje?

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 @ komentarzu Thielo za odniesienie superAction jest odcukrzona do NOT Consumer<Pair<Animal, Animal>>Consumer<Object>.

prawidłowy rodzaj dać metodę test jest coś takiego:

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

Ten typ pozwoli nam przejść Consumer<Object> do test, a także pozwalają nam nazywać konsumentowi argumentów jak Pair<Dog, Dog> zamiast po prostu Pair<Animal, Animal>.

Jako kolejne pytanie, z tym zaktualizowanym typem do testu, nie będzie już akceptować odwołania do metody, takiego jak void exactMatchAction<Pair<Animal, Animal>>, tylko void exactMatchAction<Pair<? extends Animal, ? extends Animal>>. Dlaczego to?

+0

Brak ostrzeżeń, o ile wiem. – asp

+0

Nie wiesz, jak to się dzieje, ale ma sens. Konsument obiektów może również spożywać pary. Pojawi się błąd, jeśli zmienisz ten parametr, aby napisać, String, prawda? – Thilo

+0

Naprawdę, nie wiem. Ale domyślam się, że ma to związek z obsługą '@ FunctionalInterface'. Prawdopodobnie nie dbają o parametry typu samego interfejsu, tylko o to, w jaki sposób są one przywoływane w metodzie. Tak więc metoda 'Object -> void' może prawdopodobnie służyć jako' Pair <> -> void', ponieważ jeśli może pochłonąć * dowolny obiekt *, to oczywiście może pochłonąć parę. – ryachza

Odpowiedz

0

Wyrażenia referencyjne metod (takie jak Variance::superAction) to wyrażeń poli- wych (JLS8, 15.13). Na typ ekspresji poli można wpływać przez docelowy rodzaj ekspresji typu (JLS8, 15,3), który jest typem oczekiwanym w tym kontekście (JLS8, 5), tj. Consumer<Pair<Animal, Animal>>, w twoim przypadku.

Szczegóły podano w JLS8, 15.13.2. Podstawową ideą jest to, że istnieje specjalna obsługa dla funkcjonalnych typów interfejsu, takich jak Consumer. Konkretnie, typ metody musi być tylko przystający do typu funkcji (który jest Pair<Animal, Animal> -> void - zauważ, że Consumer zniknął z rozważań o typie tutaj), co jest spełnione przez "identif [ying] pojedynczą deklarację czasu kompilacji odpowiadającą odniesienie "(i posiadające void jako typ powrotu). Tutaj pojęcie "identyfikacji" deklaracji sięga 15.12.2 i zasadniczo opisuje proces rozwiązywania przeciążenia metody. Innymi słowy, język przyjmuje teraz parametry funkcji oczekiwane dla Consumer<Pair<Animal, Animal>>.accept() (tj. Pair<Animal, Animal>) i sprawdza, czy odwołanie do metody mogłoby zostać wywołane z tym (to rozwiązuje przeciążenie w przypadku, gdy istnieje wiele metod statycznych o tej samej nazwie).

Powiązane problemy