Начиная с Kotlin 2.0 лямбды перестали напрямую компилироваться в анонимные классы:
val twice = { x: Int -> x * 2 }
// тело лямбды становиться статическим методом
private static final int main$lambda$0(int x) {
return x * 2;
}
// для получения реализации одного из интерфейсов FunctionX используется специальная JVM инструкция, куда передается необходимый интерфейс (Function1) и ссылка на Bootstrap метод
invokedynamic invoke:()Lkotlin/jvm/functions/Function1;Инструкция
invokedynamic устроена так что не привязана к конкретной реализации и поэтому нуждается в Bootstrap методе, который должен отдать нужную реализацию:// В Bootstrap метод передается интерфейс FunctionX, сигнатура его метода invoke и ссылка на метод, реализующий этот интерфейс (статический метод который является телом лямбды)
CallSite site = LambdaMetafactory.metafactory(
lookup,
"invoke",
methodType(Function1.class),
methodType(Object.class, Object.class),
methodHandle(MainKt::main$lambda$0),
methodType(Integer.class, Integer.class)
);
// CallSite это контейнер, содержащий MethodHandle который либо является готовым объектом лямбды либо конструктором который можно использовать для ее создания. CallSite нужен для того чтобы один и тот же вызов invokedynamic не делал снова вызов Bootstrap метода.
repeat(1000) {
// тут не будет тысяча аллокаций, так как CallSite после первого вызова invokedynamic уже связан с этим местом вызова и содержит уже готовый объект лямбды.
val f = { x: Int -> x * 2 }
use(f)
}
Функция
LambdaMetafactory.metafactory() под капотом собирает класс, реализующий интерфейс лямбды через ClassFile API, такие классы нельзя найти через Class.forName(), также для них не создаются отдельные .class файлы, что для лямбд является неплохой такой оптимизацией.В итоге цепочка следующая:
1) Генерируется static метод с телом лямбды method0.
2) Вызывается invokedynamic куда передается нужный интерфейс FunctionX, ссылка на Bootstrap метод и при необходимости захваченные переменные.
3) Bootstrap метод через ClassFile API собирает класс, реализующий нужный FunctionX интерфейс, а в тело кладет ссылку на статический метод method0.
4) Создается обьект лямбды либо ссылка на его конструктор если есть захваченные переменные, все это оборачивается в CallSite, который нужен чтобы связать invokedynamic c определенным местом вызова и не вызывать повторно Bootstrap метод.
5) Получаем наконец объект лямбды, если был захват переменных invokedynamic сам передаст их значения в конструктор лямбды.
