1. Grensesnitt

For å forstå hva lambda-funksjoner er, må du først forstå hva grensesnitt er. Så la oss huske hovedpoengene.

Et grensesnitt er en variant av konseptet til en klasse. En sterkt avkortet klasse, la oss si. I motsetning til en klasse, kan ikke et grensesnitt ha sine egne variabler (bortsett fra statiske). Du kan heller ikke opprette objekter hvis type er et grensesnitt:

  • Du kan ikke deklarere variabler for klassen
  • Du kan ikke lage objekter

Eksempel:

interface Runnable
{
   void run();
}
Eksempel på standard grensesnitt

Ved hjelp av et grensesnitt

Så hvorfor trengs et grensesnitt? Grensesnitt brukes kun sammen med arv. Det samme grensesnittet kan arves av forskjellige klasser, eller som det også er sagt - klasser implementerer grensesnittet .

Hvis en klasse implementerer et grensesnitt, må den implementere metodene deklarert av, men ikke implementert av grensesnittet. Eksempel:

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());
   }
}

Klassen Timerimplementerer Runnablegrensesnittet, så den må deklarere i seg selv alle metodene som er i grensesnittet Runnableog implementere dem, altså skrive kode i en metodekropp. Det samme gjelder klassen Calendar.

Men nå Runnablekan variabler lagre referanser til objekter som implementerer grensesnittet Runnable.

Eksempel:

Kode Merk
Timer timer = new Timer();
timer.run();

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

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

Metoden run()i Timerklassen vil hete


Metoden run()i Timerklassen vil hete


Metoden run()i Calendarklassen vil bli kalt

Du kan alltid tilordne en objektreferanse til en variabel av enhver type, så lenge den typen er en av objektets stamfarklasser. For klassene Timerog Calendarer det to slike typer: Objectog Runnable.

Hvis du tilordner en objektreferanse til en Objectvariabel, kan du bare kalle metodene som er deklarert i klassen Object. Og hvis du tilordner en objektreferanse til en Runnablevariabel, kan du kalle metodene for typen Runnable.

Eksempel 2:

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

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

Denne koden vil fungere, fordi Timerog- Calendarobjektene har kjørt metoder som fungerer utmerket. Så det er ikke noe problem å ringe dem. Hvis vi bare hadde lagt til en run()-metode til begge klassene, ville vi ikke kunne kalle dem på en så enkel måte.

I utgangspunktet Runnablebrukes grensesnittet kun som et sted å sette kjøremetoden.



2. Sortering

La oss gå videre til noe mer praktisk. La oss for eksempel se på sorteringsstrenger.

For å sortere en samling av strenger alfabetisk, har Java en flott metode kaltCollections.sort(collection);

Denne statiske metoden sorterer den beståtte samlingen. Og i prosessen med sortering utfører den parvise sammenligninger av elementene for å forstå om elementer bør byttes.

Under sortering utføres disse sammenligningene ved hjelp av compareTo()-metoden, som alle standardklassene har: Integer, String, ...

CompareTo()-metoden til Integer-klassen sammenligner verdiene til to tall, mens compareTo()-metoden til String-klassen ser på den alfabetiske rekkefølgen av strenger.

Så en samling av tall vil bli sortert i stigende rekkefølge, mens en samling av strenger vil bli sortert alfabetisk.

Alternative sorteringsalgoritmer

Men hva om vi vil sortere strenger ikke alfabetisk, men etter lengden? Og hva om vi ønsker å sortere tall i synkende rekkefølge? Hva gjør du i dette tilfellet?

For å håndtere slike situasjoner Collectionshar klassen en annen sort()metode som har to parametere:

Collections.sort(collection, comparator);

Hvor komparator er et spesielt objekt som vet hvordan man sammenligner objekter i en samling under en sorteringsoperasjon . Begrepet komparator kommer fra det engelske ordet comparator , som igjen stammer fra compare , som betyr «å sammenligne».

Så hva er dette spesielle objektet?

Comparatorgrensesnitt

Vel, det hele er veldig enkelt. Typen av sort()metodens andre parameter erComparator<T>

Hvor T er en typeparameter som indikerer typen av elementene i samlingen , og Comparatorer et grensesnitt som har en enkelt metodeint compare(T obj1, T obj2);

Med andre ord er et komparatorobjekt et hvilket som helst objekt av en hvilken som helst klasse som implementerer Comparator-grensesnittet. Komparator-grensesnittet ser veldig enkelt ut:

public interface Comparator<Type>
{
   public int compare(Type obj1, Type obj2);
}
Kode for Comparator-grensesnittet

Metoden compare()sammenligner de to argumentene som sendes til den.

Hvis metoden returnerer et negativt tall, betyr det obj1 < obj2. Hvis metoden returnerer et positivt tall, betyr det obj1 > obj2. Hvis metoden returnerer 0, betyr det obj1 == obj2.

Her er et eksempel på et komparatorobjekt som sammenligner strenger etter deres lengde:

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

For å sammenligne strenglengder, trekker du bare en lengde fra den andre.

Den komplette koden for et program som sorterer strenger etter lengde vil se slik ut:

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();
   }
}
Sortering av strenger etter lengde


3. Syntaktisk sukker

Hva tror du, kan denne koden skrives mer kompakt? I utgangspunktet er det bare én linje som inneholder nyttig informasjon — obj1.length() - obj2.length();.

Men kode kan ikke eksistere utenfor en metode, så vi måtte legge til en compare()metode, og for å lagre metoden måtte vi legge til en ny klasse — StringLengthComparator. Og vi må også spesifisere typene av variablene... Alt ser ut til å være riktig.

Men det finnes måter å gjøre denne koden kortere på. Vi har litt syntaktisk sukker til deg. To scoops!

Anonym indre klasse

Du kan skrive komparatorkoden rett inne i main()metoden, og kompilatoren vil gjøre resten. Eksempel:

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);
    }
}
Sorter strenger etter lengde

Du kan lage et objekt som implementerer Comparatorgrensesnittet uten eksplisitt å lage en klasse! Kompilatoren vil opprette den automatisk og gi den et midlertidig navn. La oss sammenligne:

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

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

Den samme fargen brukes for å indikere identiske kodeblokker i de to forskjellige tilfellene. Forskjellene er ganske små i praksis.

Når kompilatoren møter den første kodeblokken, genererer den ganske enkelt en tilsvarende andre kodeblokk og gir klassen et tilfeldig navn.


4. Lambda-uttrykk i Java

La oss si at du bestemmer deg for å bruke en anonym indre klasse i koden din. I dette tilfellet vil du ha en kodeblokk som dette:

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

Her kombinerer vi deklarasjonen av en variabel med opprettelsen av en anonym klasse. Men det er en måte å gjøre denne koden kortere på. For eksempel slik:

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

Semikolonet er nødvendig fordi her har vi ikke bare en implisitt klasseerklæring, men også opprettelsen av en variabel.

Notasjon som dette kalles et lambda-uttrykk.

Hvis kompilatoren møter notasjon som dette i koden din, genererer den ganske enkelt den detaljerte versjonen av koden (med en anonym indre klasse).

Merk at når vi skrev lambda-uttrykket, utelot vi ikke bare navnet på klassen , men også navnet på metoden.Comparator<String>int compare()

Kompileringen vil ikke ha noe problem med å bestemme metoden , fordi et lambda-uttrykk kun kan skrives for grensesnitt som har en enkelt metode . Forresten, det er en måte å komme seg rundt denne regelen på, men du vil lære om det når du begynner å studere OOP i større dybde (vi snakker om standardmetoder).

La oss se på den detaljerte versjonen av koden igjen, men vi gråter ut delen som kan utelates når du skriver et lambda-uttrykk:

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

Det ser ut til at ingenting viktig ble utelatt. Faktisk, hvis Comparatorgrensesnittet bare har den ene compare()metoden, kan kompilatoren fullstendig gjenopprette den nedtonede koden fra den gjenværende koden.

Sortering

Nå kan vi forresten skrive sorteringskoden slik:

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

Eller til og med slik:

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

Vi erstattet ganske enkelt comparatorvariabelen umiddelbart med verdien som ble tildelt variabelen comparator.

Skriv slutning

Men det er ikke alt. Koden i disse eksemplene kan skrives enda mer kompakt. Først kan kompilatoren selv bestemme at variablene obj1og obj2er Strings. Og for det andre kan setningen med krøllete klammer og retur også utelates hvis du bare har en enkelt kommando i metodekoden.

Den forkortede versjonen vil være slik:

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

Collections.sort(list, comparator);

Og hvis comparatorvi i stedet for å bruke variabelen umiddelbart bruker verdien, får vi følgende versjon:

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

Vel, hva synes du om det? Bare én kodelinje uten overflødig informasjon – bare variabler og kode. Det er ingen måte å gjøre det kortere! Eller finnes det?



5. Hvordan det fungerer

Faktisk kan koden skrives enda mer kompakt. Men mer om det senere.

Du kan skrive et lambda-uttrykk der du vil bruke en grensesnitttype med en enkelt metode.

For eksempel, i koden kan du skrive et lambda-uttrykk fordi metodens signatur er slik:Collections.sort(list, (obj1, obj2) -> obj1.length() - obj2.length());sort()

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

Da vi sendte ArrayList<String>samlingen som det første argumentet til sorteringsmetoden, var kompilatoren i stand til å fastslå at typen av det andre argumentet er . Og fra dette konkluderte den med at dette grensesnittet har en enkelt metode. Alt annet er en teknikalitet.Comparator<String>int compare(String obj1, String obj2)