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();
}
Példa szabványos interfészre

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 Timerosztály implementálja az Runnableinterfé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 Calendarosztályra is.

Most azonban Runnablea változók hivatkozásokat tárolhatnak az Runnableinterfészt megvalósító objektumokra.

Példa:

Kód jegyzet
Timer timer = new Timer();
timer.run();

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

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

run()Az osztályban lévő metódus neve Timerlesz


Az osztályban run()lévő metódus Timerneve lesz


Az osztályban run()lévő metódus meg Calendarlesz 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 Calendarosztá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 Runnabletí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 Calendarobjektumok 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 Runnableinterfé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 Collectionsosztá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?

Comparatorfelü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 Comparatoregy 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 Comparator interfész kódja

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();
   }
}
StringLengthComparatorAz osztály kódja

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();
   }
}
A karakterláncok rendezése hossz szerint


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);
    }
}
A karakterláncok rendezése hosszúság szerint

Létrehozhat olyan objektumot, amely megvalósítja a Comparatorfelü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();
    }
};
Névtelen belső osztály
Comparator<String> comparator = new StringLengthComparator();

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

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();
    }
};
Névtelen belső osztály

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();
   }
};
Névtelen belső osztály

Úgy tűnik, semmi fontosat nem hagytak ki. Valójában, ha az Comparatorinterfé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 comparatorvá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 obj2a 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 comparatorazonnal 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)