Ինչպես են աշխատում լամբդա արտահայտությունները և ինչ է տեղի ունենում 'ծածկույթի տակ'?
sobes.tech AI
Պատասխան AI-ից
Լամբդա արտահայտությունները Java-ում աշխատում են հետևյալ մեխանիզմների միջոցով:
-
Ֆունկցիոնալ ինտերֆեյսներ: Լամբդա արտահայտությունը պետք է վերագրել փոփոխականին, վերադարձնել մեթոդից կամ փոխանցել մեթոդին, որի տիպը հանդիսանում է ֆունկցիոնալ ինտերֆեյս։ Ֆունկցիոնալ ինտերֆեյսը այն ինտերֆեյսն է, որի մեջ կա միայն մեկ աբստրակտ մեթոդ (SAM - Single Abstract Method):
@FunctionalInterfaceանոտացիան ընտրովի է, բայց խորհուրդ է տրվում օգտագործել կոմպիլյատորով ստուգելու համար։ -
Դինամիկ կանչի (invokeDynamic) մեխանիզմը: Փոխարենը անանուն ներքին դասի գեներացման байթքոդում, ինչպես դա կատարվում էր հին տարբերակների անանուն դասերի համար, Java-ն օգտագործում է
invokeDynamicհրահանգը։ Այս հրահանգը ավելացվել է Java 7-ում՝ աջակցելու համար դինամիկ տիպավորված լեզուներին JVM-ում, իսկ Java 8-ում այն օգտագործվում է լամբդա արտահայտությունների և մեթոդների հղումների իրականացման համար։invokeDynamic-ը հետաձգում է մեթոդի կանչի լուծումը մինչև գործարկումը (runtime):
Ինչ է տեղի ունենում "մատնային" մասում:
Տեղադրման փուլում:
- Կոմպիլյատորը վերլուծում է լամբդա արտահայտությունը։
- Հիմնվում է ֆունկցիոնալ ինտերֆեյսի տիպին, որը լամբդան վերագրվում է։
- Գեներացվում է հատուկ սինթետիկ մեթոդ (հաճախ
lambda$նախածանցով) նույն դասում, որը պարունակում է լամբդա արտահայտության տրամաբանությունը։ Այս մեթոդը ունի նույն սիգնատուրան, ինչ ֆունկցիոնալ ինտերֆեյսի աբստրակտ մեթոդը։ - Լամբդա արտահայտության օգտագործման վայրում կոմպիլյատորը գեներացնում է
invokeDynamicհրահանգ։ Այս հրահանգը պարունակում է հղում bootstrap method-ին (LambdaMetafactory.metafactory)։
Նպատակի փուլում (runtime):
- Երբ JVM-ը հանդիպում է
invokeDynamicհրահանգին, այն առաջին անգամ կանչում է bootstrap method-ին (LambdaMetafactory.metafactory)։ - Bootstrap method-ը դինամիկ կերպով գեներացնում է հիշողության մեջ call site-ը։
- Call site-ը ստեղծում և վերադարձնում է ֆունկցիոնալ ինտերֆեյսը իրականացնող դասի օրինակ։ Այս օրինակն կարող է լինել՝
- ստեղծված առանձին (օրինակ՝ սերած
Constant CallSite)-ով, - կեշավորված՝ հետագա օգտագործման համար (stateless լամբդաների համար),
- կամ ներկայացված որպես հղում արդեն գոյություն ունեցող ֆունկցիոնալ ինտերֆեյսի օրինակին (օրինակ՝
Serializable-ը իրականացնող stateless լամբդաների համար):
- ստեղծված առանձին (օրինակ՝ սերած
- Այս գեներացված դասը դելեգատավորում է աբստրակտ մեթոդի կանչը գեներացվող սինթետիկ մեթոդին (
lambda$)։ - Առաջին
bootstrapկանչից հետո, call site-ը հիշում է, թե ինչ օբյեկտ է պետք վերադարձնել, և հաջորդinvokeDynamicկանչերը կարող են արագորեն ստանալ այդ օբյեկտը կամ կանչել այն ուղղակիորեն առանց կրկնակիbootstrapկանչի։
Այսպիսով, լամբդաները երբեմն չեն ստեղծում նոր դաս յուրաքանչյուր ինստանցիայի համար։ Լամբդաները, որոնք չեն բռնում փոփոխականներ արտաքին կոնտեքստից (stateless), կարող են լինել սինգլթոն կամ կրկնվող ինստանցիա, որը կեշավորվում և կրկնակի օգտագործվում է։ Փոխանցվող փոփոխականներ բռնող լամբդաները պահանջում են նոր ինստանցիայի ստեղծում, որը կպահի բռնված փոփոխականները։
invokeDynamic-ի օգտագործումը թույլ է տալիս JIT-կոմպիլյատորին ավելի արդյունավետ օպտիմիզացնել ֆունկցիոնալ ինտերֆեյսի մեթոդների կանչերը, քան ավանդական անանուն դասերի մոդելը, քանի որ JIT-ը կարող է ավելի տեղեկացված որոշումներ կայացնել գործարկման ժամանակ։
// Ֆունկցիոնալ ինտերֆեյսի օրինակ
@FunctionalInterface
interface MyFunction {
String apply(String input);
}
public class LambdaExample {
public static void main(String[] args) {
// Լամբդա արտահայտության օգտագործում
MyFunction func = s -> "Processed: " + s;
String result = func.apply("Test");
System.out.println(result);
}
}