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();
}
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 Timer
klasse implementeert de Runnable
interface, dus het moet alle methoden in de Runnable
interface in zichzelf declareren en implementeren, dwz code schrijven in een methodebody. Hetzelfde geldt voor de Calendar
klas.
Maar nu Runnable
kunnen variabelen verwijzingen opslaan naar objecten die de Runnable
interface implementeren.
Voorbeeld:
Code | Opmerking |
---|---|
|
De run() methode in de Timer klasse wordt aangeroepen De run() methode in de Timer klasse wordt aangeroepen De run() methode in de Calendar klasse 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 Timer
en zijn er twee van dergelijke typen: en .Calendar
Object
Runnable
Als u een objectreferentie toewijst aan een Object
variabele, kunt u alleen de methoden aanroepen die in de Object
klasse zijn gedeclareerd. En als u een objectreferentie toewijst aan een Runnable
variabele, 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 Timer
en Calendar
objecten 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 Runnable
interface 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 compareTo
methode () 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, Collections
heeft 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?
Comparator
koppel
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 Comparator
een 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);
}
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();
}
}
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();
}
}
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);
}
}
U kunt een object maken dat de interface implementeert Comparator
zonder 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();
}
};
Comparator<String> comparator = new StringLengthComparator();
class StringLengthComparator implements Comparator<String>
{
public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
}
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();
}
};
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();
}
};
Het lijkt erop dat er niets belangrijks is weggelaten. Als de Comparator
interface 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 comparator
door de waarde die aan de comparator
variabele 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 obj1
en obj2
variabelen 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 comparator
variabele 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)
GO TO FULL VERSION