Sobes.tech
Middle+

Jak fungují lambda výrazy a co se děje 'pod kapotou'?

sobes.tech AI

Odpověď od AI

Výrazy lambda v Javě fungují díky následujícím mechanismům:

  1. Funkční rozhraní: Lambda výraz musí být přiřazen proměnné, vrácen z metody nebo předán metodě, jejíž typ je funkční rozhraní. Funkční rozhraní je rozhraní s přesně jednou abstraktní metodou (SAM - Single Abstract Method). Anotace @FunctionalInterface je volitelná, ale doporučuje se pro kontrolu kompilátorem.

  2. Dynamické volání (invokeDynamic): Místo generování anonymní vnitřní třídy v bytecode při kompilaci, jak to dělaly starší verze anonymních tříd, Java používá instrukci invokeDynamic. Tato instrukce byla přidána v Java 7 na podporu dynamicky typovaných jazyků na JVM a v Java 8 se používá k implementaci výrazů lambda a odkazů na metody. invokeDynamic odkládá řešení volání metody na dobu běhu (runtime).

Co se děje "pod kapotou":

V fázi kompilace:

  • Kompilátor analyzuje lambda výraz.
  • Určí typ funkčního rozhraní, na které se lambda odkazuje.
  • Vytvoří speciální syntetickou metodu (často s prefixem lambda$) ve stejné třídě, která obsahuje logiku lambda výrazu. Tato metoda má stejnou signaturu jako abstraktní metoda funkčního rozhraní.
  • Na místě použití lambda výrazu kompilátor generuje instrukci invokeDynamic. Tato instrukce obsahuje odkaz na bootstrap method (LambdaMetafactory.metafactory).

V době běhu (runtime):

  • Když JVM narazí na instrukci invokeDynamic, poprvé zavolá bootstrap method (LambdaMetafactory.metafactory).
  • Bootstrap method (LambdaMetafactory.metafactory) dynamicky generuje v paměti call site.
  • Call site vytvoří a vrátí instanci třídy, která implementuje funkční rozhraní. Tato instance může být:
    • Vytvořena na místě (například pomocí generovaného Constant CallSite).
    • Uložena do cache pro pozdější použití (pro stateless lambda).
    • Představována jako odkaz na již existující instanci funkčního rozhraní (například pro stateless lambda implementující Serializable).
  • Tato generovaná třída deleguje volání abstraktní metody funkčního rozhraní na generovanou syntetickou metodu (lambda$).
  • Po prvním volání bootstrap method si call site zapamatuje, jaký objekt má vracet, a následná volání invokeDynamic pro stejnou lambda mohou rychle získat tento objekt nebo jej přímo volat bez opětovného volání bootstrap method.

Tím pádem lambda výrazy nevytvářejí vždy novou třídu při každé instanciaci. Pro lambda výrazy, které nezachytávají proměnné z vnějšího kontextu (stateless lambda), může být implementace funkčního rozhraní singleton nebo instancí, která je cacheována a znovu používána. Lambda výrazy, které zachytávají proměnné (stateful lambda), vyžadují vytvoření nové instance třídy, která bude uchovávat zachycené proměnné.

Použití invokeDynamic umožňuje JIT-kompilátoru optimalizovat provádění volání metod funkčního rozhraní efektivněji než tradiční model anonymních tříd, protože JIT může dělat informovanější rozhodnutí během běhu.

// Příklad funkčního rozhraní
@FunctionalInterface
interface MyFunction {
    String apply(String input);
}

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

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