1. Interfaces

Om te begrijpen wat lambda-functies zijn, moet u eerst begrijpen wat interfaces zijn. Laten we dus de belangrijkste punten in herinnering brengen.

Een interface is een variatie op het concept van een klasse. Een sterk ingekorte klasse, laten we zeggen. In tegenstelling tot een klasse kan een interface geen eigen variabelen hebben (behalve statische). U kunt ook geen objecten maken waarvan het type een interface is:

  • U kunt geen variabelen van de klasse declareren
  • U kunt geen objecten maken

Voorbeeld:

interface Runnable
{
   void run();
}
Voorbeeld van een standaard interface

Een interface gebruiken

Dus waarom is een interface nodig? Interfaces worden alleen samen met overerving gebruikt. Dezelfde interface kan worden geërfd door verschillende klassen, of zoals ook wordt gezegd: klassen implementeren de interface .

Als een klasse een interface implementeert, moet deze de methoden implementeren die door de interface zijn gedeclareerd, maar niet zijn geïmplementeerd. Voorbeeld:

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

De Timerklasse implementeert de Runnableinterface, dus het moet alle methoden in de Runnableinterface in zichzelf declareren en implementeren, dwz code schrijven in een methodebody. Hetzelfde geldt voor de Calendarklas.

Maar nu Runnablekunnen variabelen verwijzingen opslaan naar objecten die de Runnableinterface implementeren.

Voorbeeld:

Code Opmerking
Timer timer = new Timer();
timer.run();

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

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

De run()methode in de Timerklasse wordt aangeroepen


De run()methode in de Timerklasse wordt aangeroepen


De run()methode in de Calendarklasse wordt aangeroepen

U kunt altijd een objectreferentie toewijzen aan een variabele van elk type, zolang dat type een van de voorouderklassen van het object is. Voor de klassen Timeren zijn er twee van dergelijke typen: en .CalendarObjectRunnable

Als u een objectreferentie toewijst aan een Objectvariabele, kunt u alleen de methoden aanroepen die in de Objectklasse zijn gedeclareerd. En als u een objectreferentie toewijst aan een Runnablevariabele, kunt u de methoden van het type aanroepen Runnable.

Voorbeeld 2:

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

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

Deze code zal werken, omdat de Timeren Calendarobjecten uitvoeringsmethoden hebben die perfect werken. Bellen is dus geen probleem. Als we zojuist een methode run() aan beide klassen hadden toegevoegd, dan zouden we ze niet op zo'n eenvoudige manier kunnen aanroepen.

In principe wordt de Runnableinterface alleen gebruikt als plaats om de run-methode te plaatsen.



2. Sorteren

Laten we verder gaan met iets praktischer. Laten we bijvoorbeeld eens kijken naar het sorteren van strings.

Om een ​​verzameling strings alfabetisch te sorteren, heeft Java een geweldige methode genaamdCollections.sort(collection);

Deze statische methode sorteert de doorgegeven collectie. En tijdens het sorteren voert het paarsgewijze vergelijkingen uit van zijn elementen om te begrijpen of elementen moeten worden verwisseld.

Tijdens het sorteren worden deze vergelijkingen uitgevoerd met behulp van de compareTomethode () die alle standaardklassen hebben: Integer, String, ...

De methode CompareTo() van de klasse Integer vergelijkt de waarden van twee getallen, terwijl de methode CompareTo() van de klasse String kijkt naar de alfabetische volgorde van tekenreeksen.

Een verzameling getallen wordt dus in oplopende volgorde gesorteerd, terwijl een verzameling strings alfabetisch wordt gesorteerd.

Alternatieve sorteeralgoritmen

Maar wat als we strings niet alfabetisch willen sorteren, maar op lengte? En wat als we getallen in aflopende volgorde willen sorteren? Wat doe je in dit geval?

Om met dergelijke situaties om te gaan, Collectionsheeft de klasse een andere sort()methode die twee parameters heeft:

Collections.sort(collection, comparator);

Waar comparator een speciaal object is dat objecten in een verzameling weet te vergelijken tijdens een sorteerbewerking . De term comparator komt van het Engelse woord comparator , dat op zijn beurt is afgeleid van vergelijken , wat "vergelijken" betekent.

Dus wat is dit speciale object?

Comparatorkoppel

Nou, het is allemaal heel simpel. Het type van de sort()tweede parameter van de methode isComparator<T>

Waarbij T een typeparameter is die het type elementen in de collectie aangeeft , en Comparatoreen interface is die één methode heeftint compare(T obj1, T obj2);

Met andere woorden, een comparator-object is elk object van elke klasse dat de Comparator-interface implementeert. De Comparator-interface ziet er heel eenvoudig uit:

public interface Comparator<Type>
{
   public int compare(Type obj1, Type obj2);
}
Code voor de Comparator-interface

De compare()methode vergelijkt de twee argumenten die eraan worden doorgegeven.

Als de methode een negatief getal retourneert, betekent dat obj1 < obj2. Als de methode een positief getal retourneert, betekent dat obj1 > obj2. Als de methode 0 retourneert, betekent dat obj1 == obj2.

Hier is een voorbeeld van een comparator-object dat strings vergelijkt op basis van hun lengte:

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

Om stringlengtes te vergelijken, trekt u eenvoudig de ene lengte van de andere af.

De volledige code voor een programma dat strings op lengte sorteert, ziet er als volgt uit:

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();
   }
}
Strings sorteren op lengte


3. Syntactische suiker

Wat denk je, kan deze code compacter geschreven worden? In principe is er maar één regel die nuttige informatie bevat: obj1.length() - obj2.length();.

Maar code kan niet bestaan ​​buiten een methode, dus moesten we een compare()methode toevoegen, en om de methode op te slaan moesten we een nieuwe klasse toevoegen — StringLengthComparator. En we moeten ook de soorten variabelen specificeren... Alles lijkt te kloppen.

Maar er zijn manieren om deze code korter te maken. We hebben wat syntactische suiker voor je. Twee bolletjes!

Anonieme innerlijke klasse

U kunt de comparatorcode rechtstreeks in de main()methode schrijven en de compiler doet de rest. Voorbeeld:

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);
    }
}
Sorteer strings op lengte

U kunt een object maken dat de interface implementeert Comparatorzonder expliciet een klasse te maken! De compiler maakt het automatisch aan en geeft het een tijdelijke naam. Laten we vergelijken:

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

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

Dezelfde kleur wordt gebruikt om identieke codeblokken aan te geven in de twee verschillende gevallen. De verschillen zijn in de praktijk vrij klein.

Wanneer de compiler het eerste codeblok tegenkomt, genereert het eenvoudigweg een overeenkomstig tweede codeblok en geeft de klasse een willekeurige naam.


4. Lambda-expressies in Java

Stel dat u besluit een anonieme innerlijke klasse in uw code te gebruiken. In dit geval heb je een codeblok zoals dit:

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

Hier combineren we de declaratie van een variabele met het creëren van een anonieme klasse. Maar er is een manier om deze code korter te maken. Bijvoorbeeld als volgt:

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

De puntkomma is nodig omdat we hier niet alleen een impliciete klassedeclaratie hebben, maar ook de creatie van een variabele.

Een dergelijke notatie wordt een lambda-expressie genoemd.

Als de compiler een dergelijke notatie in uw code tegenkomt, genereert deze eenvoudig de uitgebreide versie van de code (met een anonieme innerlijke klasse).

Merk op dat we bij het schrijven van de lambda-expressie niet alleen de naam van de klasse hebben weggelaten, maar ook de naam van de methode.Comparator<String>int compare()

Het compileren zal geen probleem hebben om de methode te bepalen , omdat een lambda-expressie alleen kan worden geschreven voor interfaces die een enkele methode hebben . Er is trouwens een manier om deze regel te omzeilen, maar daar leer je meer over als je OOP dieper gaat bestuderen (we hebben het over standaardmethoden).

Laten we nog eens naar de uitgebreide versie van de code kijken, maar we zullen het deel grijs maken dat kan worden weggelaten bij het schrijven van een lambda-expressie:

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

Het lijkt erop dat er niets belangrijks is weggelaten. Als de Comparatorinterface slechts één compare()methode heeft, kan de compiler de grijs weergegeven code volledig herstellen van de resterende code.

Sorteren

Trouwens, nu kunnen we de sorteercode als volgt schrijven:

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

Of zelfs zo:

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

We hebben de variabele eenvoudigweg onmiddellijk vervangen comparatordoor de waarde die aan de comparatorvariabele was toegewezen.

Typ gevolgtrekking

Maar dat is niet alles. De code in deze voorbeelden kan nog compacter geschreven worden. Ten eerste kan de compiler zelf bepalen dat de obj1en obj2variabelen Strings. En ten tweede kunnen de accolades en het return-statement ook worden weggelaten als je maar één commando in de methodecode hebt.

De verkorte versie zou er als volgt uitzien:

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

Collections.sort(list, comparator);

En als we in plaats van de comparatorvariabele te gebruiken, onmiddellijk de waarde ervan gebruiken, krijgen we de volgende versie:

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

Nou, wat vind je daarvan? Slechts één regel code zonder overbodige informatie - alleen variabelen en code. Er is geen manier om het korter te maken! Of is er?



5. Hoe het werkt

Sterker nog, de code kan nog compacter geschreven worden. Maar daarover later meer.

U kunt een lambda-expressie schrijven waarbij u een interfacetype zou gebruiken met een enkele methode.

In de code kun je bijvoorbeeld een lambda-expressie schrijven omdat de handtekening van de methode als volgt is:Collections.sort(list, (obj1, obj2) -> obj1.length() - obj2.length());sort()

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

Toen we de verzameling als eerste argument aan de sorteermethode doorgaven ArrayList<String>, kon de compiler bepalen dat het type van het tweede argument . En hieruit concludeerde het dat deze interface een enkele methode heeft. Al het andere is technisch.Comparator<String>int compare(String obj1, String obj2)