1. Interfețe

Pentru a înțelege ce sunt funcțiile lambda, mai întâi trebuie să înțelegeți ce sunt interfețele. Deci, să ne amintim punctele principale.

O interfață este o variație a conceptului de clasă. O clasă puternic trunchiată, să spunem. Spre deosebire de o clasă, o interfață nu poate avea propriile variabile (cu excepția celor statice). De asemenea, nu puteți crea obiecte al căror tip este o interfață:

  • Nu puteți declara variabile ale clasei
  • Nu poți crea obiecte

Exemplu:

interface Runnable
{
   void run();
}
Exemplu de interfață standard

Folosind o interfață

Deci de ce este nevoie de o interfață? Interfețele sunt folosite numai împreună cu moștenirea. Aceeași interfață poate fi moștenită de clase diferite sau, după cum se mai spune, clasele implementează interfața .

Dacă o clasă implementează o interfață, trebuie să implementeze metodele declarate de, dar nu implementate de interfață. Exemplu:

interface Runnable
{
   void run();
}

class Timer implements Runnable
{
   void run()
   {
      System.out.println(LocalTime.now());
   }
}

class Calendar implements Runnable
{
   void run()
   {
      var date = LocalDate.now();
      System.out.println("Today: " + date.getDayOfWeek());
   }
}

Clasa Timerimplementează Runnableinterfața, deci trebuie să declare în interiorul ei toate metodele care se află în Runnableinterfață și să le implementeze, adică să scrie cod într-un corp de metodă. Același lucru este valabil și pentru Calendarclasă.

Dar acum Runnablevariabilele pot stoca referințe la obiecte care implementează Runnableinterfața.

Exemplu:

Cod Notă
Timer timer = new Timer();
timer.run();

Runnable r1 = new Timer();
r1.run();

Runnable r2 = new Calendar();
r2.run();

Metoda run()din Timerclasă va fi numită


Metoda run()din Timerclasă va fi numită


Metoda run()din Calendarclasă va fi numită

Puteți atribui oricând o referință de obiect unei variabile de orice tip, atâta timp cât acel tip este una dintre clasele strămoși ale obiectului. Pentru clasele Timerși Calendar, există două astfel de tipuri: Objectși Runnable.

Dacă atribuiți o referință de obiect unei Objectvariabile, puteți apela doar metodele declarate în Objectclasă. Și dacă atribuiți o referință de obiect unei Runnablevariabile, puteți apela metodele tipului Runnable.

Exemplul 2:

ArrayList<Runnable> list = new ArrayList<Runnable>();
list.add (new Timer());
list.add (new Calendar());

for (Runnable element: list)
    element.run();

Acest cod va funcționa, deoarece obiectele Timerși Calendarau rulat metode care funcționează perfect. Deci, apelarea lor nu este o problemă. Dacă tocmai am fi adăugat o metodă run() la ambele clase, atunci nu am fi capabili să le apelăm într-un mod atât de simplu.

Practic, Runnableinterfața este folosită doar ca loc pentru a pune metoda de rulare.



2. Sortarea

Să trecem la ceva mai practic. De exemplu, să ne uităm la sortarea șirurilor.

Pentru a sorta o colecție de șiruri alfabetic, Java are o metodă grozavă numităCollections.sort(collection);

Această metodă statică sortează colecția trecută. Și în procesul de sortare, efectuează comparații în perechi ale elementelor sale pentru a înțelege dacă elementele ar trebui schimbate.

În timpul sortării, aceste comparații sunt efectuate folosind compareTometoda (), pe care toate clasele standard o au: Integer, String, ...

Metoda compareTo() a clasei Integer compară valorile a două numere, în timp ce metoda compareTo() a clasei String analizează ordinea alfabetică a șirurilor.

Deci o colecție de numere va fi sortată în ordine crescătoare, în timp ce o colecție de șiruri va fi sortată alfabetic.

Algoritmi alternativi de sortare

Dar dacă vrem să sortăm șirurile nu în ordine alfabetică, ci după lungimea lor? Și dacă vrem să sortăm numerele în ordine descrescătoare? Ce faci in acest caz?

Pentru a gestiona astfel de situații, Collectionsclasa are o altă sort()metodă care are doi parametri:

Collections.sort(collection, comparator);

Unde comparatorul este un obiect special care știe să compare obiectele dintr-o colecție în timpul unei operații de sortare . Termenul comparator provine din cuvântul englez comparator , care, la rândul său, derivă din compare , însemnând „a compara”.

Deci, ce este acest obiect special?

Comparatorinterfață

Ei bine, totul este foarte simplu. Tipul celui de sort()-al doilea parametru al metodei esteComparator<T>

Unde T este un parametru de tip care indică tipul elementelor din colecție și Comparatoreste o interfață care are o singură metodăint compare(T obj1, T obj2);

Cu alte cuvinte, un obiect comparator este orice obiect din orice clasă care implementează interfața Comparator. Interfața Comparator pare foarte simplă:

public interface Comparator<Type>
{
   public int compare(Type obj1, Type obj2);
}
Cod pentru interfața Comparator

Metoda compare()compară cele două argumente care îi sunt transmise.

Dacă metoda returnează un număr negativ, înseamnă obj1 < obj2. Dacă metoda returnează un număr pozitiv, înseamnă obj1 > obj2. Dacă metoda returnează 0, înseamnă obj1 == obj2.

Iată un exemplu de obiect comparator care compară șirurile după lungime:

public class StringLengthComparator implements Comparator<String>
{
   public int compare (String obj1, String obj2)
   {
      return obj1.length() – obj2.length();
   }
}
Codul StringLengthComparatorclasei

Pentru a compara lungimile șirurilor, pur și simplu scădeți o lungime din cealaltă.

Codul complet pentru un program care sortează șirurile după lungime ar arăta astfel:

public class Solution
{
   public static void main(String[] args)
   {
      ArrayList<String> list = new ArrayList<String>();
      Collections.addAll(list, "Hello", "how's", "life?");
      Collections.sort(list, new StringLengthComparator());
   }
}

class StringLengthComparator implements Comparator<String>
{
   public int compare (String obj1, String obj2)
   {
      return obj1.length() – obj2.length();
   }
}
Sortarea șirurilor după lungime


3. Zahar sintactic

Ce crezi, se poate scrie acest cod mai compact? Practic, există o singură linie care conține informații utile — obj1.length() - obj2.length();.

Dar codul nu poate exista în afara unei metode, așa că a trebuit să adăugăm o compare()metodă, iar pentru a stoca metoda a trebuit să adăugăm o nouă clasă — StringLengthComparator. Și trebuie să specificăm și tipurile de variabile... Totul pare să fie corect.

Dar există modalități de a face acest cod mai scurt. Avem niște zahăr sintactic pentru tine. Două linguri!

Clasa interioară anonimă

Puteți scrie codul comparator chiar în interiorul main()metodei, iar compilatorul se va ocupa de restul. Exemplu:

public class Solution
{
    public static void main(String[] args)
    {
        ArrayList<String> list = new ArrayList<String>();
        Collections.addAll(list, "Hello", "how's", "life?");

        Comparator<String> comparator = new Comparator<String>()
        {
            public int compare (String obj1, String obj2)
            {
                return obj1.length() – obj2.length();
            }
        };

        Collections.sort(list, comparator);
    }
}
Sortați șirurile după lungime

Puteți crea un obiect care implementează Comparatorinterfața fără a crea în mod explicit o clasă! Compilatorul îl va crea automat și îi va da un nume temporar. Să comparăm:

Comparator<String> comparator = new Comparator<String>()
{
    public int compare (String obj1, String obj2)
    {
        return obj1.length() – obj2.length();
    }
};
Clasa interioară anonimă
Comparator<String> comparator = new StringLengthComparator();

class StringLengthComparator implements Comparator<String>
{
    public int compare (String obj1, String obj2)
    {
        return obj1.length() – obj2.length();
    }
}
StringLengthComparatorclasă

Aceeași culoare este folosită pentru a indica blocuri de cod identice în cele două cazuri diferite. Diferențele sunt destul de mici în practică.

Când compilatorul întâlnește primul bloc de cod, pur și simplu generează un al doilea bloc de cod corespunzător și dă clasei un nume aleatoriu.


4. Expresii lambda în Java

Să presupunem că decideți să utilizați o clasă interioară anonimă în codul dvs. În acest caz, veți avea un bloc de cod ca acesta:

Comparator<String> comparator = new Comparator<String>()
{
    public int compare (String obj1, String obj2)
    {
        return obj1.length() – obj2.length();
    }
};
Clasa interioară anonimă

Aici combinăm declararea unei variabile cu crearea unei clase anonime. Dar există o modalitate de a face acest cod mai scurt. De exemplu, așa:

Comparator<String> comparator = (String obj1, String obj2) ->
{
    return obj1.length() – obj2.length();
};

Este nevoie de punct și virgulă deoarece aici nu avem doar o declarație implicită de clasă, ci și crearea unei variabile.

O notație ca aceasta se numește expresie lambda.

Dacă compilatorul întâlnește astfel de notații în codul dvs., pur și simplu generează versiunea verbosă a codului (cu o clasă interioară anonimă).

Rețineți că atunci când scriem expresia lambda, am omis nu numai numele clasei , ci și numele metodei .Comparator<String>int compare()

Compilarea nu va avea nicio problemă în determinarea metodei , deoarece o expresie lambda poate fi scrisă doar pentru interfețele care au o singură metodă . Apropo, există o modalitate de a ocoli această regulă, dar veți afla despre asta atunci când începeți să studiați OOP mai în profunzime (vorbim despre metode implicite).

Să ne uităm din nou la versiunea verbosă a codului, dar vom dezactiva partea care poate fi omisă când scriem o expresie lambda:

Comparator<String> comparator = new Comparator<String>()
{
    public int compare (String obj1, String obj2)
   {
      return obj1.length() – obj2.length();
   }
};
Clasa interioară anonimă

Se pare că nimic important nu a fost omis. Într-adevăr, dacă Comparatorinterfața are doar o singură compare()metodă, compilatorul poate recupera în întregime codul gri din codul rămas.

Triere

Apropo, acum putem scrie codul de sortare astfel:

Comparator<String> comparator = (String obj1, String obj2) ->
{
   return obj1.length() – obj2.length();
};
Collections.sort(list, comparator);

Sau chiar asa:

Collections.sort(list, (String obj1, String obj2) ->
   {
      return obj1.length() – obj2.length();
   }
);

Pur și simplu am înlocuit imediat comparatorvariabila cu valoarea care a fost atribuită variabilei comparator.

Inferență de tip

Dar asta nu este tot. Codul din aceste exemple poate fi scris și mai compact. În primul rând, compilatorul poate determina singur că variabilele obj1și obj2sunt Strings. Și în al doilea rând, acoladele și instrucțiunea return pot fi, de asemenea, omise dacă aveți doar o singură comandă în codul metodei.

Versiunea scurtată ar fi așa:

Comparator<String> comparator = (obj1, obj2) ->
   obj1.length() – obj2.length();

Collections.sort(list, comparator);

Și dacă în loc să folosim comparatorvariabila, îi folosim imediat valoarea, atunci obținem următoarea versiune:

Collections.sort(list, (obj1, obj2) ->  obj1.length() — obj2.length() );

Ei bine, ce crezi despre asta? Doar o linie de cod fără informații de prisos - doar variabile și cod. Nu ai cum să-l scurtezi! Sau există?



5. Cum funcționează

De fapt, codul poate fi scris și mai compact. Dar mai multe despre asta mai târziu.

Puteți scrie o expresie lambda în care ați folosi un tip de interfață cu o singură metodă.

De exemplu, în cod , puteți scrie o expresie lambda deoarece semnătura metodei este astfel:Collections.sort(list, (obj1, obj2) -> obj1.length() - obj2.length());sort()

sort(Collection<T> colls, Comparator<T> comp)

Când am trecut ArrayList<String>colecția ca prim argument la metoda de sortare, compilatorul a putut determina că tipul celui de-al doilea argument este . Și din aceasta, a concluzionat că această interfață are o singură metodă. Orice altceva este o tehnică.Comparator<String>int compare(String obj1, String obj2)