1. Interfészek
A lambda függvények megértéséhez először meg kell értenie, hogy mik azok az interfészek. Emlékezzünk tehát a főbb pontokra.
Az interfész az osztály fogalmának egy változata. Mondjuk egy erősen csonka osztály. Az osztályokkal ellentétben az interfésznek nem lehet saját változója (kivéve a statikusakat). Nem hozhat létre olyan objektumokat sem, amelyek típusa interfész:
- Nem deklarálhatja az osztály változóit
- Nem hozhat létre objektumokat
Példa:
interface Runnable
{
void run();
}
Interfész használata
Akkor miért van szükség interfészre? Az interfészek csak az örökléssel együtt használhatók. Ugyanazt az interfészt örökölhetik különböző osztályok, vagy ahogy mondják – az osztályok valósítják meg az interfészt .
Ha egy osztály interfészt valósít meg, akkor meg kell valósítania az interfész által deklarált, de általa nem megvalósított metódusokat. Példa:
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());
}
}
Az Timer
osztály implementálja az Runnable
interfészt, tehát magában kell deklarálnia az összes interfészben található metódust, Runnable
és implementálnia kell azokat, azaz kódot kell írnia egy metódustörzsbe. Ugyanez vonatkozik az Calendar
osztályra is.
Most azonban Runnable
a változók hivatkozásokat tárolhatnak az Runnable
interfészt megvalósító objektumokra.
Példa:
Kód | jegyzet |
---|---|
|
run() Az osztályban lévő metódus neve Timer lesz Az osztályban run() lévő metódus Timer neve lesz Az osztályban run() lévő metódus meg Calendar lesz hívva |
Bármilyen típusú változóhoz mindig hozzárendelhet objektumhivatkozást, feltéve, hogy az adott típus az objektum egyik ősosztálya. A Timer
és Calendar
osztályok esetében két ilyen típus létezik: Object
és Runnable
.
Ha objektumhivatkozást rendelünk egy változóhoz Object
, akkor csak az osztályban deklarált metódusokat hívhatjuk meg Object
. És ha objektumhivatkozást rendelünk egy változóhoz Runnable
, akkor meghívhatjuk a Runnable
típus metódusait.
2. példa:
ArrayList<Runnable> list = new ArrayList<Runnable>();
list.add (new Timer());
list.add (new Calendar());
for (Runnable element: list)
element.run();
Ez a kód működni fog, mert a Timer
és Calendar
objektumok futtatott metódusai tökéletesen működnek. Tehát nem probléma felhívni őket. Ha csak egy run() metódust adtunk volna mindkét osztályhoz, akkor nem tudnánk ilyen egyszerű módon meghívni őket.
Alapvetően az Runnable
interfész csak a run metódus elhelyezésére szolgál.
2. Rendezés
Térjünk át valami praktikusabbra. Nézzük például a karakterláncok rendezését.
A karakterláncok gyűjteményének ábécé szerinti rendezéséhez a Java egy nagyszerű módszerrel rendelkezikCollections.sort(collection);
Ez a statikus módszer rendezi az átadott gyűjteményt. A rendezés során pedig páronként összehasonlítja elemeit, hogy megértse, kell-e az elemeket felcserélni.
A rendezés során ezeket az összehasonlításokat a () metódussal hajtjuk végre compareTo
, amely minden szabványos osztály rendelkezik: Integer
, String
, ...
Az Integer osztály összehasonlítása() metódusa két szám értékét hasonlítja össze, míg a String osztály összehasonlítása() metódusa a karakterláncok ábécé sorrendjét nézi.
Így a számok gyűjteménye növekvő sorrendben, míg a karakterláncok gyűjteménye ábécé szerint lesz rendezve.
Alternatív rendezési algoritmusok
De mi van akkor, ha a karakterláncokat nem ábécé szerint, hanem hosszuk szerint szeretnénk rendezni? És mi van akkor, ha csökkenő sorrendbe akarjuk rendezni a számokat? Mit tesz ebben az esetben?
Az ilyen helyzetek kezelésére az Collections
osztálynak van egy másik sort()
metódusa, amely két paraméterrel rendelkezik:
Collections.sort(collection, comparator);
Ahol a komparátor egy speciális objektum, amely tudja, hogyan kell összehasonlítani a gyűjteményben lévő objektumokat a rendezési művelet során . A komparátor kifejezés az angol comparator szóból származik , amely viszont az összehasonlítás szóból származik , ami "összehasonlítani".
Tehát mi ez a különleges tárgy?
Comparator
felület
Nos, minden nagyon egyszerű. sort()
A metódus második paraméterének típusa aComparator<T>
Ahol T egy típusparaméter, amely a gyűjtemény elemeinek típusát jelzi , és Comparator
egy olyan interfész, amelynek egyetlen metódusa vanint compare(T obj1, T obj2);
Más szavakkal, a komparátor objektum bármely osztály bármely objektuma, amely megvalósítja a Comparator interfészt. A Comparator felülete nagyon egyszerűnek tűnik:
public interface Comparator<Type>
{
public int compare(Type obj1, Type obj2);
}
A compare()
módszer összehasonlítja a neki átadott két argumentumot.
Ha a metódus negatív számot ad vissza, az azt jelenti obj1 < obj2
. Ha a metódus pozitív számot ad vissza, az azt jelenti obj1 > obj2
. Ha a metódus 0-t ad vissza, az azt jelenti obj1 == obj2
.
Íme egy példa egy összehasonlító objektumra, amely a karakterláncokat hosszuk alapján hasonlítja össze:
public class StringLengthComparator implements Comparator<String>
{
public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
}
A karakterlánchosszak összehasonlításához egyszerűen vonja ki az egyik hosszt a másikból.
A karakterláncokat hossz szerint rendező program teljes kódja így néz ki:
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. Szintaktikus cukor
Mit gondolsz, lehet ezt a kódot tömörebben írni? Alapvetően egyetlen sor van, amely hasznos információkat tartalmaz — obj1.length() - obj2.length();
.
A kód azonban nem létezhet metóduson kívül, ezért hozzá kellett adnunk egy compare()
metódust, és a metódus tárolásához egy új osztályt kellett hozzáadnunk - StringLengthComparator
. És meg kell adnunk a változók típusait is... Úgy tűnik, minden rendben van.
De van mód ennek a kódnak a rövidítésére. Van egy kis szintaktikus cukor a számodra. Két gombóc!
Névtelen belső osztály
Az összehasonlító kódot közvetlenül a metódusba írhatja main()
, és a fordító elvégzi a többit. Példa:
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);
}
}
Létrehozhat olyan objektumot, amely megvalósítja a Comparator
felületet anélkül, hogy kifejezetten osztályt hozna létre! A fordító automatikusan létrehozza, és ideiglenes nevet ad neki. Hasonlítsuk össze:
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();
}
}
Ugyanazt a színt használják az azonos kódblokkok jelzésére a két különböző esetben. A gyakorlatban meglehetősen kicsik a különbségek.
Amikor a fordító az első kódblokkkal találkozik, egyszerűen generál egy megfelelő második kódblokkot, és véletlenszerű nevet ad az osztálynak.
4. Lambda kifejezések Java nyelven
Tegyük fel, hogy úgy dönt, hogy egy névtelen belső osztályt használ a kódjában. Ebben az esetben a következőhöz hasonló kódblokkja lesz:
Comparator<String> comparator = new Comparator<String>()
{
public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
};
Itt kombináljuk egy változó deklarációját egy névtelen osztály létrehozásával. De van mód ennek a kódnak a rövidítésére. Például így:
Comparator<String> comparator = (String obj1, String obj2) ->
{
return obj1.length() – obj2.length();
};
A pontosvesszőre azért van szükség, mert itt nemcsak implicit osztálydeklarációról van szó, hanem változó létrehozásáról is.
Az ilyen jelöléseket lambda kifejezésnek nevezzük .
Ha a fordító ilyen jelöléssel találkozik a kódban, akkor egyszerűen előállítja a kód bőbeszédű változatát (anonim belső osztállyal).
Figyeljük meg, hogy a lambda kifejezés írásakor nem csak az osztály nevét hagytuk ki , hanem a metódus nevét is .Comparator<String>
int compare()
A fordítás során nem okoz gondot a metódus meghatározása , mert lambda kifejezés csak olyan interfészekre írható , amelyek egyetlen metódussal rendelkeznek . Egyébként van mód ennek a szabálynak a megkerülésére, de ezt akkor fogod megtudni, amikor elkezded alaposabban tanulmányozni az OOP-t (alapértelmezett módszerekről beszélünk).
Nézzük meg még egyszer a kód bőbeszédű változatát, de kiszürkítjük a lambda kifejezés írásakor elhagyható részt:
Comparator<String> comparator = new Comparator<String>()
{
public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
};
Úgy tűnik, semmi fontosat nem hagytak ki. Valójában, ha az Comparator
interfész csak az egyetlen compare()
metódussal rendelkezik, a fordító teljesen vissza tudja állítani a kiszürkült kódot a fennmaradó kódból.
Válogatás
A rendezési kódot egyébként most így írhatjuk:
Comparator<String> comparator = (String obj1, String obj2) ->
{
return obj1.length() – obj2.length();
};
Collections.sort(list, comparator);
Vagy akár így:
Collections.sort(list, (String obj1, String obj2) ->
{
return obj1.length() – obj2.length();
}
);
Egyszerűen azonnal lecseréltük a comparator
változót a változóhoz rendelt értékre comparator
.
Típuskövetkeztetés
De ez még nem minden. A példákban szereplő kód még tömörebben írható. Először is, a fordító saját maga határozza meg, hogy a obj1
és obj2
a változók Strings
. Másodszor, a kapcsos kapcsos zárójelek és a return utasítás szintén elhagyható, ha csak egyetlen parancs van a metóduskódban.
A rövidített változat így nézne ki:
Comparator<String> comparator = (obj1, obj2) ->
obj1.length() – obj2.length();
Collections.sort(list, comparator);
És ha a változó használata helyett comparator
azonnal az értékét használjuk, akkor a következő verziót kapjuk:
Collections.sort(list, (obj1, obj2) -> obj1.length() — obj2.length() );
Nos, mit gondolsz erről? Csak egy sor kód felesleges információk nélkül – csak változók és kód. Nincs mód arra, hogy rövidebb legyen! Vagy van?
5. Hogyan működik
Valójában a kód még tömörebben is írható. De erről majd később.
Írhat egy lambda kifejezést, ahol egyetlen metódussal egy interfésztípust használna .
Például a kódban írhat egy lambda kifejezést, mert a metódus aláírása a következő:Collections.sort(list, (obj1, obj2) -> obj1.length() - obj2.length());
sort()
sort(Collection<T> colls, Comparator<T> comp)
Amikor a gyűjteményt első argumentumként átadtuk ArrayList<String>
a rendezési metódusnak, a fordító meg tudta állapítani, hogy a második argumentum típusa . Ebből pedig arra a következtetésre jutott, hogy ennek a felületnek egyetlen módszere van. Minden más technikai kérdés.Comparator<String>
int compare(String obj1, String obj2)
GO TO FULL VERSION