Method Handles in Java

1. Inleiding

In dit artikel gaan we een belangrijke API verkennen die is geïntroduceerd in Java 7 en verbeterd in de volgende versies, de java.lang.invoke.MethodHandles.

We zullen in het bijzonder leren wat methodehandvatten zijn, hoe u ze kunt maken en hoe u ze kunt gebruiken.

2. Wat zijn methodes?

Komt tot de definitie, zoals vermeld in de API-documentatie:

Een methode-handle is een getypte, direct uitvoerbare verwijzing naar een onderliggende methode, constructor, veld of vergelijkbare bewerking op laag niveau, met optionele transformaties van argumenten of retourwaarden.

Op een eenvoudigere manier, methodhandvatten zijn een low-level mechanisme voor het vinden, aanpassen en aanroepen van methodes.

Methodehandvatten zijn onveranderlijk en hebben geen zichtbare toestand.

Voor het maken en gebruiken van een MethodHandle, Zijn 4 stappen vereist:

  • De zoekopdracht maken
  • Het type methode maken
  • De methode-handle vinden
  • Het aanroepen van de methode-handle

2.1. Methode behandelt versus reflectie

Om naast het bestaande te werken, zijn methodehandvatten geïntroduceerd java.lang.reflect API, omdat ze verschillende doeleinden dienen en verschillende kenmerken hebben.

Vanuit het oogpunt van prestaties is het Methode: Handvatten API kan veel sneller zijn dan de Reflection API, aangezien de toegangscontroles worden uitgevoerd tijdens het maken in plaats van tijdens de uitvoering. Dit verschil wordt nog groter als er een beveiligingsmanager aanwezig is, aangezien het opzoeken van leden en klassen aan aanvullende controles wordt onderworpen.

Aangezien prestatie echter niet de enige maatstaf voor geschiktheid voor een taak is, moeten we er ook rekening mee houden dat de Methode: Handvatten API is moeilijker te gebruiken vanwege het gebrek aan mechanismen zoals opsomming van ledenklassen, inspectie van toegankelijkheidsvlaggen en meer.

Toch is de Methode: Handvatten API biedt de mogelijkheid om methoden te curryen, de soorten parameters te wijzigen en hun volgorde te wijzigen.

Een duidelijke definitie en doelstellingen hebben van de Methode: Handvatten API, we kunnen er nu mee beginnen te werken, beginnend bij het opzoeken.

3. Creëren van het Opzoeken

Het eerste dat we moeten doen als we een methode-handle willen maken, is de lookup ophalen, het fabrieksobject dat verantwoordelijk is voor het maken van methodehandles voor methoden, constructors en velden, die zichtbaar zijn voor de lookup-klasse.

Door het Methode: Handvatten API is het mogelijk om het lookup-object te maken, met verschillende toegangsmodi.

Laten we de lookup maken die toegang geeft tot openbaar methoden:

MethodHandles.Lookup publicLookup = MethodHandles.publicLookup ();

Voor het geval we echter ook toegang willen hebben tot privaat en beschermd methoden, kunnen we in plaats daarvan de opzoeken() methode:

MethodHandles.Lookup lookup = MethodHandles.lookup ();

4. Een MethodType

Om het MethodHandlevereist het lookup-object een definitie van het type en dit wordt bereikt door de MethodType klasse.

Vooral, een MethodType vertegenwoordigt de argumenten en het retourtype dat is geaccepteerd en geretourneerd door een methodehandle of doorgegeven en verwacht door een methodehandle-aanroeper.

De structuur van een MethodType is eenvoudig en wordt gevormd door een retourneringstype samen met een passend aantal parametertypen die correct moeten worden afgestemd tussen een methodehandle en al zijn aanroepers.

Op dezelfde manier als MethodHandle, zelfs de gevallen van een MethodType zijn onveranderlijk.

Laten we eens kijken hoe het mogelijk is om een MethodType dat specificeert een java.util.List class als retourtype en een Voorwerp array als invoertype:

MethodType mt = MethodType.methodType (List.class, Object []. Class);

In het geval dat de methode een primitief type retourneert of leegte als het retourtype gebruiken we de klasse die deze typen vertegenwoordigt (void.class, int.class…).

Laten we een MethodType die een int-waarde retourneert en een accepteert Voorwerp:

MethodType mt = MethodType.methodType (int.class, Object.class);

We kunnen nu doorgaan met het maken MethodHandle.

5. Het vinden van een MethodHandle

Nadat we ons type methode hebben gedefinieerd, om een Methode Handvat, we moeten het vinden via de opzoeken of publicLookup object, waarbij ook de oorsprongklasse en de naam van de methode worden opgegeven.

In het bijzonder biedt de opzoekfabriek een reeks methoden waarmee we de methodehandle op een geschikte manier kunnen vinden, rekening houdend met de reikwijdte van onze methode. Laten we beginnen met het eenvoudigste scenario en de belangrijkste onderzoeken.

5.1. Methodehandvat voor methoden

De ... gebruiken findVirtual () method stellen ons in staat om een ​​MethodHandle voor een objectmethode te maken. Laten we er een maken, gebaseerd op de concat () methode van de Draad klasse:

MethodType mt = MethodType.methodType (String.class, String.class); MethodHandle concatMH = publicLookup.findVirtual (String.class, "concat", mt);

5.2. Methodehandgreep voor statische methoden

Als we toegang willen krijgen tot een statische methode, kunnen we in plaats daarvan de findStatic () methode:

MethodType mt = MethodType.methodType (List.class, Object []. Class); MethodHandle asListMH = publicLookup.findStatic (Arrays.class, "asList", mt);

In dit geval hebben we een methode-handle gemaakt die een array van Voorwerpen naar een Lijst van hen.

5.3. Methodehandvat voor constructeurs

Toegang krijgen tot een constructor kan worden gedaan met behulp van de findConstructor () methode.

Laten we een methodehandles maken die zich gedraagt ​​als de constructor van het Geheel getal klasse, waarbij u een Draad attribuut:

MethodType mt = MethodType.methodType (void.class, String.class); MethodHandle newIntegerMH = publicLookup.findConstructor (Integer.class, mt);

5.4. Methodehandvat voor velden

Met behulp van een methode handle is het mogelijk om ook toegang te krijgen tot velden.

Laten we beginnen met het definiëren van het Boek klasse:

public class Book {String id; String titel; // constructor}

Met als voorwaarde een directe toegangszichtbaarheid tussen de methodehandle en de gedeclareerde eigenschap, kunnen we een methodehandle maken die zich gedraagt ​​als een getter:

MethodHandle getTitleMH = lookup.findGetter (Book.class, "title", String.class);

Voor meer informatie over het omgaan met variabelen / velden, bekijk de Java 9 Variable Handles Demystified, waar we de java.lang.invoke.VarHandle API bespreken, toegevoegd in Java 9.

5.5. Methodehandling voor privémethoden

Het maken van een methodehandle voor een privémethode kan worden gedaan met behulp van de java.lang.reflect API.

Laten we beginnen met het toevoegen van een privaat methode naar de Boek klasse:

private String formatBook () {return id + ">" + titel; }

Nu kunnen we een methode-handle maken die zich precies gedraagt ​​als de formatBook () methode:

Methode formatBookMethod = Book.class.getDeclaredMethod ("formatBook"); formatBookMethod.setAccessible (true); MethodHandle formatBookMH = lookup.unreflect (formatBookMethod);

6. Aanroepen van een Method Handle

Nadat we onze methodehandvatten hebben gemaakt, is het gebruik ervan de volgende stap. In het bijzonder de MethodHandle class biedt 3 verschillende manieren om een ​​methode-handle uit te voeren: beroep doen op(), invokeWithArugments () en invokeExact ().

Laten we beginnen met de beroep doen op keuze.

6.1. Een Method Handle aanroepen

Bij gebruik van de beroep doen op() methode dwingen we het aantal argumenten (arity) af dat moet worden vastgesteld, maar we staan ​​het uitvoeren van casting en boxing / unboxing van de argumenten en retourtypen toe.

Laten we eens kijken hoe het mogelijk is om de beroep doen op() met een omkaderd argument:

MethodType mt = MethodType.methodType (String.class, char.class, char.class); MethodHandle replaceMH = publicLookup.findVirtual (String.class, "replace", mt); String output = (String) replaceMH.invoke ("jovo", Character.valueOf ('o'), 'a'); assertEquals ("java", uitvoer);

In dit geval is het vervangenMH vereist char argumenten, maar de beroep doen op() voert een unboxing uit op de Karakter argument voor de uitvoering ervan.

6.2. Aanroepen met argumenten

Het aanroepen van een methode-handle met de invokeWithArguments methode, is de minst beperkende van de drie opties.

In feite staat het een variabele arity-aanroep toe, naast het casten en boxen / unboxen van de argumenten en van de retourtypen.

Door in de praktijk te komen, kunnen we een Lijst van Geheel getal vanaf een array van int waarden:

MethodType mt = MethodType.methodType (List.class, Object []. Class); MethodHandle asList = publicLookup.findStatic (Arrays.class, "asList", mt); Lijst lijst = (Lijst) asList.invokeWithArguments (1,2); assertThat (Arrays.asList (1,2), is (lijst));

6.3. Exact aanroepen

Als we restrictiever willen zijn in de manier waarop we een methodehandle uitvoeren (aantal argumenten en hun type), moeten we de invokeExact () methode.

In feite biedt het geen casting voor de opgegeven klasse en vereist het een vast aantal argumenten.

Laten we eens kijken hoe we dat kunnen doen som twee int waarden met behulp van een methode-handle:

MethodType mt = MethodType.methodType (int.class, int.class, int.class); MethodHandle sumMH = lookup.findStatic (Integer.class, "sum", mt); int sum = (int) sumMH.invokeExact (1, 11); assertEquals (12, som);

Als we in dit geval besluiten om over te gaan naar de invokeExact methode een nummer dat geen int, zal de aanroep leiden tot WrongMethodTypeException.

7. Werken met array

Methode: Handvatten zijn niet bedoeld om alleen met velden of objecten te werken, maar ook met arrays. In feite, met de asSpreader () API is het mogelijk om een ​​methode voor het verspreiden van een array te maken.

In dit geval accepteert de methode-handle een array-argument, waarbij de elementen als positionele argumenten worden verspreid, en optioneel de lengte van de array.

Laten we eens kijken hoe we een methodehandle kunnen spreiden om te controleren of de elementen in een array gelijk zijn aan:

MethodType mt = MethodType.methodType (boolean.class, Object.class); MethodHandle equals = publicLookup.findVirtual (String.class, "equals", mt); MethodHandle methodHandle = equals.asSpreader (Object []. Class, 2); assertTrue ((boolean) methodHandle.invoke (nieuw object [] {"java", "java"}));

8. Verbetering van een methodehandvat

Als we eenmaal een methode-handle hebben gedefinieerd, is het mogelijk om deze te verbeteren door de methode-handle aan een argument te binden zonder deze daadwerkelijk aan te roepen.

In Java 9 wordt dit soort gedrag bijvoorbeeld gebruikt om te optimaliseren Draad aaneenschakeling.

Laten we eens kijken hoe we een aaneenschakeling kunnen uitvoeren door een achtervoegsel aan onze te binden concatMH:

MethodType mt = MethodType.methodType (String.class, String.class); MethodHandle concatMH = publicLookup.findVirtual (String.class, "concat", mt); MethodHandle bindedConcatMH = concatMH.bindTo ("Hallo"); assertEquals ("Hallo wereld!", bindedConcatMH.invoke ("Wereld!"));

9. Verbeteringen in Java 9

Met Java 9 zijn er enkele verbeteringen aangebracht in het Methode: Handvatten API met als doel het veel gebruiksvriendelijker te maken.

De verbeteringen hadden betrekking op 3 hoofdonderwerpen:

  • Opzoekfuncties - het opzoeken van klassen uit verschillende contexten toestaan ​​en niet-abstracte methoden in interfaces ondersteunen
  • Afhandeling van argumenten - verbetering van de functionaliteit voor argumentvouwen, argumenteren en argumenteren
  • Extra combinaties - lussen toevoegen (lus, herhalingslus, doWhileLoop…) en een betere ondersteuning voor het afhandelen van uitzonderingen met de probeer tot slot

Deze veranderingen leidden tot enkele extra voordelen:

  • Verbeterde optimalisaties van de JVM-compiler
  • Instantiation reductie
  • Precisie ingeschakeld bij het gebruik van de Methode: Handvatten API

Details van de aangebrachte verbeteringen zijn beschikbaar op het Methode: Handvatten API Javadoc.

10. Conclusie

In dit artikel hebben we de Methode: Handvatten API, wat ze zijn en hoe we ze kunnen gebruiken.

We hebben ook besproken hoe het zich verhoudt tot de Reflection API en aangezien de methodehandles bewerkingen op laag niveau mogelijk maken, zou het beter moeten zijn om ze niet te gebruiken, tenzij ze perfect passen bij de reikwijdte van de taak.

Zoals altijd is de volledige broncode voor dit artikel beschikbaar op Github.