1. Pengenalan
Dalam artikel ini, kita akan meneroka API penting yang diperkenalkan di Java 7 dan disempurnakan dalam versi berikut, java.lang.invoke.MethodHandles .
Khususnya, kami akan mengetahui kaedah pengendalian kaedah, cara membuatnya dan cara menggunakannya.
2. Apakah Kaedah Mengendalikan?
Menjadi definisi, seperti yang dinyatakan dalam dokumentasi API:
Pegangan kaedah adalah rujukan yang diketik dan dapat dilaksanakan secara langsung ke kaedah yang mendasari, konstruktor, medan, atau operasi tingkat rendah yang serupa, dengan transformasi argumen pilihan atau nilai pengembalian.
Dengan cara yang lebih mudah, pengendalian kaedah adalah mekanisme tahap rendah untuk mencari, menyesuaikan dan menggunakan kaedah .
Pemegang kaedah tidak berubah dan tidak mempunyai keadaan yang kelihatan.
Untuk membuat dan menggunakan MethodHandle , diperlukan 4 langkah:
- Membuat carian
- Membuat jenis kaedah
- Mencari pemegang kaedah
- Memohon kaedah pengendalian
2.1. Kaedah Mengendalikan vs Refleksi
Pegangan kaedah diperkenalkan untuk bekerja bersama API java.lang.reflect yang ada , kerana mereka melayani tujuan yang berbeza dan mempunyai ciri yang berbeza.
Dari sudut prestasi, API MethodHandles dapat menjadi lebih cepat daripada Reflection API kerana pemeriksaan akses dibuat pada waktu penciptaan dan bukan pada waktu pelaksanaan . Perbezaan ini diperkuat jika pengurus keselamatan hadir, kerana pencarian anggota dan kelas akan dikenakan pemeriksaan tambahan.
Namun, memandangkan prestasi bukan satu-satunya ukuran kesesuaian untuk sesuatu tugas, kita juga harus mempertimbangkan bahawa MethodHandles API lebih sukar digunakan kerana kekurangan mekanisme seperti penghitungan kelas ahli, pemeriksaan bendera aksesibilitas dan banyak lagi.
Walaupun begitu, API MethodHandles menawarkan kemungkinan kaedah kari, mengubah jenis parameter dan mengubah susunannya.
Mempunyai definisi dan tujuan yang jelas dari MethodHandles API, kita sekarang dapat mulai bekerja dengannya, mulai dari pencarian.
3. Membuat Pencarian
Perkara pertama yang harus dilakukan ketika kita ingin membuat pengendalian kaedah adalah dengan mengambil carian, objek kilang yang bertanggungjawab untuk membuat pengendalian kaedah untuk kaedah, pembina, dan medan, yang dapat dilihat oleh kelas pencarian.
Melalui MethodHandles API, mungkin untuk membuat objek pencarian, dengan mod akses yang berbeza.
Mari buat carian yang memberikan akses kepada kaedah awam :
MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();
Namun, sekiranya kita ingin mempunyai akses ke kaedah peribadi dan dilindungi , kita boleh menggunakan kaedah pencarian (
MethodHandles.Lookup lookup = MethodHandles.lookup();
4. Membuat Jenis Kaedah
Untuk dapat membuat MethodHandle , objek pencarian memerlukan definisi jenisnya dan ini dicapai melalui kelas MethodType .
Khususnya, yang MethodType mewakili hujah-hujah dan pulangan jenis yang diterima dan dikembalikan dengan kaedah mengendalikan atau diluluskan dan dijangka dengan kaedah mengendalikan pemanggil .
Struktur MethodType sederhana dan dibentuk oleh jenis pengembalian bersama dengan sebilangan jenis parameter yang sesuai yang mesti dipadankan dengan betul antara pemegang kaedah dan semua pemanggilnya.
Dengan kaedah yang sama seperti MethodHandle , bahkan contoh dari TypeType tidak berubah.
Mari lihat bagaimana mungkin untuk menentukan MethodType yang menentukan kelas java.util.List sebagai jenis kembali dan array Objek sebagai jenis input:
MethodType mt = MethodType.methodType(List.class, Object[].class);
Sekiranya kaedah mengembalikan jenis primitif atau kekosongan sebagai jenis pengembaliannya, kami akan menggunakan kelas yang mewakili jenis tersebut (void.class, int.class…).
Mari tentukan MethodType yang mengembalikan nilai int dan menerima Objek :
MethodType mt = MethodType.methodType(int.class, Object.class);
Kita sekarang boleh terus membuat MethodHandle .
5. Mencari Kaedah Menangani
Setelah kami menentukan jenis kaedah kami, untuk membuat MethodHandle, kami harus mencarinya melalui objek carian atau publicLookup , dengan menyediakan juga kelas asal dan nama kaedah.
Khususnya, kilang mencari menyediakan sekumpulan kaedah yang membolehkan kami mencari kaedah pengendalian dengan cara yang sesuai dengan mempertimbangkan skop kaedah kami. Bermula dengan senario paling mudah, mari kita meneroka yang utama.
5.1. Kaedah Mengendalikan untuk Kaedah
Dengan menggunakan kaedah findVirtual () membolehkan kita membuat MethodHandle untuk kaedah objek. Mari buat satu, berdasarkan CONCAT () kaedah yang String kelas:
MethodType mt = MethodType.methodType(String.class, String.class); MethodHandle concatMH = publicLookup.findVirtual(String.class, "concat", mt);
5.2. Kaedah Mengendalikan untuk Kaedah Statik
When we want to gain access to a static method, we can instead use the findStatic() method:
MethodType mt = MethodType.methodType(List.class, Object[].class); MethodHandle asListMH = publicLookup.findStatic(Arrays.class, "asList", mt);
In this case, we created a method handle that converts an array of Objects to a List of them.
5.3. Method Handle for Constructors
Gaining access to a constructor can be done using the findConstructor() method.
Let's create a method handles that behaves as the constructor of the Integer class, accepting a String attribute:
MethodType mt = MethodType.methodType(void.class, String.class); MethodHandle newIntegerMH = publicLookup.findConstructor(Integer.class, mt);
5.4. Method Handle for Fields
Using a method handle it's possible to gain access also to fields.
Let's start defining the Book class:
public class Book { String id; String title; // constructor }
Having as precondition a direct access visibility between the method handle and the declared property, we can create a method handle that behaves as a getter:
MethodHandle getTitleMH = lookup.findGetter(Book.class, "title", String.class);
For further information on handling variables/fields, give a look at the Java 9 Variable Handles Demystified, where we discuss the java.lang.invoke.VarHandle API, added in Java 9.
5.5. Method Handle for Private Methods
Creating a method handle for a private method can be done, with the help of the java.lang.reflect API.
Let's start adding a private method to the Book class:
private String formatBook() { return id + " > " + title; }
Now we can create a method handle that behaves exactly as the formatBook() method:
Method formatBookMethod = Book.class.getDeclaredMethod("formatBook"); formatBookMethod.setAccessible(true); MethodHandle formatBookMH = lookup.unreflect(formatBookMethod);
6. Invoking a Method Handle
Once we've created our method handles, use them is the next step. In particular, the MethodHandle class provides 3 different way to execute a method handle: invoke(), invokeWithArugments() and invokeExact().
Let's start with the invoke option.
6.1. Invoking a Method Handle
When using the invoke() method, we enforce the number of the arguments (arity) to be fixed but we allow the performing of casting and boxing/unboxing of the arguments and return types.
Let's see how it's possible to use the invoke() with a boxed 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", output);
In this case, the replaceMH requires char arguments, but the invoke() performs an unboxing on the Character argument before its execution.
6.2. Invoking With Arguments
Invoking a method handle using the invokeWithArguments method, is the least restrictive of the three options.
In fact, it allows a variable arity invocation, in addition to the casting and boxing/unboxing of the arguments and of the return types.
Coming to practice, this allows us to create a List of Integer starting from an array of int values:
MethodType mt = MethodType.methodType(List.class, Object[].class); MethodHandle asList = publicLookup.findStatic(Arrays.class, "asList", mt); List list = (List) asList.invokeWithArguments(1,2); assertThat(Arrays.asList(1,2), is(list));
6.3. Invoking Exact
In case we want to be more restrictive in the way we execute a method handle (number of arguments and their type), we have to use the invokeExact() method.
In fact, it doesn't provide any casting to the class provided and requires a fixed number of arguments.
Let's see how we can sum two int values using a method 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, sum);
If in this case, we decide to pass to the invokeExact method a number that isn't an int, the invocation will lead to WrongMethodTypeException.
7. Working With Array
MethodHandles aren't intended to work only with fields or objects, but also with arrays. As a matter of fact, with the asSpreader() API, it's possible to make an array-spreading method handle.
In this case, the method handle accepts an array argument, spreading its elements as positional arguments, and optionally the length of the array.
Let's see how we can spread a method handle to check if the elements within an array are equals:
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. Enhancing a Method Handle
Once we've defined a method handle, it's possible to enhance it by binding the method handle to an argument without actually invoking it.
For example, in Java 9, this kind of behaviour is used to optimize String concatenation.
Let's see how we can perform a concatenation, binding a suffix to our 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 Enhancements
With Java 9, few enhancements were made to the MethodHandles API with the aim to make it much easier to use.
The enhancements affected 3 main topics:
- Lookup functions – allowing class lookups from different contexts and support non-abstract methods in interfaces
- Argument handling – improving the argument folding, argument collecting and argument spreading functionalities
- Additional combinations – adding loops (loop, whileLoop, doWhileLoop…) and a better exception handling support with the tryFinally
These changes resulted in few additional benefits:
- Peningkatan pengoptimuman penyusun JVM
- Pengurangan instantiasi
- Aktifkan ketepatan dalam penggunaan MethodHandles API
Perincian peningkatan yang dibuat boleh didapati di MethodHandles API Javadoc.
10. Kesimpulannya
Dalam artikel ini, kami membahas MethodHandles API, apa itu dan bagaimana kami dapat menggunakannya.
Kami juga membincangkan bagaimana kaitannya dengan Reflection API dan kerana kaedah pengendalian membolehkan operasi peringkat rendah, lebih baik mengelakkan penggunaannya, kecuali jika sesuai dengan ruang lingkup pekerjaan dengan sempurna.
Seperti biasa, kod sumber lengkap untuk artikel ini terdapat di Github.