JavaRush /Blog Java /Random-PL /Wyrażenia lambda z przykładami

Wyrażenia lambda z przykładami

Opublikowano w grupie Random-PL
Java jest początkowo językiem całkowicie obiektowym. Z wyjątkiem typów pierwotnych wszystko w Javie jest obiektem. Nawet tablice są obiektami. Instancje każdej klasy są obiektami. Nie ma ani jednej możliwości osobnego zdefiniowania funkcji (poza klasą – ok. tł. ). Nie ma też możliwości przekazania metody jako argumentu ani zwrócenia treści metody w wyniku działania innej metody. To tak. Ale to było przed Javą 8. Wyrażenia lambda z przykładami - 1Od czasów starego, dobrego Swinga konieczne było pisanie anonimowych klas, gdy konieczne było przekazanie jakiejś funkcjonalności jakiejś metodzie. Na przykład tak wyglądało dodanie modułu obsługi zdarzeń:
someObject.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {

                //Event listener implementation goes here...

            }
        });
Tutaj chcemy dodać trochę kodu do detektora zdarzeń myszy. Zdefiniowaliśmy anonimową klasę MouseAdapteri od razu utworzyliśmy z niej obiekt. W ten sposób przekazaliśmy dodatkową funkcjonalność do platformy addMouseListener. Krótko mówiąc, nie jest łatwo przekazać prostą metodę (funkcjonalność) w Javie za pomocą argumentów. To ograniczenie zmusiło programistów Java 8 do dodania takiej funkcji, jak wyrażenia Lambda, do specyfikacji języka.

Dlaczego Java potrzebuje wyrażeń Lambda?

Od samego początku język Java nie ewoluował zbytnio, z wyjątkiem takich rzeczy jak adnotacje, teksty ogólne itp. Po pierwsze, Java zawsze pozostawała zorientowana obiektowo. Po pracy z językami funkcjonalnymi, takimi jak JavaScript, można zrozumieć, jak Java jest ściśle zorientowana obiektowo i silnie typowana. Funkcje nie są potrzebne w Javie. Same w sobie nie można ich znaleźć w świecie Java. W funkcjonalnych językach programowania na pierwszy plan wysuwają się funkcje. Istnieją samodzielnie. Można je przypisywać do zmiennych i przekazywać poprzez argumenty do innych funkcji. JavaScript jest jednym z najlepszych przykładów funkcjonalnych języków programowania. W Internecie można znaleźć dobre artykuły szczegółowo opisujące zalety JavaScript jako języka funkcjonalnego. Języki funkcjonalne posiadają potężne narzędzia takie jak Closure, które zapewniają szereg zalet w porównaniu z tradycyjnymi metodami pisania aplikacji. Zamknięcie to funkcja z dołączonym środowiskiem - tabelą przechowującą odniesienia do wszystkich nielokalnych zmiennych funkcji. W Javie zamknięcia można symulować za pomocą wyrażeń Lambda. Oczywiście istnieją różnice pomiędzy domknięciami i wyrażeniami Lambda, i to nie małymi, ale wyrażenia lambda są dobrą alternatywą dla domknięć. Na swoim sarkastycznym i zabawnym blogu Steve Yegge opisuje, jak świat Javy jest ściśle powiązany z rzeczownikami (bytami, obiektami – ok. tłum. ). Jeśli nie czytaliście jego bloga, polecam. W zabawny i ciekawy sposób opisuje dokładny powód dodania wyrażeń Lambda do Javy. Wyrażenia lambda wprowadzają do języka Java funkcjonalność, której tak długo brakowało. Wyrażenia lambda wnoszą do języka funkcjonalność, podobnie jak obiekty. Chociaż nie jest to w 100% prawdą, widać, że wyrażenia Lambda, choć nie są domknięciami, zapewniają podobne możliwości. W języku funkcjonalnym wyrażenia lambda są funkcjami; ale w Javie wyrażenia lambda są reprezentowane przez obiekty i muszą być powiązane z określonym typem obiektu zwanym interfejsem funkcjonalnym. Następnie przyjrzymy się, co to jest. Artykuł Mario Fusco „Dlaczego potrzebujemy wyrażenia Lambda w Javie” szczegółowo opisuje, dlaczego wszystkie współczesne języki potrzebują możliwości domknięcia.

Wprowadzenie do wyrażeń lambda

Wyrażenia lambda są funkcjami anonimowymi (może nie być w 100% poprawną definicją w Javie, ale zapewnia pewną przejrzystość). Mówiąc najprościej, jest to metoda bez deklaracji, tj. bez modyfikatorów dostępu, zwracających wartość i nazwę. Krótko mówiąc, pozwalają na napisanie metody i natychmiastowe jej użycie. Jest to szczególnie przydatne w przypadku jednorazowego wywołania metody, ponieważ skraca czas potrzebny na zadeklarowanie i napisanie metody bez konieczności tworzenia klasy. Wyrażenia lambda w Javie mają zazwyczaj następującą składnię (аргументы) -> (тело). Na przykład:
(арг1, арг2...) -> { тело }

(тип1 арг1, тип2 арг2...) -> { тело }
Poniżej znajduje się kilka przykładów rzeczywistych wyrażeń Lambda:
(int a, int b) -> {  return a + b; }

() -> System.out.println("Hello World");

(String s) -> { System.out.println(s); }

() -> 42

() -> { return 3.1415 };

Struktura wyrażeń lambda

Przeanalizujmy strukturę wyrażeń lambda:
  • Wyrażenia lambda mogą mieć 0 lub więcej parametrów wejściowych.
  • Typ parametrów można określić jawnie lub można go uzyskać z kontekstu. Na przykład ( int a) można zapisać w ten sposób ( a)
  • Parametry są ujęte w nawiasy i oddzielane przecinkami. Na przykład ( a, b) lub ( int a, int b) lub ( String a, int b, float c)
  • Jeśli nie ma parametrów, należy użyć pustych nawiasów. Na przykład() -> 42
  • Jeżeli występuje tylko jeden parametr i typ nie jest określony jawnie, nawiasy można pominąć. Przykład:a -> return a*a
  • Treść wyrażenia Lambda może zawierać 0 lub więcej wyrażeń.
  • Jeżeli treść składa się z pojedynczej instrukcji, nie może być ona ujęta w nawiasy klamrowe, a wartość zwracana może być podana bez słowa kluczowego return.
  • W przeciwnym razie wymagane są nawiasy klamrowe (blok kodu), a wartość zwracana musi być podana na końcu za pomocą słowa kluczowego return(w przeciwnym razie typem zwrotu będzie void).

Co to jest interfejs funkcjonalny

W Javie interfejsy Markera są interfejsami bez deklarowania metod i pól. Innymi słowy, interfejsy tokenów są pustymi interfejsami. Podobnie interfejsy funkcjonalne to interfejsy z zadeklarowaną tylko jedną abstrakcyjną metodą. java.lang.Runnablejest przykładem funkcjonalnego interfejsu. Deklaruje tylko jedną metodę void run(). Jest też interfejs ActionListener– także funkcjonalny. Wcześniej do tworzenia obiektów implementujących funkcjonalny interfejs musieliśmy używać klas anonimowych. Dzięki wyrażeniom Lambda wszystko stało się prostsze. Każde wyrażenie lambda można niejawnie powiązać z pewnym interfejsem funkcjonalnym. Można na przykład utworzyć odwołanie do Runnableinterfejsu, jak pokazano w poniższym przykładzie:
Runnable r = () -> System.out.println("hello world");
Ten rodzaj konwersji jest zawsze wykonywany domyślnie, gdy nie określimy interfejsu funkcjonalnego:
new Thread(
    () -> System.out.println("hello world")
).start();
W powyższym przykładzie kompilator automatycznie tworzy wyrażenie lambda jako implementację Runnableinterfejsu z konstruktora klasy Thread: public Thread(Runnable r) { }. Oto kilka przykładów wyrażeń lambda i odpowiadających im interfejsów funkcjonalnych:
Consumer<Integer> c = (int x) -> { System.out.println(x) };

BiConsumer<Integer, String> b = (Integer x, String y) -> System.out.println(x + " : " + y);

Predicate<String> p = (String s) -> { s == null };
Adnotacja @FunctionalInterfacedodana w Javie 8 zgodnie ze specyfikacją języka Java sprawdza, czy zadeklarowany interfejs działa. Ponadto Java 8 zawiera szereg gotowych interfejsów funkcjonalnych do wykorzystania z wyrażeniami Lambda. @FunctionalInterfacezgłosi błąd kompilacji, jeśli zadeklarowany interfejs nie działa. Poniżej znajduje się przykład zdefiniowania interfejsu funkcjonalnego:
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

}
Jak sugeruje definicja, interfejs funkcjonalny może mieć tylko jedną metodę abstrakcyjną. Jeśli spróbujesz dodać inną metodę abstrakcyjną, pojawi się błąd kompilacji. Przykład:
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

    public void doSomeMoreWork();

}
Błąd
Unexpected @FunctionalInterface annotation
    @FunctionalInterface ^ WorkerInterface is not a functional interface multiple
    non-overriding abstract methods found in interface WorkerInterface 1 error
После определения функционального интерфейса, мы можем его использовать и получать все преимущества Lambda-выражений. Пример:// definiowanie funkcjonalnego interfejsu
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

}
public class WorkerInterfaceTest {

    public static void execute(WorkerInterface worker) {
        worker.doSomeWork();
    }

    public static void main(String [] args) {

      // wywołanie metody doSomeWork poprzez anonimową klasę
      // (klasyczny)
      execute(new WorkerInterface() {
            @Override
            public void doSomeWork() {
               System.out.println(„Pracownik wywoływany przez anonimową klasę”);
            }
        });

      // wywołanie metody doSomeWork poprzez wyrażenia Lambda
      // (Java 8 nowość)
      execute( () -> System.out.println(„Pracownik wezwany przez Lambda”) );
    }

}
Wniosek:
Worker вызван через анонимный класс
Worker вызван через Lambda
Tutaj zdefiniowaliśmy własny interfejs funkcjonalny i użyliśmy wyrażenia lambda. Metoda execute()może akceptować wyrażenia lambda jako argument.

Przykłady wyrażeń Lambda

Najlepszym sposobem zrozumienia wyrażeń Lambda jest przyjrzenie się kilku przykładom: Strumień Threadmożna zainicjować na dwa sposoby:
// Stara droga:
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello from thread");
    }
}).start();
// Nowy sposób:
new Thread(
    () -> System.out.println("Hello from thread")
).start();
Zarządzanie zdarzeniami w Javie 8 można również wykonać za pomocą wyrażeń Lambda. Poniżej przedstawiono dwa sposoby dodania modułu obsługi zdarzeń ActionListenerdo komponentu interfejsu użytkownika:
// Stara droga:
button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println(„Przycisk wciśnięty. Po staremu!”);
    }
});
// Nowy sposób:
button.addActionListener( (e) -> {
        System.out.println(„Przycisk wciśnięty. Lambda!”);
});
Prosty przykład wyświetlenia wszystkich elementów danej tablicy. Należy pamiętać, że istnieje więcej niż jeden sposób użycia wyrażenia lambda. Poniżej tworzymy wyrażenie lambda w zwykły sposób, używając składni strzałek, a także używamy operatora podwójnego dwukropka (::), który w Javie 8 konwertuje metodę regularną na wyrażenie lambda:
// Stara droga:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
for(Integer n: list) {
    System.out.println(n);
}
// Nowy sposób:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
list.forEach(n -> System.out.println(n));
// Nowy sposób z użyciem operatora podwójnego dwukropka ::
list.forEach(System.out::println);
W poniższym przykładzie używamy interfejsu funkcjonalnego Predicatedo utworzenia testu i wydrukowania elementów, które przeszły ten test. W ten sposób możesz umieścić logikę w wyrażeniach lambda i robić na jej podstawie różne rzeczy.
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

public class Main {

    public static void main(String [] a)  {

        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);

        System.out.print("Wyprowadza wszystkie liczby: ");
        evaluate(list, (n)->true);

        System.out.print(„Nie wyświetla żadnej liczby:”);
        evaluate(list, (n)->false);

        System.out.print("Wyprowadź liczby parzyste: ");
        evaluate(list, (n)-> n%2 == 0 );

        System.out.print("Wyprowadź liczby nieparzyste: ");
        evaluate(list, (n)-> n%2 == 1 );

        System.out.print("Liczby wyjściowe większe niż 5: ");
        evaluate(list, (n)-> n > 5 );

    }

    public static void evaluate(List<Integer> list, Predicate<Integer> predicate) {
        for(Integer n: list)  {
            if(predicate.test(n)) {
                System.out.print(n + " ");
            }
        }
        System.out.println();
    }

}
Wniosek:
Выводит все числа: 1 2 3 4 5 6 7
Не выводит ни одного числа:
Вывод четных чисел: 2 4 6
Вывод нечетных чисел: 1 3 5 7
Вывод чисел больше 5: 6 7
Majsterkując przy wyrażeniach Lambda, możesz wyświetlić kwadrat każdego elementu listy. Zauważ, że używamy tej metody stream()do konwersji zwykłej listy na strumień. Java 8 zapewnia niesamowitą klasę Stream( java.util.stream.Stream). Zawiera mnóstwo przydatnych metod, za pomocą których można używać wyrażeń lambda. Przekazujemy wyrażenie lambda x -> x*xdo metody map(), która stosuje je do wszystkich elementów w strumieniu. Po czym drukujemy forEachwszystkie elementy listy.
// Stara droga:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
for(Integer n : list) {
    int x = n * n;
    System.out.println(x);
}
// Nowy sposób:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
list.stream().map((x) -> x*x).forEach(System.out::println);
Mając listę, musisz wydrukować sumę kwadratów wszystkich elementów listy. Wyrażenia lambda pozwalają to osiągnąć, pisząc tylko jedną linię kodu. W tym przykładzie zastosowano metodę splotu (redukcji) reduce(). Używamy metody map()do kwadratowania każdego elementu, a następnie używamy metody reduce()do zwijania wszystkich elementów w jedną liczbę.
// Stara droga:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
int sum = 0;
for(Integer n : list) {
    int x = n * n;
    sum = sum + x;
}
System.out.println(sum);
// Nowy sposób:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
int sum = list.stream().map(x -> x*x).reduce((x,y) -> x + y).get();
System.out.println(sum);

Różnica między wyrażeniami Lambda a klasami anonimowymi

Główną różnicą jest użycie słowa kluczowego this. W przypadku klas anonimowych słowo kluczowe „” thisoznacza obiekt klasy anonimowej, natomiast w wyrażeniu lambda „ this” oznacza obiekt klasy, w której zastosowano wyrażenie lambda. Kolejną różnicą jest sposób ich kompilacji. Java kompiluje wyrażenia lambda i konwertuje je na privatemetody klasowe. Wykorzystuje to instrukcję invokedynamic wprowadzoną w Javie 7 do dynamicznego wiązania metod. Tal Weiss opisał na swoim blogu, jak Java kompiluje wyrażenia lambda do kodu bajtowego

Wniosek

Mark Reinhold (główny architekt Oracle) nazwał Lambdę „najbardziej znaczącą zmianą w modelu programowania, jaka kiedykolwiek nastąpiła” – nawet bardziej znaczącą niż zmiany generyczne. Musi mieć rację, bo... dają programistom Java możliwości funkcjonalnego języka programowania, na które wszyscy czekali. Wraz z innowacjami takimi jak metody rozszerzenia wirtualnego, wyrażenia Lambda pozwalają na pisanie kodu o bardzo wysokiej jakości. Mam nadzieję, że ten artykuł pozwolił ci zajrzeć pod maskę Java 8. Powodzenia :)
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION