Sobes.tech
Middle+

Ինչպես են աշխատում լամբդա արտահայտությունները և ինչ է տեղի ունենում 'ծածկույթի տակ'?

sobes.tech AI

Պատասխան AI-ից

Լամբդա արտահայտությունները Java-ում աշխատում են հետևյալ մեխանիզմների միջոցով:

  1. Ֆունկցիոնալ ինտերֆեյսներ: Լամբդա արտահայտությունը պետք է վերագրել փոփոխականին, վերադարձնել մեթոդից կամ փոխանցել մեթոդին, որի տիպը հանդիսանում է ֆունկցիոնալ ինտերֆեյս։ Ֆունկցիոնալ ինտերֆեյսը այն ինտերֆեյսն է, որի մեջ կա միայն մեկ աբստրակտ մեթոդ (SAM - Single Abstract Method): @FunctionalInterface անոտացիան ընտրովի է, բայց խորհուրդ է տրվում օգտագործել կոմպիլյատորով ստուգելու համար։

  2. Դինամիկ կանչի (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);
    }
}