1. Grænseflader

For at forstå, hvad lambda-funktioner er, skal du først forstå, hvad grænseflader er. Så lad os huske hovedpunkterne.

En grænseflade er en variation af begrebet en klasse. En stærkt afkortet klasse, lad os sige. I modsætning til en klasse kan en grænseflade ikke have sine egne variabler (undtagen statiske). Du kan heller ikke oprette objekter, hvis type er en grænseflade:

  • Du kan ikke erklære variabler for klassen
  • Du kan ikke oprette objekter

Eksempel:

interface Runnable
{
   void run();
}
Eksempel på en standardgrænseflade

Brug af en grænseflade

Så hvorfor er der brug for en grænseflade? Grænseflader bruges kun sammen med arv. Den samme grænseflade kan nedarves af forskellige klasser, eller som det også er sagt - klasser implementerer grænsefladen .

Hvis en klasse implementerer en grænseflade, skal den implementere de metoder, der er erklæret af, men ikke implementeret af grænsefladen. 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 Runnablegrænsefladen, så den skal inde i sig selv deklarere alle de metoder, der er i grænsefladen Runnableog implementere dem, altså skrive kode i en metodekropp. Det samme gælder for Calendarklassen.

Men nu Runnablekan variabler gemme referencer til objekter, der implementerer grænsefladen Runnable.

Eksempel:

Kode Bemærk
Timer timer = new Timer();
timer.run();

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

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

Metoden run()i Timerklassen vil hedde


Metoden run()i Timerklassen vil hedde


Metoden run()i Calendarklassen vil hedde

Du kan altid tildele en objektreference til en variabel af enhver type, så længe den type er en af ​​objektets forfaderklasser. For klasserne Timerog Calendarer der to sådanne typer: Objectog Runnable.

Hvis du tildeler en objektreference til en Objectvariabel, kan du kun kalde de metoder, der er erklæret i Objectklassen. Og hvis du tildeler en objektreference til en Runnablevariabel, kan du kalde metoderne 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 kode vil fungere, fordi Timerog Calendarobjekterne har kørt metoder, der fungerer perfekt. Så det er ikke noget problem at ringe til dem. Hvis vi bare havde tilføjet en run()-metode til begge klasser, så ville vi ikke kunne kalde dem på så simpel en måde.

Grundlæggende Runnablebruges grænsefladen kun som et sted at placere kørselsmetoden.



2. Sortering

Lad os gå videre til noget mere praktisk. Lad os for eksempel se på sorteringsstrenge.

For at sortere en samling af strenge alfabetisk, har Java en fantastisk metode kaldetCollections.sort(collection);

Denne statiske metode sorterer den beståede samling. Og i processen med at sortere udfører den parvise sammenligninger af sine elementer for at forstå, om elementer skal byttes.

Under sorteringen udføres disse sammenligninger ved hjælp af compareTo() metoden, som alle standardklasserne har: Integer, String, ...

CompareTo()-metoden i Integer-klassen sammenligner værdierne af to tal, mens compareTo()-metoden i String-klassen ser på den alfabetiske rækkefølge af strenge.

Så en samling af tal vil blive sorteret i stigende rækkefølge, mens en samling af strenge vil blive sorteret alfabetisk.

Alternative sorteringsalgoritmer

Men hvad nu hvis vi vil sortere strenge ikke alfabetisk, men efter deres længde? Og hvad hvis vi vil sortere tal i faldende rækkefølge? Hvad gør du i dette tilfælde?

For at håndtere sådanne situationer Collectionshar klassen en anden sort()metode, der har to parametre:

Collections.sort(collection, comparator);

Hvor komparator er et specielt objekt, der ved, hvordan man sammenligner objekter i en samling under en sorteringsoperation . Begrebet komparator kommer af det engelske ord komparator , som igen stammer fra sammenligne , der betyder "at sammenligne".

Så hvad er dette specielle objekt?

Comparatorinterface

Nå, det hele er meget enkelt. Typen af sort()​​metodens anden parameter erComparator<T>

Hvor T er en typeparameter, der angiver typen af ​​elementerne i samlingen , og Comparatorer en grænseflade, der har en enkelt metodeint compare(T obj1, T obj2);

Med andre ord er et komparatorobjekt ethvert objekt af enhver klasse, der implementerer komparatorgrænsefladen. Komparator-grænsefladen ser meget enkel ud:

public interface Comparator<Type>
{
   public int compare(Type obj1, Type obj2);
}
Kode til Comparator-grænsefladen

Metoden compare()sammenligner de to argumenter, der sendes til den.

Hvis metoden returnerer et negativt tal, betyder det obj1 < obj2. Hvis metoden returnerer et positivt tal, betyder det obj1 > obj2. Hvis metoden returnerer 0, betyder det obj1 == obj2.

Her er et eksempel på et komparatorobjekt, der sammenligner strenge efter deres længde:

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

For at sammenligne strenglængder skal du blot trække den ene længde fra den anden.

Den komplette kode for et program, der sorterer strenge efter længde, ville se sådan ud:

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 af strenge efter længde


3. Syntaktisk sukker

Hvad synes du, kan denne kode skrives mere kompakt? Grundlæggende er der kun én linje, der indeholder nyttige oplysninger — obj1.length() - obj2.length();.

Men kode kan ikke eksistere uden for en metode, så vi var nødt til at tilføje en compare()metode, og for at gemme metoden var vi nødt til at tilføje en ny klasse — StringLengthComparator. Og vi skal også specificere variablernes typer... Alt ser ud til at være korrekt.

Men der er måder at gøre denne kode kortere på. Vi har noget syntaktisk sukker til dig. To scoops!

Anonym indre klasse

Du kan skrive komparatorkoden lige inde i main()metoden, og compileren klarer 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 strenge efter længde

Du kan oprette et objekt, der implementerer Comparatorgrænsefladen uden eksplicit at oprette en klasse! Compileren vil oprette den automatisk og give den et midlertidigt navn. Lad os 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 farve bruges til at angive identiske kodeblokke i de to forskellige tilfælde. Forskellene er ret små i praksis.

Når compileren støder på den første kodeblok, genererer den blot en tilsvarende anden kodeblok og giver klassen et tilfældigt navn.


4. Lambda-udtryk i Java

Lad os sige, at du beslutter dig for at bruge en anonym indre klasse i din kode. I dette tilfælde vil du have en kodeblok som denne:

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

Her kombinerer vi deklarationen af ​​en variabel med oprettelsen af ​​en anonym klasse. Men der er en måde at gøre denne kode kortere på. For eksempel sådan her:

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

Der er brug for semikolon, for her har vi ikke kun en implicit klasseerklæring, men også oprettelsen af ​​en variabel.

Notation som denne kaldes et lambda-udtryk.

Hvis compileren støder på notation som denne i din kode, genererer den simpelthen den verbose version af koden (med en anonym indre klasse).

Bemærk, at når vi skrev lambda-udtrykket, udelod vi ikke kun navnet på klassen , men også navnet på metoden.Comparator<String>int compare()

Kompileringen vil ikke have noget problem med at bestemme metoden , fordi et lambda-udtryk kun kan skrives for grænseflader, der har en enkelt metode . Forresten er der en måde at komme uden om denne regel, men det lærer du om, når du begynder at studere OOP i større dybde (vi taler om standardmetoder).

Lad os se på den detaljerede version af koden igen, men vi nedtoner den del, der kan udelades, når du skriver et lambda-udtryk:

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

Det ser ud til, at intet vigtigt blev udeladt. Faktisk, hvis Comparatorgrænsefladen kun har den ene compare()metode, kan compileren helt gendanne den nedtonede kode fra den resterende kode.

Sortering

Forresten, nu kan vi skrive sorteringskoden sådan her:

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

Eller endda sådan her:

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

Vi erstattede simpelthen comparatorvariablen med den værdi, der blev tildelt variablen comparator.

Skriv slutning

Men det er ikke alt. Koden i disse eksempler kan skrives endnu mere kompakt. For det første kan compileren selv bestemme, at variablerne obj1og obj2er Strings. Og for det andet kan de krøllede klammer og return-udsagn også udelades, hvis du kun har en enkelt kommando i metodekoden.

Den forkortede version ville se sådan ud:

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

Collections.sort(list, comparator);

Og hvis vi i stedet for at bruge comparatorvariablen straks bruger dens værdi, får vi følgende version:

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

Nå, hvad synes du om det? Kun én linje kode uden overflødig information - kun variabler og kode. Der er ingen måde at gøre det kortere! Eller er der?



5. Hvordan det virker

Faktisk kan koden skrives endnu mere kompakt. Men mere om det senere.

Du kan skrive et lambda-udtryk, hvor du vil bruge en grænsefladetype med en enkelt metode.

For eksempel kan du i koden skrive et lambda-udtryk, fordi metodens signatur er sådan: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 argument til sorteringsmetoden, var compileren i stand til at bestemme, at typen af ​​det andet argument er . Og ud fra dette konkluderede den, at denne grænseflade har en enkelt metode. Alt andet er en teknikalitet.Comparator<String>int compare(String obj1, String obj2)