18. JAVA – wyrażenia Lambda


Wyrażenia lambda inaczej zwane domknięciami, to fragmenty kodu, które przekazujemy argumentem. Mogą zostać przypisane do zmiennej i przekazywane np. w pracy z listami.

Wyrażenia lambda wykorzystujemy z interfejsami funkcyjnymi posiadającymi jedną abstrakcyjną metodę.

Interfejs funkcyjny może posiadać tylko jedną metodę abstrakcyjną, ale może posiadać wiele metod default i static. Metody default nie muszą być implementowane przez klasy korzystające z interfejsu (przydaje się to gdy już istniejący interfejs należy rozszerzyć).
Jeżeli w kodzie natrafimy na znak „->” oznacza on właśnie wykorzystanie lambdy.

 

Przykład:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.function.Predicate;

//tworzymy interfejs funkcyjny do mnożenia 2 intów
interface Multiplication{
int multi(int x, int y);
}

//interfejs z metodą generyczną dzielenia
interface Division<T>{
T div(T x, T y);
}

//krok4 - dodamy ogólny szablon dla działań matematycznych z typem generycznym
//będzie zracany wynik jakiegoś typu z metody, która będzie posiadała 2 parametry
//dzięki temu nie musimy definiować wielu interfejsów jw.
interface Operations<T>{
T operate(T x, T y);
}

public class Lambda {

//krok3 - przekazujemy wyrażenie lambda jako argument metody
public void lambda(Multiplication multi){
System.out.println("Wynik z metody pobierającej lambdę " + multi.multi(10,10));
}

public static void main(String[] args) {
//krok1 - wykorzystujemy wyrażenie lambda
Multiplication multi = (int x, int y) -> x * y;
System.out.println("Mnożenie wynosi " + multi.multi(2,3));

//krok2 - wykorzystujemy interfejs generyczny
Division<Float> div = (Float x, Float y) -> {//użyjemy nawiasów klamrowych dla bloku kodu
float d = x/y;
return d;
};//dajemy średnik, bo to koniec linijki kodu!
float d = div.div(10f,2f);
System.out.println("Dzielenie wynosi " + d);

//cd krok3 - wykorzystamy metodę
Lambda lambdaExampleWithMethod = new Lambda();
lambdaExampleWithMethod.lambda(multi);
lambdaExampleWithMethod.lambda( (x,y) -> (x+2) * y );//modyfikujemy dane, nie podaję typów, bo Java sama się domyśli

//krok4 - wykorzystujemy szablon ogólny Operations
Operations</*typ w zależności od potrzeb*/Integer> add = /*podpinamy teraz wyrażenie lambda*/ (x,y)->x+y;
System.out.println("Metoda lambda z dodawaniem " + add.operate(5,6));

Operations<Integer> multipl = (x,y)->x*y;
System.out.println("Metoda lambda z mnożeniem " + add.operate(5,6));

//wykorzystajmy wyrażenie lambda do sortowania elementów w tablicy ze względu na długość
String[] arr = {"Python", "Java", "PHP", "C++", "HTML+CSS"};
//korzystamy z metody sort dla Arrays
Arrays.sort(arr, /*tworzymy wyrażenie lambda*/(x,y)->{return x.length() - y.length();});
for (String a : arr) {
System.out.println(a);
}

//wyrażenie lambda może korzystać z wbudowanych interfejsów funkcyjnych z pakietu java.util.functions
//np. Predicate, żeby usunąć element z listy

//tworzymy nową listę
ArrayList<String> al = new ArrayList<>();
al.add("PHP");
al.add("Python");
al.add(null);
al.add("Java");
al.add("C++");
al.add("Pascal");
al.add(null);

//skorzystamy z metody removeIf
//dodajmy na górze import pakietu functions
//usuń element jeżeli x jest równe null lub jest równe "Pascal"
al.removeIf( x -> x == null || x.equalsIgnoreCase("Pascal"));
System.out.println(al); //otrzymujemy oczyszczoną listę


}
}

 

Przykład: Wyrażenie lambda i default


@FunctionalInterface
interface Operators{
int operate(int x, int y);

default int add(int x, int y){
System.out.println("Metoda add z interfejsu");
return x+y;
}
default int subtract(int x, int y){
System.out.println("Metoda subtract z intefejsu");
return x-y;
}
default int multi(int x, int y){
System.out.println("Metoda multi z intefejsu");
return x*y;
}
static int divide(int x, int y){
System.out.println("Metoda divide z intefejsu");
return x/y;
}
}

//tworzymy klasę implementującą nasz interfejs
class OperatorsClass implements Operators{

//przesłaniamy metody abstrakcyjne

@Override
public int multi(int x, int y) {
System.out.println("OperatorsClass.multi()");
return x*y;
}

@Override
public int operate(int x, int y) {
System.out.println("OperatorsClass.operate()");
return x+y;
}

//możemy wykorzystać domyślną implementację metody wewnątrz interfejsu

@Override
public int add(int x, int y) {
//wywołamy metodę domyślną interfejsu
System.out.println("OperatorsClass.add()");
return Operators.super.add(x,y);
}
}

public class LambdaAndDefaultMethods {
public static void main(String[] args) {
Operators math = (int x, int y) -> x * y;
System.out.println(math.operate(5,2));

System.out.println(Operators.divide(55,11));

System.out.println(math.multi(5,2));

OperatorsClass operatorsClass = new OperatorsClass();
System.out.println(operatorsClass.operate(12,2));
System.out.println(operatorsClass.multi(2,2));
System.out.println(operatorsClass.subtract(20,10));
System.out.println(operatorsClass.add(20,10));
}
}

 

Referencja do metody

Z wyrażeniem lambda związane jest pojęcie referencji do metody.
Oznacza to, że możemy się odwołać do metody bez jej wywołania, potrzebuje jednak interfejsu funkcyjnego, gdyż referencja do metody tworzy instancję tegoż interfejsu.


Konstrukcja to: nazwaklasy::nazwametody.

Wykorzystamy referencję lambda do wyświetlenia hashu danego obiektu:

import java.util.function.IntSupplier;

public class LambdaReferenceToMethods {

//stworzymy własną metodę wykorzystującą ntSupplier
public void myFunction(IntSupplier intSupplier){
System.out.println(intSupplier.getAsInt());
}

public static void main(String[] args) {
Object obj = new Object();
//wywołujemy interfejs funkcyjny zwracający int bezargumentowo - IntSupploer
//jeżeli po obj damy kropkę zobaczymy listę dostępnych metod, ale my chcemy dostać się do metody poprzez referencję czyli wykorzystamy ::
IntSupplier intSupplier = obj::hashCode;

//wywołujemy metodę interfejsu funkcyjnego
System.out.println(intSupplier.getAsInt());

//tworzymy nową instancję z wykorzystaniem własnej metody
//otrzymamy ten sam wynik jw
LambdaReferenceToMethods lr = new LambdaReferenceToMethods();
lr.myFunction(intSupplier);

}
}

 

Lambda i Timer

Przykład wykorzystania lambdy wraz z klasą Timer do wyświetlania w okienku (klasa JFrame) aktualnego czasu co 1s.

import javax.swing.*;
import java.util.Date;

public class LambdaAndTimer extends JFrame {
public static void main(String[] args) {
//tworzymy okienko
LambdaAndTimer lat = new LambdaAndTimer();
lat.setTitle("Timer");//ustawiamy tytuł
lat.setSize(300,100);//ustawiamy rozmiar okna
lat.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//ustalamy co się stanie po naciśnięciu przycisku zamknięcia

//miejsce gdzie będzie wyświetlany tekst w pojedynczej linijce
JLabel lbl = new JLabel();
lat.add(lbl);//dodajemy label do timera

//wyświetlamy
lat.setVisible(true);

//tworzymy timer - ustawiamy czas na 1s(1000ms) i przekazujemy lambdę
Timer timer = new Timer(1000, event ->{//aktualizujemy co 1s lbl i terminal
Date date = new Date(); //pobieramy aktualną datę
System.out.println(date.toString()); //wyświetlamy w terminalu
lbl.setText(date.toString()); //wyświetlamy w label
});
//startujemy odliczanie
timer.start();


}
}