Sobes.tech
Middle+

Hogyan működnek a lambda kifejezések és mi történik 'a motorháztető alatt'?

sobes.tech MI

Válasz az MI-től

A Java-ban a lambda kifejezések a következő mechanizmusokon keresztül működnek:

  1. Funkcionális interfészek: A lambda kifejezés egy változóhoz rendelendő, egy metódusból visszatérő vagy metódusnak átadott, amelynek típusa funkcionális interfész. A funkcionális interfész egy olyan interfész, amely pontosan egy absztrakt metódust tartalmaz (SAM - Single Abstract Method). Az @FunctionalInterface annotáció opcionális, de ajánlott a fordító általi ellenőrzéshez.

  2. Dinamikus hívás (invokeDynamic): Ahelyett, hogy a régi típusú anonim osztályokat bytecode-ban hozná létre a fordítás során, a Java az invokeDynamic utasítást használja. Ezt az utasítást a Java 7 vezette be a dinamikusan típusos nyelvek támogatására a JVM-en, és Java 8-ban a lambda kifejezések és metódusreferenciák megvalósítására szolgál. Az invokeDynamic késlelteti a metódushívás feloldását futásidőben.

Mi történik "a motorháztető alatt":

Fordítási szakaszban:

  • A fordító elemzi a lambda kifejezést.
  • Meghatározza a lambda által hivatkozott funkcionális interfész típusát.
  • Egy speciális szintetikus metódust (gyakran lambda$ prefixszel) generál ugyanabban a osztályban, amely tartalmazza a lambda logikáját. Ez a metódus ugyanazt a szignatúrát használja, mint a funkcionális interfész absztrakt metódusa.
  • A lambda használatának helyén a fordító az invokeDynamic utasítást generálja. Ez az utasítás tartalmaz egy hivatkozást a bootstrap method-ra (LambdaMetafactory.metafactory).

Futásidőben:

  • Amikor a JVM találkozik az invokeDynamic utasítással, először meghívja a bootstrap method-ot (LambdaMetafactory.metafactory).
  • A bootstrap method dinamikusan generál egy call site-ot a memóriában.
  • A call site létrehoz és visszaad egy példányt egy osztályból, amely megvalósítja a funkcionális interfészt. Ez a példány lehet:
    • A helyszínen létrehozott (pl. egy generált Constant CallSite segítségével).
    • Gyorsítótárazott a későbbi használatra (stateless lambda esetén).
    • Egy már létező példányra mutató referencia (pl. stateless lambda esetén, amely Serializable-t implementál).
  • Ez a generált osztály delegálja a funkcionális interfész absztrakt metódusának hívását a generált szintetikus metódusra (lambda$).
  • Az első hívás után a bootstrap method megjegyzi, melyik objektumot kell visszaadni, és a későbbi invokeDynamic hívások gyorsan elérhetik ezt az objektumot vagy közvetlenül hívhatják azt, anélkül, hogy újra meghívnák a bootstrap method-ot.

Így a lambda-k nem mindig hoznak létre új osztályt minden példányosításkor. Azok a lambda-k, amelyek nem fogják el a külső kontextusból származó változókat (stateless lambda-k), a funkcionális interfész implementációja lehet singleton vagy cache-elt példány, amely újrahasznosítható. A változókat fogó lambda-k (állapotos lambda-k) új példány létrehozását igénylik, amely tárolja a fogott változókat.

Az invokeDynamic használata lehetővé teszi a JIT-kompilátor számára, hogy hatékonyabban optimalizálja a funkcionális interfész metódushívásokat, mint a hagyományos anonim osztály modell, mivel a JIT tájékozottabb döntéseket hozhat futás közben.

// Funkcionális interfész példa
@FunctionalInterface
interface MyFunction {
    String apply(String input);
}

public class LambdaExample {
    public static void main(String[] args) {
        // Lambda kifejezés használata
        MyFunction func = s -> "Processed: " + s;

        String result = func.apply("Test");
        System.out.println(result);
    }
}