Lambda išraiškos

M.Gžegoževskis ... 2021-10-01 Java
  • Programavimas
  • Java
About 5 min

Lambda išraiškos (angl. lambda expressions) atsirado nuo JDK (opens new window) 1.8 versijos. Šis atnaujinimas įvedė į programavimo kalbą Java (opens new window) naujų vėjų. Tai vienas iš didžiausių pokyčių per pastaruosius metus, kai 1.5 JDK (opens new window) buvo pasiūlytas generic (opens new window) tipas tai buvo vienas iš didžiausių pokyčių, kuris pakeitė kodo rašymo stilių. Taip pat dalis karkasų buvo perrašytų naudojant Lambda išraiškas, kad išvengtų vykdymo metu įvykstančių klaidų pvz. ClassCastException, kuri įvykdavo dėl kolekcijoje esančio elemento tipo konversijos. Kodėl Lambda išraiškos yra naudojamos?

  • Įgalina naudoti funkcinį programavimą (angl. functional programming).
  • Palengvina kodo skaitomumą, sumažina kodo kiekį.
  • Sukurti APIs (opens new window) ir bibliotekos yra aiškesnės struktūros, todėl yra žymiai lengviau jais pasinaudoti (angl. Easy-to-use).
  • Darbas su kolekcijomis (angl. Collections) tampa žymiai paprastesnis tiek kodo aiškumo prasme, tiek sukurtaisiais metodais skirtais išlygiagretinti programos vykdymą skirtą kompiuteriui turinčiam daugiau nei vieną branduolį.

Kodo struktūra naudojant Java JDK (opens new window) 1.7 versiją, kada dar nebuvo lambda išraiškų.

public class Greeter { 
  public void greet(){ 
    System.out.println("Hello world!"); 
 }
 public static void main(String[] args) {    
   Greeter greeter = new Greeter(); 
   greeter.greet(); 
 }
}
1
2
3
4
5
6
7
8
9

Turint 1.7 JDK (opens new window) versiją metodui perduoti parametrą kaip funkciją su skirtinga elgsena yra įmanomas naudojant interfeisus. Kaip tai atrodytų programiškai? Privalome modifikuoti klasę Greeter taip:

  1. Sukurti interfeisą pvz: Greeting ir jame vieną metodą perform();

  2. Kiekvienai naujai elgsenai privalote sukurti klasę ir joje įgyvendinti metodą perform() iš interfeiso Greeting.

Atlikus Greeter klasės greet() metodo modifikacija, kuriam perduosime norimą interfeiso egzempliorių ("funkciją") kaip metodo parametrą.

public void greet(Greeting greeting){ 
  greeting.perform(); 
}
public interface Greeting { // Interfeisas Greeting 
  void perform(); // Interfeiso metodas perform();
}
public class HelloWorld implements Greeting{ // Interfeiso panaudojimas
  @Override
  public void perform() { // Metodo realizacija
    System.out.println("Sveikas pasauli!!!"); 
  }
}

// Greeter klasė po atliktos modifikacijos

public class Greeter {
  public void greet(Greeting greeting){
    greeting.perform();
  }
  public static void main(String[] args) {
    Greeter greeter = new Greeter();
    HelloWorld helloWorld = new HelloWorld();
    greeter.greet(helloWorld);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

Kas yra Lambda? Anoniminė (izoliuota) vidinė funkcija. Bevardė funkcija be pasikartojančio (angl. boilerplate) kodo. Tenkina funkcinio interfeiso savoką. Interfeisas, kuris turi tik vieną abstraktų metodą. Lambda bendru atveju: <parametrų_sąrašas> -> <funkcijos_kūnas>. () - parametrų sąrašas, -> už strėlytės vykdomi sakiniai esantys aprašomos funkcijos bloke. Lambda išraiškos funkcijos kūnas gali būti sudarytas iš vieno sakinio arba kelių sakinių.

Jeigu naudojame vieną sakinį lambda išraišką galime aprašyti taip:

 Funkcinis_Interfeisas egzempliorius = () -> System.out.println("Sveikas pasauli!!!");
1

Jeigu naudojame kelis sakinius lambda išraišką galime aprašyti taip:

Funkcinis_Interfeisas egzempliorius = () ->{
     System.out.println("Sveikas pasauli!!!");
     System.out.println("Aš jau naudoju lambda išraiškas");
}
1
2
3
4

Pavyzdžiui turime interfeisą Greeting:

interface Greeting{
  void perform();
}
1
2
3

tada lambda išraišką galime aprašyti taip:

Greeting greeting = () -> System.out.println("Sveikas pasauli!!!");
// Sukurtas greeting egzempliorius. Panaudojus jį iškviečiame metodą perform(); kaip ir įprasta:
greeting.perform();
1
2
3

tada bus įvykdoma Lambda išraiška ir į ekraną išvedamas tekstas "Sveikas pasauli!!!"

Lambda supaprastina panaudojimą lyginant su JDK (opens new window) 1.7 versija, kur papildomai privaloma aprašyti atskiras klases ir jose aprašyti metodo logiką. Kiekvienai skirtingai funkcijai privalome sukurti atskirą įprastinę klasę arba anoniminę klasę. Turint Lambda viskas daug supaprastėjo pakanka turėti funkcinį interfeisą ir vietoj klasių kūrimo pakanka kurti Lambda išraiškas ir jas panaudoti pagal poreikį. Taip pat vienas iš esminių pliusų jog Lambda išraiškos iš dalies įgalino funkcinį programavimą. Šiam tikslui pasiekti buvo sukurtas java (opens new window).util.Function.*; karkasas (API (opens new window)) skirta darbui su funkciniais interfeisas. Kad kiekvienai situacijai nekurti individualaus interfeiso siūloma naudoti esamuosius:

  • Function<T, R> priima parametrą T, grąžina rezultatą R.
Funtion<Integer, String> funkcija = i-> "Įvedėte" + i;
1
  • BiFunction<T, U, R> priima parametrus T ir U, grąžina rezultatą R.
BiFuntion<Integer, Integer, String> duParametraiFunkcija = (a, b) -> "Suma:" + (a + b);
1
  • Consumer<T> priima parametrą T, grąžina rezultatą void
Consumer vykdytojas = i -> Sysyem.out.print(i); 
1
  • Supplier<T> nieko priima, grąžina rezultatą T
Supplier isduodantysis = () -> Math.Random();
1

Predicate<T> priima parametrą T, grąžina rezultatą boolean

Predicate<Integer> predikatas = i -> i % 2 == 0;
1

Daugiau interfeisų rasite čią: https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html (opens new window).

Sukurti funkcinį interfeisą yra labai paprasta, nes tai yra paprasčiausias Java (opens new window) interfeisas turintis nedaugiau nei vieną abstraktų metodą pvz:

// generic interfeisas su 2 parametrais
// T - perduodamas parametras
// R - parametras skirtas rezultatui grąžinti
@FunctionalInterface
interface Funkcija<T, R>{
  // abstraktus metodas priimantis vieną parametrą T
  // ir gražinantis rezultą R
  R pateikti(T t); 
}
// Sukurto funkcinio interfeiso panaudojimas
Funkcija<String, Integer> funkcija = (parametras) -> {
  return parametras.length();
};
// Sutrumpintas būdas jeigu Lambda išraiška yra vieno sakinio     
Funkcija<String, Integer> funkcija = parametras -> parametras.length(); 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Anotacija @FunctionalInterface privalote pažymėti interfeisą tuo atveju jeigu norite būti užtikrinti, kad jūsų interfeisas turės tik vieną abstraktų metodą ko ir reikia, kad tai būtų funkcinis interfeisas. Papildomai aprašomai funkcinio interfeiso klasėje galite turėti: default, static ir private metodus nuo JDK (opens new window) 1.8+. Visi interfeisai iš ankstesnių versijų nei JDK (opens new window) 1.8, kurie turi vieną abstraktų interfeisą gali būti naudojanti rašant Lambda išraiškas.

Geroji praktika yra naudoti Lambda išraiškas tik vieno sakinio. Kodėl privalome tai daryti, o todėl jog Lambda skirta supaprastinti kodo skaitomumą ir lengvą kodo derinimą įvykus klaidai reaguoti į ją. Panašiai kaip ir UNIT testai, rekomenduojama rašyti tik vieną assert metodą.

# Java metodo nuoroda

Java (opens new window) suteikia naują galimybę nuo JDK (opens new window) 1.8 vadinamą metodo nuorodą. Tai kompaktiška išraiška ir lengvai pakeičiama egzistuojanti Lambda išraiška į metodo nuorodą. Dažnai metodo nuoroda supaprastina skaitomumą, prieš mokinantis Lambda išraiškas rekomenduojama gerai susipažinti kaip veikia ir kaip aprašomos Lambda išraiškos, o tik tada pereiti į dar paprastesnį aprašymo būdą tai naudoti metodo nuorodą. Daugelis modernių programavimo IDE pateikia siūlymus supaprastinti kodą ir pakeisti esamas Lambda išraiškas į metodo nuorodas. Metodų nuorodos yra skirstomos į tris skirtingas kategorijas:

  1. Nuoroda į statinį metodą. Sintaksė: <KlasėKurYraMetodas>::<statinio_metodo_pavadinimas>(pvz. MethodReference::saySomething):
interface Sayable{  
    void say();  
}  
public class MethodReference {  
    public static void saySomething(){  
        System.out.println("Hello, this is static method.");  
    }  
    public static void main(String[] args) {  
        // Nuoroda į statinį metodą 
        Sayable sayable = MethodReference::saySomething;  
        // Iškviečiamas interfeiso metodas 
        sayable.say();  
    }  
}  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  1. Nuoroda į metodą naudojant klasės egzempliorių/objektą. Sintaksė: egzemplioriusKurYraAprašytasMetodas::egzemplioriausMetodas (pvz. methodReference::saySomething):
interface Sayable{  
    void say();  
}  
public class InstanceMethodReference {  
    public void saySomething(){  
        System.out.println("Hello, this is non-static method.");  
    }  
    public static void main(String[] args) {  
        InstanceMethodReference methodReference = new InstanceMethodReference(); // Kuriamas objektas  
           // Nuoroda iš nestatinio metodo naudojant objekto nuorodą 
            Sayable sayable = methodReference::saySomething;  
            // Iškviečiamas interfeiso metodas
            sayable.say();  
            // Nuoroda iš nestatinio metodo naudojant anoniminį objekto egzempliorių   
            Sayable sayable2 = new InstanceMethodReference()::saySomething; // Galima naudoti ir anoniminį klasės egzempliorių  
            // Iškviečiamas interfeiso metodas  
            sayable2.say();  
    }  
} 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  1. Nuorodą į kontruktorių. Sintaksė: KlasėsPavadinimas::new (pvz. Message::new):
essageable{  
    Message getMessage(String msg);  
}  
class Message{  
    Message(String msg){  
        System.out.print(msg);  
    }  
}  
public class ConstructorReference {  
    public static void main(String[] args) {  
        Messageable hello = Message::new;  
        hello.getMessage("Laba diena");  
    }  
} 
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Palyginimas tarp Java (opens new window) 7 ir Java (opens new window) 8 įvedūs Lambda išraiškas

Įprastinis grafinės sąsajos komponento interfeiso įvykio interfeiso aprašymas vietoj anoniminės klasės naudojama Lambda išraiška supaprastina kodo skaitomumą:

//Java 7
ActionListener al = new ActionListener() {
  @Override 
  public void actionPerformed(ActionEvent e) {
    System.out.println(e.getActionCommand());
  }
};
//Java 8
ActionListener al8 = e -> System.out.println(e.getActionCommand());
1
2
3
4
5
6
7
8
9

Teksto išvedimas į ekraną:

List list = Arrays.asList("Labas", "Viso gero");
//Java 7
for(String s : list) {
  System.out.println(s);
}
//Java 8
list.forEach(System.out::println);
1
2
3
4
5
6
7

Pavyzdys rušiuoti tekstui:

//Java 7
Collections.sort(list, new Comparator() { 
    @Override
    public int compare(String s1, String s2) {
        return s1.length() - s2.length();             
    }
});
//Java 8
Collections.sort(list, (s1, s2) -> s1.length()-s2.length()); 
// arba naudoti metodo nuoroda ir nuo JDK 1.8 versijos
// įvestą interfeiso Comparator metodą comparingInt
list.sort(Comparator.comparingInt(String::length)); 
1
2
3
4
5
6
7
8
9
10
11
12

Rūšiuoti tekstą pagal du parametrus naudojant savo sukurtą klasę Person:

public class Person {
  String firstName;
  String lastName;
  public String getFirstName() {
    return firstName;
  }
  public String getLastName() {
    return lastName;
  }
}
// Java 7
Collections.sort(list, new Comparator() {
  @Override
  public int compare(Person p1, Person p2) {
    int n = p1.getLastName().compareTo(p2.getLastName());
    if (n == 0) {
      return p1.getFirstName().compareTo(p2.getFirstName());
    }
  return n;
  }
});
//Java 8 naudojant metodo nuorodą
list.sort(Comparator.comparing(Person::getLastName).thenComparing(Person::getFirstName));  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Last update: September 20, 2021 06:35
Contributors: Marius Gžegoževskis