2017-05-13 42 views
14

Przyjmijmy, mają następujące działania: interfejsOgraniczenia forEach z odniesieniem sposobu przykład Jawa 8

public interface TemperatureObserver { 
    void react(BigDecimal t); 
} 

i w innej klasie i już wypełniony ArrayList obiektów typu TemperatureObserver. Zakładając, że temp jest BigDecimal mogę powołać react w pętli przy użyciu:

observers.forEach(item -> item.react(temp)); 

Moje pytanie: Mogę użyć metody odniesienia dla powyższego kodu?

Następujące nie działa:

observers.forEach(TemperatureObserver::react); 

Komunikat o błędzie mówi mi, że

  1. forEach w Arraylist observers nie ma zastosowania do typu TemperatureObserver::react
  2. TemperatureObserver nie definiuje metodę react(TemperatureObserver)

Wystarczająco uczciwe, jako że forEach oczekuje jako argumentu Consumer<? super TemperatureObserver>, a mój interfejs, choć funkcjonalny, nie jest zgodny z Consumer z powodu odmiennego argumentu react (a BigDecimal w moim przypadku).

Czy można to rozwiązać, czy jest to przypadek, w którym lambda nie ma odpowiedniego odniesienia do metody?

+0

Metoda, do której się odwołujesz, znajduje się w interfejsie. Nie ma implementacji, więc nie ma sensu go używać. Jeśli stworzysz klasę i użyjesz instancji tej klasy dla odniesienia do metody (np. 'InstanceTempObs :: react') lub uczynisz metodę statyczną, to by działało. (JEŻELI twoja lista jest wpisana jako' BigDecimal') – Riiverside

+5

Byłoby możliwe, gdyby java.lang.Iterable miał drugą metodę forEach, która zajęłaby BiConsumer i parametr. Następnie możesz użyć referencji do metody i przekazać BigDecimal jako drugi parametr. Ten wzorzec istnieje w kolekcjach Eclipse w metodach z przyrostkiem "Z" (np. DlaEachWith). https://github.com/eclipse/eclipse-collections/blob/master/eclipse-collections-api/src/main/java/org/eclipse/collections/api/InternalIterable.java#L106 –

+1

Zobacz [currying] (https) : //en.wikipedia.org/wiki/Currying). –

Odpowiedz

16

Istnieją trzy rodzaje odniesień metody, które mogą być stosowane, gdy jedna wartość jest dostępna ze strumienia:

  1. Parametr mniej metoda strumieniowanego obiektu.

    class Observer { 
        public void act() { 
         // code here 
        } 
    } 
    
    observers.forEach(Observer::act); 
    
    observers.forEach(obs -> obs.act()); // equivalent lambda 
    

    Obiekt przesyłany staje się obiektem obiektu w postaci this.

  2. Metoda statyczna z przesyłanym obiektem jako parametrem.

  3. Niestatyczna metoda z przesyłanym obiektem jako parametrem.

    class Other { 
        void act(Observer o); 
    } 
    
    Other other = new Other(); 
    observers.forEach(other::act); 
    
    observers.forEach(obs -> other.act(obs)); // equivalent lambda 
    

Jest też wzmianka konstruktor, ale to naprawdę nie jest istotne dla tej kwestii.

Ponieważ mają wartość zewnętrznego temp, a chcesz używać Referencyjny sposób można zrobić trzecią opcję:

class Temp { 
    private final BigDecimal temp; 
    public Temp(BigDecimal temp) { 
     this.temp = temp; 
    } 
    public void apply(TemperatureObserver observer) { 
     observer.react(this.temp); 
    } 
} 

Temp tempObj = new Temp(temp); 

observers.forEach(tempObj::apply); 
+7

@ TempAgilist, podczas gdy Andreas jest tutaj, wątpiłbym, że użycie klasy do zawijania wartości, * wystarczy użyć referencji do metody *, aby dodać dowolną wartość; oprócz tego, że trudno jest to zrozumieć przyszłym opiekunom. – Eugene

+2

@Eugene: agreed. Twoja uwaga dotyczy również ważnego rozwiązania Federico (patrz poniżej) przy użyciu metody pomocniczej. Zastanawiam się tylko, czy brakowało mi prostego rozwiązania wykorzystującego referencje do metod. Teraz wiem, że nie miałem :) –

+1

@ TempAgilist Aahh ... Jak chciałbym móc zrobić 'TemperatureObserver :: react (temp)' –

5

To nie działa, ponieważ wykonuje się iterację po procedurach obsługi, a nie parametrach.

Na przykład, ten kod działa:

ArrayList<BigDecimal> temps = new ArrayList<>(); 

    TemperatureObserver observer = new TemperatureObserverImpl(); 

    temps.forEach(observer::react); 
+0

Założenie 'TemperatureObserver' jest klasą, a nie interfejsem.Alternatywnie możesz mieć coś w stylu 'TemperatureObserverImpl', które implementuje interfejs. –

+0

@ Code-Apprentice, masz rację. Naprawiony. –

9

spojrzeć na Method References section in the Java Tutorial. Tam jest napisane:

Istnieją cztery rodzaje odniesień metoda:

  • odniesienie do metody statycznej: ContainingClass::staticMethodName

  • odniesienia do metody instancji konkretnego obiektu: containingObject::instanceMethodName

  • Odwołanie do metody instancji dowolnego obiektu określonego typu: ContainingType::methodName

  • Odniesienie do konstruktora: ClassName::new

Jest to wyjaśnia, że ​​to znaczy TemperatureObserver::react byłoby odniesienie metoda 3. Typ: odniesienie do metody instancji z dowolnego przedmiotu określonego typu . W kontekście wezwanie do metody Stream.forEach, że referencyjna metoda byłoby równoznaczne z następującego wyrażenia lambda:

(TemperatureObserver item) -> item.react() 

lub po prostu:

item -> item.react() 

Które nie pasuje do podpisu void TemperatureObserver.react(BigDecimal t) metody.

Jak już podejrzewasz, istnieją przypadki, dla których nie można znaleźć równoważnego odniesienia do metody dla lambda. Lambdy są o wiele bardziej elastyczne, choć czasami IMHO są mniej czytelne niż odniesienia do metod (ale to kwestia gustu, wielu ludzi myśli odwrotnie).

Sposób, aby nadal korzystać z Referencyjna metoda będzie z metody pomocnika:

public static <T, U> Consumer<? super T> consumingParam(
     BiConsumer<? super T, ? super U> biConsumer, 
     U param) { 

    return t -> biConsumer.accept(t, param); 
} 

które można wykorzystać w następujący sposób:

observers.forEach(consumingParam(TemperatureObserver::react, temp)); 

Ale, szczerze mówiąc, wolę używać lambda .