1. Einleitung
In diesem Tutorial werden wir eine wichtige API behandeln, die in Java 7 eingeführt und in neuen Versionen erweitert wurde: java.lang.invoke.MethodHandles .
Wir werden lernen, was Methodenhandles sind, wie man sie erstellt und verwendet.
2. Was sind Methodenhandles?
In der API-Dokumentation hat das Methodenhandle folgende Definition:
Das Methodenhandle ist eine typisierte, ausführbare Referenz auf die Basismethode, den Konstruktor, das Feld oder eine andere Operation auf niedriger Ebene mit zusätzlichen Transformationen von Argumenten oder Rückgabewerten.
Mit anderen Worten, Methodenhandles sind ein Mechanismus auf niedriger Ebene zum Suchen, Anpassen und Aufrufen von Methoden. Methodenhandles sind unveränderlich und haben keinen Anzeigestatus.
Um MethodHandle
zu erstellen und zu verwenden, MethodHandle
Sie 4 Schritte ausführen:
- Erstellen Sie einen Suchdeskriptor - Lookup
- Methodentyp deklarieren
- Suchmethodenhandle
- Methodenhandle aufrufen
2.1. Methodengriffe gegen Reflexion
Es wurden Methodenhandles eingeführt, die zusammen mit der API java.lang.reflect funktionieren Sie werden für unterschiedliche Zwecke erstellt und unterscheiden sich in ihren Eigenschaften.
In Bezug auf die Leistung kann die MethodHandles-API viel schneller als die Reflection-API sein, da Zugriffsprüfungen eher zur Erstellungszeit als zur Ausführung durchgeführt werden . Wenn es einen Sicherheitsmanager gibt, erhöht sich dieser Unterschied, weil Das Suchen nach Klassen und das Abrufen ihrer Elemente unterliegen zusätzlichen Überprüfungen.
Die Leistung ist jedoch nicht der einzige Indikator für die Optimalität einer Aufgabe. Es sollte berücksichtigt werden, dass die MethodHandles-API aufgrund fehlender Mechanismen wie dem Abrufen von Klassenmethoden, dem Überprüfen von Zugriffstoken usw. schwieriger zu verwenden ist.
Trotzdem ermöglicht die MethodHandles-API das Currying von Methoden, wobei Typ und Reihenfolge der Parameter geändert werden.
Wenn wir nun die Definition und den Zweck der MethodHandles-API kennen, können wir mit ihnen arbeiten. Beginnen wir mit der Suche nach Methoden.
3. Lookup erstellen
Wenn Sie ein Methodenhandle erstellen möchten, müssen Sie zunächst eine Suche abrufen, das Factory-Objekt, das für die Erstellung von Methodenhandles für Methoden, Konstruktoren und Felder verantwortlich ist, die für die Suchklasse sichtbar sind.
Mit der MethodHandles-API können Sie ein Suchobjekt mit verschiedenen Zugriffsmodi erstellen.
Erstellen Sie eine Suche, die Zugriff auf öffentliche Methoden bietet:
MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();
Wenn wir jedoch Zugriff auf die privaten und geschützten Methoden benötigen, können wir stattdessen die lookup () -Methode verwenden:
MethodHandles.Lookup lookup = MethodHandles.lookup();
4. Erstellen eines MethodType
Um ein MethodHandle zu erstellen, muss das Suchobjekt auf einen Typ festgelegt werden. Dies kann mithilfe der MethodType-Klasse erfolgen.
Insbesondere stellt ein MethodType die Argumente und den Rückgabetyp dar, die vom Methodenhandle akzeptiert und zurückgegeben oder vom aufrufenden Code übergeben und erwartet werden.
Die MethodType-Struktur ist einfach. Sie besteht aus dem Rückgabetyp und der entsprechenden Anzahl von Parametertypen, die vollständig zwischen dem Methodenhandle und dem aufrufenden Code korrelieren sollten.
Wie bei MethodHandle sind alle Instanzen von MethodType unveränderlich.
Lassen Sie uns sehen, wie Sie einen MethodType definieren, der die Klasse java.util.List
als Rückgabetyp und das Objektarray als Dateneingabetyp definiert:
MethodType mt = MethodType.methodType(List.class, Object[].class);
Falls die Methode einen einfachen oder ungültigen (void.class, int.class …)
, verwenden wir eine Klasse, die diese Typen darstellt (void.class, int.class …)
.
Definieren Sie einen MethodType, der ein int zurückgibt und ein Objekt akzeptiert:
MethodType mt = MethodType.methodType(int.class, Object.class);
Sie können mit der Erstellung des MethodHandle beginnen.
5. Search MethodHandle
Nachdem wir den Typ der Methode festgelegt haben, müssen Sie ihn zum Erstellen eines MethodHandle mithilfe des Lookup-Objekts oder publicLookup finden, das auch die Quellklasse und den Methodennamen zurückgibt.
Lookup bietet eine Reihe von Methoden, mit denen Sie das Methodenhandle unter Berücksichtigung des Anwendungsbereichs der Methode optimal finden können. Betrachten Sie die grundlegenden Ansätze, beginnend mit den einfachsten.
5.1. Methodenhandle für Methoden
Mit der findVirtual()
-Methode können Sie ein MethodHandle für eine Instanzmethode erstellen. Erstellen Sie es basierend auf der concat()
-Methode der String
Klasse:
MethodType mt = MethodType.methodType(String.class, String.class); MethodHandle concatMH = publicLookup.findVirtual(String.class, "concat", mt);
5.2. Methodenhandle für statische Methoden
Um auf die statische Methode zuzugreifen, können Sie die findStatic()
-Methode verwenden:
MethodType mt = MethodType.methodType(List.class, Object[].class); MethodHandle asListMH = publicLookup.findStatic(Arrays.class, "asList", mt);
In diesem Fall haben wir ein Methodenhandle für eine Methode erstellt, die ein Array vom Typ Object
in eine List
konvertiert.
5.3. Methodenhandle für Konstruktoren
Sie können mit der findConstructor()
-Methode auf den Konstruktor findConstructor()
.
Erstellen Sie mit dem String-Parameter ein Methodenhandle mit einem Verhalten ähnlich dem Konstruktor der Integer-Klasse:
MethodType mt = MethodType.methodType(void.class, String.class); MethodHandle newIntegerMH = publicLookup.findConstructor(Integer.class, mt);
5.4. Methodenhandle für Felder
Mit dem Methodenhandle können Sie auch auf die Felder zugreifen.
Beginnen wir mit der Definition der Buchklasse:
public class Book { String id; String title;
Als Anfangsbedingung haben wir eine direkte Sichtbarkeit zwischen dem Methodenhandle und der deklarierten Eigenschaft, sodass wir ein Methodenhandle mit einem ähnlichen Verhalten wie eine get-Methode erstellen können:
MethodHandle getTitleMH = lookup.findGetter(Book.class, "title", String.class);
Weitere Informationen zur Verwaltung von Variablen / Feldern finden Sie im Artikel Java 9 Variable Handles Demystified , in dem wir über die in Java 9 eingeführte API java.lang.invoke.VarHandle sprechen.
5.5. Methodenhandle für private Methoden
Mit der API java.lang.reflect können Sie ein Methodenhandle für eine Methode vom Typ private erstellen.
Beginnen wir mit der Erstellung einer privaten Methode für die Book-Klasse:
private String formatBook() { return id + " > " + title; }
Jetzt können wir ein Methodenhandle mit dem Verhalten der formatBook()
-Methode formatBook()
:
Method formatBookMethod = Book.class.getDeclaredMethod("formatBook"); formatBookMethod.setAccessible(true); MethodHandle formatBookMH = lookup.unreflect(formatBookMethod);
6. Methodenhandle aufrufen
Fahren Sie mit dem nächsten Schritt fort, nachdem Sie unser Methodenhandle erstellt haben. Die MethodHandle-Klasse bietet drei verschiedene Möglichkeiten zum Aufrufen des Methodenhandles: invokeWithArugments()
, invokeWithArugments()
und invokeExact()
.
Beginnen wir mit der invoke
.
6.1. Methodenhandle aufrufen
Bei Verwendung der invoke()
-Methode ist die Anzahl der Argumente (arity) festgelegt. Es ist jedoch möglich, das Typ-Casting durchzuführen und die Argumente und Typen des Rückgabewerts zu packen / zu entpacken.
Nun wollen wir sehen, wie invoke()
mit einem gepackten Argument verwendet werden kann:
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", output);
In diesem Fall erfordert replaceMH
char
Argumente, aber die invoke()
-Methode dekomprimiert das Character-Argument, bevor es ausgeführt wird.
6.2. Aufruf mit Argumenten
Das Aufrufen des Methodenhandles mit invokeWithArguments
der geringsten Einschränkung.
Zusätzlich zum Überprüfen von Typen und zum Packen / Entpacken von Argumenten und Rückgabewerten können Sie Aufrufe mit einer variablen Anzahl von Parametern tätigen.
In der Praxis können wir eine Ganzzahlliste mit einem Array von int-Werten unbekannter Länge erstellen:
MethodType mt = MethodType.methodType(List.class, Object[].class); MethodHandle asList = publicLookup.findStatic(Arrays.class, "asList", mt); List<Integer> list = (List<Integer>) asList.invokeWithArguments(1, 2); assertThat(Arrays.asList(1,2), is(list));
6.3. Genau anrufen
Wenn das Methodenhandle restriktiver ausgeführt werden soll (anhand der Argumentmenge und ihres Typs), verwenden wir die invokeExact()
-Methode.
Tatsächlich bietet es nicht die Möglichkeit, Typen einer Klasse umzuwandeln, und erfordert einen festen Satz von Argumenten.
Mal sehen, wie wir mit dem Methodenhandle zwei int-Werte hinzufügen können:
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, sum);
In diesem Fall erhalten wir beim Aufrufen eine WrongMethodTypeException
, wenn wir der invokeExact
Methode eine Nummer übergeben, die kein int ist.
7. Arbeiten Sie mit Arrays
MethodHandles können nicht nur mit Feldern und Objekten, sondern auch mit Arrays arbeiten. Mit der asSpreader()
API können Sie ein Methodenhandle erstellen, das Arrays als Positionsargumente unterstützt.
In diesem Fall nimmt das Methodenhandle ein Array und verteilt seine Elemente als Positionsargumente und optional die Länge des Arrays.
Mal sehen, wie das Methodenhandle überprüft, ob die Array-Argumente dieselbe Zeichenfolge sind:
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(new Object[] { "java", "java" }));
8. Klarstellung des Methodenhandles
Sobald das Methodenhandle festgelegt ist, können Sie es verfeinern, indem Sie an das Argument binden, ohne die Methode aufzurufen.
In Java 9 wird dieser Trick beispielsweise verwendet, um die Verkettung von Zeichenfolgen zu optimieren.
Mal sehen, wie Verkettung durch Anhängen des Suffixes an concatMH
:
MethodType mt = MethodType.methodType(String.class, String.class); MethodHandle concatMH = publicLookup.findVirtual(String.class, "concat", mt); MethodHandle bindedConcatMH = concatMH.bindTo("Hello "); assertEquals("Hello World!", bindedConcatMH.invoke("World!"));
9. Java 9-Updates
Java 9 hat mehrere Änderungen an der MethodHandles-API vorgenommen, um die Verwendung zu vereinfachen.
Aktualisierungen betreffen drei Hauptaspekte:
- Suchfunktionen - Ermöglichen die Suche aus verschiedenen Kontexten und unterstützen nicht abstrakte Methoden in Schnittstellen.
- Operationen mit Argumenten - Verbesserung der Funktionalität zum Falten, Sammeln und Verteilen von Argumenten.
- Zusätzliche Kombinationen sind das Hinzufügen von Schleifenoperationen (
loop
, whileLoop
, doWhileLoop
, ...) und eine verbesserte Ausnahmeverwaltung mit tryFinally
.
Diese Änderungen führten zu weiteren nützlichen Innovationen:
- Verbesserte JVM-Compiler-Optimierung
- Verringerte Instanz
- Konkretisierung der Verwendung der MethodHandles-API
Eine detailliertere Liste der Änderungen finden Sie in der Javadoc MethodHandles-API .
10. Schlussfolgerung
In diesem Artikel haben wir uns mit der MethodHandles-API getroffen und auch erfahren, was Methodenhandles sind und wie sie verwendet werden.
Wir haben auch beschrieben, wie es mit der Reflection-API verknüpft ist. Da das Aufrufen von Methodenhandles eine eher einfache Operation ist, ist ihre Verwendung nur gerechtfertigt, wenn sie genau zu Ihren Aufgaben passen.
Wie üblich ist der gesamte Quellcode für den Artikel auf Github verfügbar .