1. Gränssnitt
För att förstå vad lambdafunktioner är måste du först förstå vad gränssnitt är. Så låt oss komma ihåg huvudpunkterna.
Ett gränssnitt är en variant av begreppet en klass. En kraftigt trunkerad klass, låt oss säga. Till skillnad från en klass kan ett gränssnitt inte ha sina egna variabler (förutom statiska). Du kan inte heller skapa objekt vars typ är ett gränssnitt:
- Du kan inte deklarera variabler för klassen
- Du kan inte skapa objekt
Exempel:
interface Runnable
{
void run();
}
Använda ett gränssnitt
Så varför behövs ett gränssnitt? Gränssnitt används endast tillsammans med arv. Samma gränssnitt kan ärvas av olika klasser, eller som det också sägs — klasser implementerar gränssnittet .
Om en klass implementerar ett gränssnitt måste den implementera de metoder som deklarerats av men inte implementerats av gränssnittet. Exempel:
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 Timer
implementerar Runnable
gränssnittet, så den måste inom sig själv deklarera alla metoder som finns i Runnable
gränssnittet och implementera dem, dvs skriva kod i en metodkropp. Detsamma gäller klassen Calendar
.
Men nu Runnable
kan variabler lagra referenser till objekt som implementerar gränssnittet Runnable
.
Exempel:
Koda | Notera |
---|---|
|
Metoden run() i Timer klassen kommer att kallas Metoden run() i Timer klassen kommer att kallas Metoden run() i Calendar klassen kommer att kallas |
Du kan alltid tilldela en objektreferens till en variabel av vilken typ som helst, så länge den typen är en av objektets förfaderklasser. För klasserna Timer
och Calendar
finns det två sådana typer: Object
och Runnable
.
Om du tilldelar en objektreferens till en Object
variabel kan du bara anropa metoderna som deklarerats i Object
klassen. Och om du tilldelar en objektreferens till en Runnable
variabel kan du anropa metoderna för Runnable
typen.
Exempel 2:
ArrayList<Runnable> list = new ArrayList<Runnable>();
list.add (new Timer());
list.add (new Calendar());
for (Runnable element: list)
element.run();
Den här koden kommer att fungera eftersom Timer
och Calendar
-objekten har kört metoder som fungerar utmärkt. Så att ringa dem är inget problem. Om vi bara hade lagt till en run()-metod till båda klasserna, skulle vi inte kunna anropa dem på ett så enkelt sätt.
I grund och botten Runnable
används gränssnittet bara som en plats för att sätta körmetoden.
2. Sortering
Låt oss gå vidare till något mer praktiskt. Låt oss till exempel titta på sorteringssträngar.
För att sortera en samling strängar i alfabetisk ordning har Java en utmärkt metod som heterCollections.sort(collection);
Denna statiska metod sorterar den passerade samlingen. Och under sorteringsprocessen utför den parvisa jämförelser av dess element för att förstå om element bör bytas ut.
Under sorteringen utförs dessa jämförelser med compareTo
metoden () som alla standardklasser har: , , ...Integer
String
Metoden compareTo() i klassen Integer jämför värdena för två tal, medan metoden compareTo() i klassen String tittar på den alfabetiska ordningen på strängar.
Så en samling siffror kommer att sorteras i stigande ordning, medan en samling strängar kommer att sorteras alfabetiskt.
Alternativa sorteringsalgoritmer
Men vad händer om vi vill sortera strängar inte i alfabetisk ordning, utan efter deras längd? Och vad händer om vi vill sortera siffror i fallande ordning? Vad gör du i det här fallet?
För att hantera sådana situationer Collections
har klassen en annan sort()
metod som har två parametrar:
Collections.sort(collection, comparator);
Där komparator är ett speciellt objekt som vet hur man jämför objekt i en samling under en sorteringsoperation . Termen comparator kommer från det engelska ordet comparator , som i sin tur härstammar från compare , som betyder "att jämföra".
Så vad är detta speciella föremål?
Comparator
gränssnitt
Tja, det hela är väldigt enkelt. Typen av sort()
metodens andra parameter ärComparator<T>
Där T är en typparameter som anger typen av elementen i samlingen och är Comparator
ett gränssnitt som har en enda metodint compare(T obj1, T obj2);
Med andra ord är ett komparatorobjekt vilket objekt av vilken klass som helst som implementerar komparatorgränssnittet. Jämförelsegränssnittet ser väldigt enkelt ut:
public interface Comparator<Type>
{
public int compare(Type obj1, Type obj2);
}
Metoden compare()
jämför de två argumenten som skickas till den.
Om metoden returnerar ett negativt tal betyder det obj1 < obj2
. Om metoden returnerar ett positivt tal betyder det obj1 > obj2
. Om metoden returnerar 0 betyder det obj1 == obj2
.
Här är ett exempel på ett komparatorobjekt som jämför strängar efter deras längd:
public class StringLengthComparator implements Comparator<String>
{
public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
}
För att jämföra stränglängder, subtrahera helt enkelt en längd från den andra.
Den fullständiga koden för ett program som sorterar strängar efter längd skulle se ut så här:
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. Syntaktisk socker
Vad tycker du, kan den här koden skrivas mer kompakt? I grund och botten finns det bara en rad som innehåller användbar information — obj1.length() - obj2.length();
.
Men kod kan inte existera utanför en metod, så vi var tvungna att lägga till en compare()
metod, och för att lagra metoden var vi tvungna att lägga till en ny klass — StringLengthComparator
. Och vi måste också specificera typerna av variablerna... Allt verkar vara korrekt.
Men det finns sätt att göra den här koden kortare. Vi har lite syntaktisk socker åt dig. Två skopor!
Anonym inre klass
Du kan skriva komparatorkoden direkt i main()
metoden, och kompilatorn gör resten. Exempel:
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);
}
}
Du kan skapa ett objekt som implementerar gränssnittet Comparator
utan att uttryckligen skapa en klass! Kompilatorn skapar den automatiskt och ger den ett tillfälligt namn. Låt oss jämföra:
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();
}
}
Samma färg används för att indikera identiska kodblock i de två olika fallen. Skillnaderna är ganska små i praktiken.
När kompilatorn stöter på det första kodblocket genererar den helt enkelt ett motsvarande andra kodblock och ger klassen ett slumpmässigt namn.
4. Lambda-uttryck i Java
Låt oss säga att du bestämmer dig för att använda en anonym inre klass i din kod. I det här fallet kommer du att ha ett kodblock så här:
Comparator<String> comparator = new Comparator<String>()
{
public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
};
Här kombinerar vi deklarationen av en variabel med skapandet av en anonym klass. Men det finns ett sätt att göra den här koden kortare. Till exempel, så här:
Comparator<String> comparator = (String obj1, String obj2) ->
{
return obj1.length() – obj2.length();
};
Semikolon behövs för här har vi inte bara en implicit klassdeklaration, utan också skapandet av en variabel.
Notation som denna kallas ett lambdauttryck.
Om kompilatorn stöter på notation som denna i din kod, genererar den helt enkelt den utförliga versionen av koden (med en anonym inre klass).
Observera att när vi skrev lambda-uttrycket utelämnade vi inte bara namnet på klassen, utan också namnet på metoden.Comparator<String>
int compare()
Kompileringen kommer inte att ha några problem med att bestämma metoden , eftersom ett lambda-uttryck endast kan skrivas för gränssnitt som har en enda metod . Förresten, det finns ett sätt att komma runt den här regeln, men det lär du dig om när du börjar studera OOP på djupet (vi pratar om standardmetoder).
Låt oss titta på den utförliga versionen av koden igen, men vi kommer att gråa ut den del som kan utelämnas när du skriver ett lambda-uttryck:
Comparator<String> comparator = new Comparator<String>()
{
public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
};
Det verkar som att inget viktigt har utelämnats. Faktum är att om Comparator
gränssnittet bara har en compare()
metod, kan kompilatorn helt återställa den nedtonade koden från den återstående koden.
Sortering
Förresten, nu kan vi skriva sorteringskoden så här:
Comparator<String> comparator = (String obj1, String obj2) ->
{
return obj1.length() – obj2.length();
};
Collections.sort(list, comparator);
Eller till och med så här:
Collections.sort(list, (String obj1, String obj2) ->
{
return obj1.length() – obj2.length();
}
);
Vi ersatte helt enkelt omedelbart comparator
variabeln med värdet som tilldelades variabeln comparator
.
Skriv slutledning
Men det är inte allt. Koden i dessa exempel kan skrivas ännu mer kompakt. Först kan kompilatorn själv bestämma att variablerna obj1
och obj2
är Strings
. Och för det andra kan de lockiga klammerparenteserna och return-satsen också utelämnas om du bara har ett enda kommando i metodkoden.
Den förkortade versionen skulle se ut så här:
Comparator<String> comparator = (obj1, obj2) ->
obj1.length() – obj2.length();
Collections.sort(list, comparator);
Och om vi istället för att använda comparator
variabeln omedelbart använder dess värde, får vi följande version:
Collections.sort(list, (obj1, obj2) -> obj1.length() — obj2.length() );
Tja, vad tycker du om det? Bara en rad kod utan överflödig information - bara variabler och kod. Det finns inget sätt att göra det kortare! Eller finns det?
5. Hur det fungerar
Faktum är att koden kan skrivas ännu mer kompakt. Men mer om det senare.
Du kan skriva ett lambda-uttryck där du skulle använda en gränssnittstyp med en enda metod.
Till exempel, i koden kan du skriva ett lambda-uttryck eftersom metodens signatur är så här:Collections.sort(list, (obj1, obj2) -> obj1.length() - obj2.length());
sort()
sort(Collection<T> colls, Comparator<T> comp)
När vi skickade ArrayList<String>
samlingen som det första argumentet till sorteringsmetoden kunde kompilatorn fastställa att typen av det andra argumentet är . Och från detta drog den slutsatsen att detta gränssnitt har en enda metod. Allt annat är en teknikalitet.Comparator<String>
int compare(String obj1, String obj2)
GO TO FULL VERSION