Sobes.tech
Middle+

Ako fungujú lambda výrazy a čo sa deje 'pod kapotou'?

sobes.tech AI

Odpoveď od AI

Výrazy lambda v Jave fungujú vďaka nasledujúcim mechanizmom:

  1. Funkčné rozhrania: Lambda výraz musí byť priradený premennej, vrátený z metódy alebo odovzdaný do metódy, ktorej typ je funkčné rozhranie. Funkčné rozhranie je rozhranie s presne jednou abstraktnou metódou (SAM - Single Abstract Method). Anotácia @FunctionalInterface je voliteľná, ale odporúča sa na kontrolu kompilátorom.

  2. Dynamické volanie (invokeDynamic): Namiesto generovania anonymnej vnútorné triedy v bytecode počas kompilácie, ako to robili staré verzie anonymných tried, Java používa inštrukciu invokeDynamic. Táto inštrukcia bola pridaná v Java 7 na podporu dynamicky typovaných jazykov na JVM a v Java 8 sa používa na implementáciu lambda výrazov a odkazov na metódy. invokeDynamic odkladá vyriešenie volania metódy na runtime.

Čo sa deje "pod kapotou":

Na fáze kompilácie:

  • Kompilátor analyzuje lambda výraz.
  • Určí typ funkčného rozhrania, na ktoré sa lambda odkazuje.
  • Generuje špeciálnu syntetickú metódu (často s prefixom lambda$) v rovnakej triede, ktorá obsahuje logiku lambda výrazu. Táto metóda má rovnakú signatúru ako abstraktná metóda funkčného rozhrania.
  • Na mieste použitia lambda, kompilátor generuje inštrukciu invokeDynamic. Táto inštrukcia obsahuje odkaz na bootstrap method (LambdaMetafactory.metafactory).

Na fáze vykonávania (runtime):

  • Keď JVM narazí na inštrukciu invokeDynamic, prvýkrát zavolá bootstrap method (LambdaMetafactory.metafactory).
  • Bootstrap method (LambdaMetafactory.metafactory) dynamicky generuje v pamäti call site.
  • Call site vytvorí a vráti inštanciu triedy, ktorá implementuje funkčné rozhranie. Táto inštancia môže byť:
    • Vytvorená na mieste (napríklad pomocou generovaného Constant CallSite).
    • Cache-ovaná na neskoršie použitie (pre stateless lambda).
    • Predstavená ako odkaz na už existujúci príklad funkčného rozhrania (napríklad pre stateless lambda, ktorý implementuje Serializable).
  • Táto generovaná trieda deleguje volanie abstraktnej metódy funkčného rozhrania na generovanú syntetickú metódu (lambda$).
  • Po prvom volaní bootstrap method si call site zapamätá, aký objekt má vrátiť, a následné volania invokeDynamic pre tú istú lambda môžu rýchlo získať tento objekt alebo ho volať priamo bez opätovného volania bootstrap method.

Týmto spôsobom lambda výrazy ne vždy vytvárajú novú triedu pri každej inštanciácii. Pre lambda, ktoré nezachytávajú premenné z vonkajšieho kontextu (stateless lambda), môže byť implementácia funkčného rozhrania singleton alebo inštanciou, ktorá sa cacheuje a opakovane používa. Lambda, ktoré zachytávajú premenné (stateful lambda), vyžadujú vytvorenie novej inštancie triedy, ktorá bude ukladať zachytené premenné.

Použitie invokeDynamic umožňuje JIT-kompilátoru optimalizovať vykonávanie volaní metód funkčného rozhrania efektívnejšie ako tradičný model anonymných tried, pretože JIT môže robiť informovanejšie rozhodnutia počas vykonávania.

// Príklad funkčného rozhrania
@FunctionalInterface
interface MyFunction {
    String apply(String input);
}

public class LambdaExample {
    public static void main(String[] args) {
        // Použitie lambda výrazu
        MyFunction func = s -> "Processed: " + s;

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