Hoe werken lambda-uitdrukkingen en wat gebeurt er 'onder de motorkap'?
sobes.tech AI
Antwoord van AI
Lambdafuncties in Java werken via de volgende mechanismen:
-
Functionele interfaces: De lambda-uitdrukking moet worden toegewezen aan een variabele, worden geretourneerd uit een methode of worden doorgegeven aan een methode waarvan het type een functioneel interface is. Een functioneel interface is een interface met precies één abstracte methode (SAM - Single Abstract Method). De annotatie
@FunctionalInterfaceis optioneel, maar wordt aanbevolen voor controle door de compiler. -
invokeDynamic (invokeDynamic): In plaats van een anonieme interne klasse te genereren in de bytecode tijdens compilatie, gebruikt Java de instructie
invokeDynamic. Deze instructie is toegevoegd in Java 7 om dynamisch getypeerde talen op de JVM te ondersteunen, en in Java 8 wordt het gebruikt voor het implementeren van lambda-uitdrukkingen en methodereferenties.invokeDynamicstelt het resolutieproces van de methodeaanroep uit tot runtime.
Wat er 'onder de motorkap' gebeurt:
Tijdens de compilatiefase:
- De compiler analyseert de lambda-uitdrukking.
- Het bepaalt het type van het functionele interface waarnaar wordt verwezen.
- Er wordt een speciale synthetische methode (vaak met het prefix
lambda$) gegenereerd in dezelfde klasse die de lambda-logica bevat. Deze methode heeft dezelfde handtekening als de abstracte methode van het functionele interface. - Op de plaats waar de lambda wordt gebruikt, genereert de compiler de instructie
invokeDynamic. Deze bevat een verwijzing naar bootstrap method (LambdaMetafactory.metafactory).
Tijdens de uitvoering (runtime):
- Wanneer de JVM de instructie
invokeDynamictegenkomt, roept deze voor het eerst de bootstrap method (LambdaMetafactory.metafactory). - De bootstrap method genereert dynamisch in het geheugen een call site.
- De call site creëert en retourneert een instantie van een klasse die het functionele interface implementeert. Deze instantie kan zijn:
- On-the-fly gemaakt (bijvoorbeeld met behulp van een gegenereerde
Constant CallSite). - Gecached voor toekomstig gebruik (voor stateless lambdas).
- Gepromoot als een verwijzing naar een reeds bestaande instantie van het functionele interface (bijvoorbeeld voor stateless lambdas die
Serializableimplementeren).
- On-the-fly gemaakt (bijvoorbeeld met behulp van een gegenereerde
- Deze gegenereerde klasse delegeert de aanroep van de abstracte methode van het functionele interface naar de gegenereerde synthetische methode (
lambda$). - Na de eerste aanroep van de bootstrap method onthoudt de call site welk object moet worden geretourneerd, en latere
invokeDynamic-aanroepen voor dezelfde lambda kunnen dit object snel verkrijgen of rechtstreeks aanroepen zonder de bootstrap method opnieuw te gebruiken.
Op deze manier creëren lambdas niet altijd een nieuwe klasse bij elke instantie. Voor lambdas die geen variabelen uit de externe context vastleggen (stateless lambdas), kan de implementatie van het functionele interface een singleton of een in cache opgeslagen instantie zijn die wordt hergebruikt. Lambdas die variabelen vastleggen (stateful lambdas) vereisen het maken van een nieuwe instantie van een klasse die de vastgelegde variabelen opslaat.
Het gebruik van invokeDynamic stelt de JIT-compiler in staat om de uitvoering van methodes aanroepen van functionele interfaces efficiënter te optimaliseren dan het traditionele model van anonieme klassen, omdat de JIT meer geïnformeerde beslissingen kan nemen tijdens runtime.
// Voorbeeld van een functioneel interface
@FunctionalInterface
interface MyFunction {
String apply(String input);
}
public class LambdaExample {
public static void main(String[] args) {
// Gebruik van lambda-uitdrukking
MyFunction func = s -> "Processed: " + s;
String result = func.apply("Test");
System.out.println(result);
}
}