Как работят ламбда изразите и какво се случва 'под капака'?
sobes.tech AI
Отговор от AI
Изразите lambda в Java работят чрез следните механизми:
-
Функционални интерфейси: Ламбда изразът трябва да бъде присвоен на променлива, върнат от метод или предаден на метод, чието тип е функционален интерфейс. Функционалният интерфейс е интерфейс с точно един абстрактен метод (SAM - Single Abstract Method). Анотацията
@FunctionalInterfaceе опционална, но се препоръчва за проверка от компилатора. -
Динамично извикване (invokeDynamic): Вместо да генерира анонимен вътрешен клас в байткода по време на компилация, както се правеше за старите версии на анонимните класове, Java използва инструкцията
invokeDynamic. Тази инструкция беше добавена в Java 7 за поддръжка на динамично типизирани езици на JVM, а в Java 8 се използва за реализиране на lambda изрази и препратки към методи.invokeDynamicотлага разрешаването на извикването на метода до изпълнение (runtime).
Какво се случва "под капака":
На етапа на компилация:
- Компилаторът анализира lambda израза.
- Определя типа на функционалния интерфейс, към който се отнася lambda.
- Генерира специален синтетичен метод (често с префикс
lambda$) в същия клас, който съдържа логиката на lambda израза. Този метод има същата сигнатура като абстрактния метод на функционалния интерфейс. - На мястото на използване на lambda, компилаторът генерира инструкция
invokeDynamic. Тази инструкция съдържа препратка към bootstrap method (LambdaMetafactory.metafactory).
На етапа на изпълнение (runtime):
- Когато JVM срещне инструкция
invokeDynamic, тя за първи път извиква bootstrap method (LambdaMetafactory.metafactory). - Bootstrap method (
LambdaMetafactory.metafactory) динамично генерира в паметта call site. - Call site-ът създава и връща екземпляр на класа, който реализира функционалния интерфейс. Този екземпляр може да бъде:
- Създаден на момента (например, с помощта на генериран
Constant CallSite). - Кеширан за последваща употреба (за stateless lambda).
- Представен като препратка към вече съществуващ екземпляр на функционалния интерфейс (например, за stateless lambda, реализиращ
Serializable).
- Създаден на момента (например, с помощта на генериран
- Този генериран клас делегира извикването на абстрактния метод на функционалния интерфейс към генерирания синтетичен метод (
lambda$). - След първия извик, bootstrap method запомня кой обект трябва да бъде върнат, и последващите извиквания
invokeDynamicза същата lambda могат бързо да получат този обект или да го извикат директно без повторно извикване на bootstrap method.
По този начин, lambda-ите не винаги създават нов клас за всяка инстанция. За lambda-ите, които не захващат променливи от външния контекст (stateless lambda), реализацията на функционалния интерфейс може да бъде сингълтон или инстанция, която се кешира и използва повторно. Lambda-ите, които захващат променливи (stateful lambda), изискват създаването на нов екземпляр на класа, който ще съхранява захванатите променливи.
Използването на invokeDynamic позволява на JIT-компилатора да оптимизира изпълнението на извикванията на методи на функционалния интерфейс по-ефективно, отколкото традиционният модел на анонимните класове, тъй като JIT може да взема по-информирани решения по време на изпълнение.
// Пример за функционален интерфейс
@FunctionalInterface
interface MyFunction {
String apply(String input);
}
public class LambdaExample {
public static void main(String[] args) {
// Използване на lambda израз
MyFunction func = s -> "Processed: " + s;
String result = func.apply("Test");
System.out.println(result);
}
}