Java8 Lambdas vs Anonymous Classes


111

Ponieważ Java8 została niedawno wydana, a jej zupełnie nowe wyrażenia lambda wyglądają na naprawdę fajne, zastanawiałem się, czy oznacza to upadek klas Anonymous, do których byliśmy tak przyzwyczajeni.

Zbadałem trochę na ten temat i znalazłem kilka fajnych przykładów tego, jak wyrażenia lambda będą systematycznie zastępować te klasy, takie jak metoda sortowania Collection, która służyła do uzyskania anonimowej instancji komparatora w celu wykonania sortowania:

Collections.sort(personList, new Comparator<Person>(){
  public int compare(Person p1, Person p2){
    return p1.firstName.compareTo(p2.firstName);
  }
});

Teraz można to zrobić za pomocą Lambdas:

Collections.sort(personList, (Person p1, Person p2) -> p1.firstName.compareTo(p2.firstName));

I wygląda zaskakująco zwięźle. Więc moje pytanie brzmi: czy jest jakiś powód, aby nadal używać tych klas w Javie8 zamiast Lambdas?

EDYTOWAĆ

To samo pytanie, ale w przeciwnym kierunku, jakie są korzyści z używania Lambd zamiast klas anonimowych, skoro Lambdas można używać tylko z interfejsami jednej metody, czy ta nowa funkcja jest tylko skrótem używanym tylko w kilku przypadkach, czy jest naprawdę przydatna?


5
Jasne, dla wszystkich tych anonimowych klas, które zapewniają metody z efektami ubocznymi.
tobias_k

11
Dla informacji możesz również skonstruować komparator jako:, Comparator.comparing(Person::getFirstName)jeśli getFirstName()byłaby to metoda zwracająca firstName.
skiwi

1
Albo anonimowe zajęcia z wieloma metodami, albo ...
Mark Rotteveel,

1
Kusi mnie, by głosować za zbliżeniem jako zbyt szerokim, zwłaszcza ze względu na dodatkowe pytania po EDYCIE .
Mark Rotteveel,

1
Miły, dogłębny artykuł na ten temat: infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood
Ram Patra

Odpowiedzi:


108

Anonimowej klasy wewnętrznej (AIC) można użyć do utworzenia podklasy klasy abstrakcyjnej lub klasy konkretnej. AIC może również zapewnić konkretną implementację interfejsu, w tym dodanie stanu (pól). Do instancji AIC można odwoływać się przy użyciu thisjej treści metod, więc można na niej wywoływać dalsze metody, zmieniać jej stan w czasie itd. Żadne z nich nie mają zastosowania do lambd.

Domyślam się, że większość zastosowań AIC polegała na zapewnieniu bezstanowych implementacji pojedynczych funkcji i dlatego można je zastąpić wyrażeniami lambda, ale są inne zastosowania AIC, dla których nie można używać lambd. AIC są tutaj.

AKTUALIZACJA

Inną różnicą między AIC a wyrażeniami lambda jest to, że AIC wprowadzają nowy zakres. Oznacza to, że nazwy są rozpoznawane z nadklas i interfejsów AIC i mogą przesłonić nazwy występujące w środowisku obejmującym leksyk. W przypadku lambd wszystkie nazwy są rozwiązywane leksykalnie.


1
Lambdy mogą mieć stan. Pod tym względem nie widzę różnicy między Lambdami a AIC.
nosid

1
@nosid AIC, podobnie jak instancje dowolnej klasy, mogą utrzymywać stan w polach, a stan ten jest dostępny (i może być modyfikowany przez) każdą metodę klasy. Ten stan istnieje, dopóki obiekt nie zostanie oznaczony GC, tj. Ma nieokreślony zakres, więc może utrzymywać się między wywołaniami metod. Jedyny stan z nieokreślonym zakresem, jaki mają lambdy, jest przechwytywany w momencie napotkania lambda; ten stan jest niezmienny. Zmienne lokalne w lambdzie są zmienne, ale istnieją tylko wtedy, gdy trwa wywołanie lambda.
Stuart Marks

1
@nosid Ah, jednoelementowy hack do tablicy. Po prostu nie próbuj używać swojego licznika z wielu wątków. Jeśli zamierzasz zaalokować coś na stercie i przechwycić to w lambdzie, równie dobrze możesz użyć AIC i dodać pole, które możesz bezpośrednio mutować. Używanie lambdy w ten sposób może działać, ale po co zawracać sobie głowę, skoro można użyć prawdziwego obiektu?
Stuart Marks

2
AIC utworzy podobny plik, A$1.classale Lambda nie. Czy mogę dodać to w Difference?
Asif Mushtaq

2
@UnKnown To głównie kwestia implementacji; nie wpływa na to, jak program z AIC vs lambdami, i właśnie o to głównie chodzi w tym pytaniu. Zauważ, że wyrażenie lambda generuje klasę o nazwie takiej jak LambdaClass$$Lambda$1/1078694789. Jednak ta klasa jest generowana w locie przez metafabrykę lambda, a nie przez javac, więc nie ma odpowiedniego .classpliku. Jest to jednak problem związany z wdrażaniem.
Stuart Marks

60

Lambdy, choć świetna funkcja, będą działać tylko z typami SAM. Oznacza to, że interfejsy mają tylko jedną abstrakcyjną metodę. To się nie powiedzie, jeśli twój interfejs będzie zawierał więcej niż jedną metodę abstrakcyjną. Tutaj przydadzą się klasy anonimowe.

Więc nie, nie możemy po prostu ignorować anonimowych klas. I po prostu FYI, twoja sort()metoda może być bardziej uproszczona, pomijając deklarację typu dla p1i p2:

Collections.sort(personList, (p1, p2) -> p1.firstName.compareTo(p2.firstName));

Możesz również użyć tutaj odwołania do metody. Albo dodasz compareByFirstName()metodę w Personklasie i użyjesz:

Collections.sort(personList, Person::compareByFirstName);

lub dodaj getter for firstName, pobierz bezpośrednio metodę Comparatorfrom Comparator.comparing():

Collections.sort(personList, Comparator.comparing(Person::getFirstName));

4
Wiedziałem, ale wolę długi pod względem czytelności, ponieważ w przeciwnym razie ustalenie, skąd te zmienne pochodzą, mogłoby być mylące.
Amin Abu-Taleb,

@ AminAbu-Taleb Dlaczego miałoby to być mylące. To jest poprawna składnia Lambda. Typy są tak czy inaczej wywnioskowane. Tak czy inaczej, to osobisty wybór. Możesz podać typy jawnie. Nie ma problemów.
Rohit Jain,

1
Istnieje jeszcze jedna subtelna różnica między lambdami a klasami anonimowymi: klasy anonimowe mogą być bezpośrednio opatrzone adnotacjami za pomocą nowych adnotacji typu Java 8 , jak np new @MyTypeAnnotation SomeInterface(){};. Nie jest to możliwe w przypadku wyrażeń lambda. Aby uzyskać szczegółowe informacje, zobacz moje pytanie tutaj: Dodawanie adnotacji do funkcjonalnego interfejsu wyrażenia Lambda .
Balder,

36

Wydajność lambda z klasami Anonymous

Po uruchomieniu aplikacji każdy plik klasy musi zostać załadowany i zweryfikowany.

Klasy anonimowe są przetwarzane przez kompilator jako nowy podtyp dla danej klasy lub interfejsu, więc dla każdej z nich zostanie wygenerowany nowy plik klasy.

Lambdy są inne przy generowaniu kodu bajtowego, są bardziej wydajne, używają dynamicznych instrukcji, które są dostarczane z JDK7.

W przypadku Lambdas ta instrukcja służy do opóźnienia tłumaczenia wyrażenia lambda w kodzie bajtowym do czasu wykonania. (instrukcja zostanie wywołana tylko po raz pierwszy)

W rezultacie wyrażenie lambda stanie się metodą statyczną (utworzoną w czasie wykonywania). (Istnieje niewielka różnica w przypadku stanów statycznych i przypadków stanowych, są one rozwiązywane za pomocą wygenerowanych argumentów metody)


Każda lambda również potrzebuje nowej klasy, ale jest generowana w czasie wykonywania, więc w tym sensie lambdy nie są bardziej wydajne niż klasy anonimowe. Lambdy są tworzone za pośrednictwem, invokedynamicktóre jest generalnie wolniejsze niż invokespecialużywane do tworzenia nowych instancji klas anonimowych. Tak więc w tym sensie lambdy są również wolniejsze (jednak JVM może optymalizować invokedynamicwywołania przez większość czasu).
ZhekaKozlov

3
@AndreiTomashpolskiy 1. Prosimy o uprzejmość. 2. Przeczytaj komentarz inżyniera kompilatora: habrahabr.ru/post/313350/comments/#comment_9885460
ZhekaKozlov

@ZhekaKozlov, nie musisz być inżynierem kompilatora, aby czytać kod źródłowy JRE i używać javap / debugger. Brakuje Ci tego, że generowanie klasy opakowania dla metody lambda odbywa się całkowicie w pamięci i prawie nic nie kosztuje, podczas gdy tworzenie wystąpienia AIC obejmuje rozwiązywanie i ładowanie odpowiedniego zasobu klasy (co oznacza wywołanie systemowe we / wy). W związku invokedynamicz tym generowanie klas ad-hoc przebiega błyskawicznie w porównaniu do skompilowanych klas anonimowych.
Andrei Tomashpolskiy

@AndreiTomashpolskiy I / O niekoniecznie jest powolne
ZhekaKozlov

14

Istnieją następujące różnice:

1) Składnia

Wyrażenia lambda wyglądają zgrabnie w porównaniu z Anonimową Klasą Wewnętrzną (AIC)

public static void main(String[] args) {
    Runnable r = new Runnable() {
        @Override
        public void run() {
            System.out.println("in run");
        }
    };

    Thread t = new Thread(r);
    t.start(); 
}

//syntax of lambda expression 
public static void main(String[] args) {
    Runnable r = ()->{System.out.println("in run");};
    Thread t = new Thread(r);
    t.start();
}

2) Zakres

Anonimowa klasa wewnętrzna jest klasą, co oznacza, że ​​ma zakres dla zmiennej zdefiniowanej wewnątrz klasy wewnętrznej.

Natomiast wyrażenie lambda nie jest własnym zakresem, ale jest częścią otaczającego zakresu.

Podobna reguła ma zastosowanie do super i tego słowa kluczowego podczas używania anonimowej klasy wewnętrznej i wyrażenia lambda. W przypadku anonimowej klasy wewnętrznej to słowo kluczowe odnosi się do zakresu lokalnego, a słowo kluczowe super odnosi się do superklasy anonimowej klasy. Podczas gdy w przypadku wyrażenia lambda to słowo kluczowe odnosi się do obiektu typu otaczającego, a super będzie odnosić się do superklasy otaczającej klasy.

//AIC
    public static void main(String[] args) {
        final int cnt = 0; 
        Runnable r = new Runnable() {
            @Override
            public void run() {
                int cnt = 5;    
                System.out.println("in run" + cnt);
            }
        };

        Thread t = new Thread(r);
        t.start();
    }

//Lambda
    public static void main(String[] args) {
        final int cnt = 0; 
        Runnable r = ()->{
            int cnt = 5; //compilation error
            System.out.println("in run"+cnt);};
        Thread t = new Thread(r);
        t.start();
    }

3) Wydajność

W czasie wykonywania anonimowe klasy wewnętrzne wymagają ładowania klasy, alokacji pamięci oraz inicjalizacji obiektu i wywołania metody niestatycznej, podczas gdy wyrażenie lambda jest czystą czynnością czasu kompilacji i nie powoduje dodatkowych kosztów w czasie wykonywania. Zatem wydajność wyrażenia lambda jest lepsza w porównaniu z anonimowymi klasami wewnętrznymi. **

** Zdaję sobie sprawę, że ten punkt nie jest do końca prawdziwy. Szczegółowe informacje można znaleźć w poniższym pytaniu. Lambda vs anonimowa wydajność klasy wewnętrznej: zmniejszenie obciążenia ClassLoader?


3

Lambdy w Javie 8 zostały wprowadzone do programowania funkcjonalnego. Gdzie można uniknąć kodu standardowego. Natknąłem się na ten interesujący artykuł na temat lambda.

http://radar.oreilly.com/2014/04/whats-new-in-java-8-lambdas.html

Zaleca się używanie funkcji lambda dla prostych logik. Jeśli implementacja złożonej logiki przy użyciu lambd będzie narzutem w debugowaniu kodu w przypadku problemu.


0

Klasy anonimowe istnieją na stałe, ponieważ lambda jest dobra dla funkcji z pojedynczymi metodami abstrakcyjnymi, ale we wszystkich innych przypadkach anonimowe klasy wewnętrzne są twoim wybawcą.


-1
  • Składnia lambda nie wymaga pisania oczywistego kodu, który może wywnioskować java.
  • Używając invoke dynamic, lambda nie są konwertowane z powrotem na klasy anonimowe w czasie kompilacji (Java nie musi przechodzić przez tworzenie obiektów, tylko dbać o podpis metody, może powiązać się z metodą bez tworzenia obiektu
  • lambda kładzie większy nacisk na to, co chcemy zrobić, zamiast tego, co musimy zrobić, zanim będziemy mogli to zrobić
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.