Dinamis 101

Rilis Oracle Java 7 memperkenalkan invokedynamicinstruksi bytecode baru ke Java Virtual Machine (JVM) dan java.lang.invokepaket API baru ke pustaka kelas standar. Posting ini memperkenalkan Anda pada instruksi dan API ini.

Apa dan bagaimana dari invokedynamic

T: Apa itu invokedynamic?

J: invokedynamic adalah instruksi bytecode yang memfasilitasi implementasi bahasa dinamis (untuk JVM) melalui pemanggilan metode dinamis. Instruksi ini dijelaskan dalam Spesifikasi JVM Java SE 7 Edition.

Bahasa dinamis dan statis

Sebuah bahasa dinamis (juga dikenal sebagai bahasa dinamis diketik ) adalah bahasa pemrograman tingkat tinggi yang memeriksa jenis biasanya dilakukan pada saat runtime, fitur yang dikenal sebagai mengetik dinamis . Pemeriksaan jenis memverifikasi bahwa program bertipe aman : semua argumen operasi memiliki jenis yang benar. Groovy, Ruby, dan JavaScript adalah contoh bahasa dinamis. ( @groovy.transform.TypeCheckedAnotasi menyebabkan Groovy mengetik centang pada waktu kompilasi.)

Sebaliknya, bahasa statis (juga dikenal sebagai bahasa yang diketik secara statis ) melakukan pemeriksaan jenis pada waktu kompilasi, fitur yang dikenal sebagai pengetikan statis . Kompilator memverifikasi bahwa program memiliki tipe yang benar, meskipun mungkin menunda beberapa pemeriksaan tipe ke runtime (pikirkan cast dan checkcastinstruksinya). Java adalah contoh bahasa statis. Compiler Java menggunakan informasi jenis ini untuk menghasilkan bytecode dengan tipe kuat, yang dapat dieksekusi secara efisien oleh JVM.

T: Bagaimana invokedynamicmemfasilitasi implementasi bahasa dinamis?

J: Dalam bahasa dinamis, pemeriksaan jenis biasanya terjadi pada waktu proses. Pengembang harus meneruskan jenis yang sesuai atau risiko kegagalan waktu proses. Ini sering kali java.lang.Objectmerupakan tipe paling akurat untuk argumen metode. Situasi ini memperumit pemeriksaan jenis, yang memengaruhi kinerja.

Tantangan lain adalah bahwa bahasa dinamis biasanya menawarkan kemampuan untuk menambahkan bidang / metode ke dan menghapusnya dari kelas yang ada. Akibatnya, kelas, metode, dan resolusi kolom harus ditunda ke runtime. Selain itu, sering kali perlu untuk menyesuaikan pemanggilan metode ke target yang memiliki tanda tangan berbeda.

Tantangan ini biasanya memerlukan dukungan runtime ad hoc yang akan dibangun di atas JVM. Dukungan ini mencakup kelas tipe pembungkus, menggunakan tabel hash untuk memberikan resolusi simbol dinamis, dan seterusnya. Bytecode dibuat dengan titik masuk ke runtime dalam bentuk panggilan metode menggunakan salah satu dari empat instruksi pemanggilan metode:

  • invokestaticdigunakan untuk memanggil staticmetode.
  • invokevirtualdigunakan untuk memanggil publicdan protectednon- staticmetode melalui pengiriman dinamis.
  • invokeinterfacemirip dengan invokevirtualkecuali untuk metode pengiriman yang didasarkan pada tipe antarmuka.
  • invokespecialdigunakan untuk memanggil metode inisialisasi instance (konstruktor) serta privatemetode dan metode superclass dari kelas saat ini.

Dukungan runtime ini memengaruhi kinerja. Bytecode yang dihasilkan sering kali memerlukan beberapa pemanggilan metode JVM aktual untuk satu pemanggilan metode bahasa dinamis. Refleksi digunakan secara luas dan berkontribusi pada penurunan kinerja. Selain itu, banyaknya jalur eksekusi yang berbeda membuat compiler just-in-time (JIT) JVM tidak dapat menerapkan pengoptimalan.

Untuk mengatasi kinerja yang buruk, invokedynamicinstruksi menghilangkan dukungan runtime ad hoc. Sebagai gantinya, panggil bootstrap dengan memanggil logika runtime yang secara efisien memilih metode target, dan panggilan berikutnya biasanya memanggil metode target tanpa harus melakukan bootstrap ulang.

invokedynamicjuga menguntungkan pelaksana bahasa dinamis dengan mendukung target situs panggilan yang berubah secara dinamis - situs panggilan , lebih khusus lagi, situs panggilan dinamis adalah invokedynamicpetunjuk. Selain itu, karena JVM mendukung secara internal invokedynamic, instruksi ini dapat dioptimalkan dengan lebih baik oleh kompiler JIT.

Menangani metode

T: Saya memahami bahwa invokedynamicberfungsi dengan tuas metode untuk memfasilitasi pemanggilan metode dinamis. Apa yang dimaksud dengan pegangan metode?

J: Tuas metode adalah "referensi yang diketik dan dapat dijalankan secara langsung ke metode yang mendasari, konstruktor, kolom, atau operasi tingkat rendah serupa, dengan transformasi opsional dari argumen atau nilai kembalian." Dengan kata lain, ini mirip dengan penunjuk fungsi gaya-C yang menunjuk ke kode yang dapat dieksekusi - sebuah target - dan yang dapat direferensikan untuk memanggil kode ini. Menangani metode dijelaskan oleh java.lang.invoke.MethodHandlekelas abstrak .

T: Dapatkah Anda memberikan contoh sederhana pembuatan metode menangani dan pemanggilan?

A: Lihat Daftar 1.

Daftar 1. MHD.java(versi 1)

import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; public class MHD { public static void main(String[] args) throws Throwable { MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodHandle mh = lookup.findStatic(MHD.class, "hello", MethodType.methodType(void.class)); mh.invokeExact(); } static void hello() { System.out.println("hello"); } }

Kode 1 menjelaskan metode menangani program demonstrasi yang terdiri dari main()dan hello()metode kelas. Tujuan program ini adalah untuk memanggil hello()melalui pegangan metode.

main()Tugas pertama adalah mendapatkan sebuah java.lang.invoke.MethodHandles.Lookupobjek. Objek ini adalah pabrik untuk membuat pegangan metode dan digunakan untuk mencari target seperti metode virtual, metode statis, metode khusus, konstruktor, dan pengakses bidang. Selain itu, ini bergantung pada konteks pemanggilan situs panggilan dan memberlakukan pembatasan akses penanganan metode setiap kali pegangan metode dibuat. Dengan kata lain, situs panggilan (seperti main()metode Cantuman 1 yang bertindak sebagai situs panggilan) yang memperoleh objek pencarian hanya dapat mengakses target yang dapat diakses oleh situs panggilan tersebut. Objek pencarian diperoleh dengan memanggil metode java.lang.invoke.MethodHandleskelas MethodHandles.Lookup lookup().

publicLookup()

MethodHandlesjuga mendeklarasikan MethodHandles.Lookup publicLookup()metode. Tidak seperti lookup(), yang dapat digunakan untuk mendapatkan pegangan metode ke metode / konstruktor atau bidang yang publicLookup()dapat diakses , dapat digunakan untuk mendapatkan pegangan metode ke bidang yang dapat diakses publik atau hanya metode / konstruktor yang dapat diakses publik.

Setelah mendapatkan objek pencarian, MethodHandle findStatic(Class refc, String name, MethodType type)metode objek ini dipanggil untuk mendapatkan metode menangani hello()metode tersebut. Argumen pertama yang diteruskan ke findStatic()adalah referensi ke kelas ( MHD) tempat metode ( hello()) diakses, dan argumen kedua adalah nama metode. Argumen ketiga adalah contoh tipe metode , yang "merepresentasikan argumen dan tipe kembalian yang diterima dan dikembalikan oleh pegangan metode, atau argumen dan tipe kembalian yang dilewatkan dan diharapkan oleh pemanggil penanganan metode." Ini diwakili oleh sebuah instance dari java.lang.invoke.MethodTypekelas, dan diperoleh (dalam contoh ini) dengan memanggil java.lang.invoke.MethodType's MethodType methodType(Class rtype)metode. Metode ini dipanggil karena hello()hanya menyediakan tipe pengembalian, yang kebetulan sajavoid. Jenis pengembalian ini tersedia methodType()dengan meneruskan void.classke metode ini.

Pegangan metode yang dikembalikan ditetapkan ke mh. Objek ini kemudian digunakan untuk panggilan MethodHandle's Object invokeExact(Object... args)metode, untuk memohon metode pegangan. Dengan kata lain, invokeExact()hasil hello()dipanggil, dan helloditulis ke aliran keluaran standar. Karena invokeExact()dideklarasikan untuk melempar Throwable, saya telah menambahkan throws Throwableke main()header metode.

T: Dalam jawaban Anda sebelumnya, Anda menyebutkan bahwa objek pencarian hanya dapat mengakses target yang dapat diakses oleh situs panggilan. Dapatkah Anda memberikan contoh yang mendemonstrasikan upaya mendapatkan pegangan metode ke target yang tidak dapat diakses?

A: Lihat Daftar 2.

Daftar 2. MHD.java(versi 2)

import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; class HW { public void hello1() { System.out.println("hello from hello1"); } private void hello2() { System.out.println("hello from hello2"); } } public class MHD { public static void main(String[] args) throws Throwable { HW hw = new HW(); MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodHandle mh = lookup.findVirtual(HW.class, "hello1", MethodType.methodType(void.class)); mh.invoke(hw); mh = lookup.findVirtual(HW.class, "hello2", MethodType.methodType(void.class)); } }

Kode 2 menyatakan HW(Halo, Dunia) dan MHDkelas. HWmendeklarasikan publichello1()metode instance dan privatehello2()metode instance. MHDmendeklarasikan main()metode yang akan mencoba memanggil metode ini.

main()Tugas pertama adalah membuat instance HWdalam persiapan untuk pemanggilan hello1()dan hello2(). Selanjutnya, ia memperoleh objek pencarian dan menggunakan objek ini untuk mendapatkan pegangan metode untuk pemanggilan hello1(). Kali ini, MethodHandles.Lookup's findVirtual()metode ini disebut dan argumen pertama diteruskan ke metode ini adalah Classobjek yang menjelaskan HWclass.

Ternyata itu findVirtual()akan berhasil, dan mh.invoke(hw);ekspresi berikutnya akan dipanggil hello1(), menghasilkan hello from hello1keluaran.

Karena hello1()itu public, itu dapat diakses oleh main()situs panggilan metode. Sebaliknya, hello2()tidak dapat diakses. Akibatnya, findVirtual()pemanggilan kedua akan gagal dengan IllegalAccessException.

Saat Anda menjalankan aplikasi ini, Anda harus mengamati output berikut:

hello from hello1 Exception in thread "main" java.lang.IllegalAccessException: member is private: HW.hello2()void, from MHD at java.lang.invoke.MemberName.makeAccessException(MemberName.java:507) at java.lang.invoke.MethodHandles$Lookup.checkAccess(MethodHandles.java:1172) at java.lang.invoke.MethodHandles$Lookup.checkMethod(MethodHandles.java:1152) at java.lang.invoke.MethodHandles$Lookup.accessVirtual(MethodHandles.java:648) at java.lang.invoke.MethodHandles$Lookup.findVirtual(MethodHandles.java:641) at MHD.main(MHD.java:27)

T: Daftar 1 dan 2 menggunakan metode invokeExact()dan invoke()untuk menjalankan pegangan metode. Apa perbedaan antara metode ini?

J: Meskipun invokeExact()dan invoke()dirancang untuk mengeksekusi pegangan metode (sebenarnya, kode target yang dirujuk oleh pegangan metode), keduanya berbeda ketika melakukan konversi tipe pada argumen dan nilai kembalian. invokeExact()tidak melakukan konversi tipe kompatibel otomatis pada argumen. Argumennya (atau ekspresi argumen) harus merupakan tipe yang sama persis dengan tanda tangan metode, dengan setiap argumen disediakan secara terpisah, atau semua argumen disediakan bersama sebagai larik. invoke()memerlukan argumennya (atau ekspresi argumen) menjadi kecocokan tipe yang cocok dengan tanda tangan metode - konversi tipe otomatis dilakukan, dengan setiap argumen disediakan secara terpisah, atau semua argumen disediakan bersama sebagai larik.

T: Dapatkah Anda memberi saya contoh yang menunjukkan cara memanggil pengambil dan penyetel bidang instance?

A: Lihat Daftar 3.

Kode 3. MHD.java(versi 3)

import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; class Point { int x; int y; } public class MHD { public static void main(String[] args) throws Throwable { MethodHandles.Lookup lookup = MethodHandles.lookup(); Point point = new Point(); // Set the x and y fields. MethodHandle mh = lookup.findSetter(Point.class, "x", int.class); mh.invoke(point, 15); mh = lookup.findSetter(Point.class, "y", int.class); mh.invoke(point, 30); mh = lookup.findGetter(Point.class, "x", int.class); int x = (int) mh.invoke(point); System.out.printf("x = %d%n", x); mh = lookup.findGetter(Point.class, "y", int.class); int y = (int) mh.invoke(point); System.out.printf("y = %d%n", y); } }

Kode 3 memperkenalkan Pointkelas dengan sepasang bidang contoh integer 32-bit bernama xdan y. Setter masing-masing bidang dan pengambil diakses dengan memanggil MethodHandles.Lookup's findSetter()dan findGetter()metode, dan menghasilkan yang MethodHandledikembalikan. Masing-masing findSetter()dan findGetter()memerlukan Classargumen yang mengidentifikasi kelas bidang, nama bidang, dan Classobjek yang mengidentifikasi tanda tangan bidang.

The invoke()metode yang digunakan untuk mengeksekusi setter atau getter-- belakang layar, bidang misalnya diakses melalui JVM putfielddan getfieldpetunjuk. Metode ini mengharuskan referensi ke objek yang bidangnya sedang diakses diteruskan sebagai argumen awal. Untuk pemanggilan penyetel, argumen kedua, terdiri dari nilai yang ditetapkan ke bidang, juga harus diteruskan.

Saat Anda menjalankan aplikasi ini, Anda harus mengamati output berikut:

x = 15 y = 30

T: Definisi Anda tentang pegangan metode menyertakan frasa "dengan transformasi opsional dari argumen atau nilai kembalian". Bisakah Anda memberikan contoh transformasi argumen?

A: Saya telah membuat contoh berdasarkan Mathkelas ini double pow(double a, double b)metode kelas. Dalam contoh ini, saya memperoleh pegangan metode ke pow()metode, dan mengubah pegangan metode ini sehingga argumen kedua yang diteruskan ke pow()selalu 10. Lihat Daftar 4.