За счет чего работают Lambda-выражения, что происходит "под капотом" в Java?
Лямбда-выражения в Java реализуются через функциональные интерфейсы: компилятор выносит тело лямбды в скрытый метод и ставит для него вызов через инструкцию invokedynamic. На основе этого вызова во время выполнения через LambdaMetafactory создается объект, реализующий нужный функциональный интерфейс. Такой подход эффективнее анонимных классов и поддерживает захват (closure) внешних переменных.
Лямбда-выражения в Java появились, чтобы добавить в язык удобный функциональный стиль (передача поведения как значения) без явного написания анонимных классов. Внешне это похоже на "маленькую функцию", но формально каждая лямбда всегда приводится к экземпляру некоторого функционального интерфейса (интерфейс с единственным абстрактным методом, например Runnable, Callable, Function и т.д.).
Важно понимать, что современные JVM НЕ генерируют под каждую лямбду отдельный анонимный класс в .class-файле, как это было бы с анонимными внутренними классами. Вместо этого используется более легкий и быстрый механизм на базе байткод-инструкции invokedynamic и специальной метафабрики.
Основные шаги, которые происходят "под капотом":
() -> System.out.println("Hi") превращается во внутренний метод вида private static void lambda$main$0() (имя синтетическое).invokedynamic вместо анонимного класса. В месте, где вы пишете лямбду, компилятор вставляет не создание анонимного класса, а вызов invokedynamic. Эта инструкция говорит JVM: "здесь нужно создать объект, реализующий такой-то функциональный интерфейс, с использованием вот этого метода как реализации".LambdaMetafactory как "метафабрика" лямбд. Для invokedynamic указывается bootstrap-метод — обычно java.lang.invoke.LambdaMetafactory.metafactory. При первом вызове этого сайта JVM через метафабрику генерирует (или конфигурирует) класс/функциональный объект, который реализует нужный функциональный интерфейс, ссылаясь на синтетический метод с телом лямбды.final или эффективно final. Компилятор просто добавляет эти значения как параметры в синтетический метод или как поля в сгенерированном объекте, поэтому внутри он работает уже с захваченными копиями, а не с "живыми" изменяемыми переменными.С точки зрения программиста лямбда — это просто краткая форма записи реализации функционального интерфейса. Например:
Компилятор превращает это примерно в: "есть метод с телом System.out.println(...), а в месте создания r через invokedynamic и LambdaMetafactory создай объект, реализующий Runnable, чей метод run() вызывает этот синтетический метод". Логически это похоже на анонимный класс, но реализовано иначе и эффективнее.
Захват переменных из внешнего контекста (closure) выглядит так:
Переменная base не хранится как ссылка на изменяемую локальную переменную стека. Вместо этого её значение передаётся в синтетический метод/объект, и уже там используется как обычное поле/параметр. Поэтому лямбда не позволяет менять захваченную локальную переменную, а требует, чтобы она была final или эффективно final.
Итого: лямбда-выражения в Java — это синтаксический сахар поверх функциональных интерфейсов, реализованный через скрытый метод, вызов invokedynamic и LambdaMetafactory, что делает их и удобными, и производительными по сравнению с традиционными анонимными классами.
Отметьте свой прогресс