Apa yang invokedynamic dan bagaimana cara menggunakannya?

159

Saya terus mendengar tentang semua fitur keren baru yang ditambahkan ke JVM dan salah satu fitur keren itu adalah invokedynamic. Saya ingin tahu apa itu dan bagaimana membuat pemrograman reflektif di Jawa lebih mudah atau lebih baik?

David K.
sumber

Jawaban:

165

Ini adalah instruksi JVM baru yang memungkinkan kompiler untuk menghasilkan kode yang memanggil metode dengan spesifikasi yang lebih longgar daripada sebelumnya - jika Anda tahu apa itu " mengetik bebek ", invokedynamic pada dasarnya memungkinkan untuk mengetik bebek. Tidak terlalu banyak yang bisa Anda lakukan sebagai programmer Java; jika Anda seorang pembuat alat, Anda dapat menggunakannya untuk membangun bahasa berbasis JVM yang lebih fleksibel dan lebih efisien. Berikut ini adalah posting blog yang sangat manis yang memberikan banyak detail.

Ernest Friedman-Hill
sumber
3
Dalam pemrograman Java sehari-hari, tidak jarang melihat refleksi digunakan untuk memanggil metode secara dinamis meth.invoke(args). Jadi bagaimana invokedynamiccocoknya meth.invoke?
David K.
1
Posting blog yang saya sebutkan berbicara tentang MethodHandle, yang benar-benar hal yang sama tetapi dengan lebih banyak fleksibilitas. Tetapi kekuatan sebenarnya dalam semua ini bukan berasal dari bahasa Jawa, tetapi pada kemampuan JVM sendiri dalam mendukung bahasa lain yang secara intrinsik lebih dinamis.
Ernest Friedman-Hill
1
Tampaknya Java 8 menerjemahkan beberapa penggunaan lambda invokedynamicyang menjadikannya performant (dibandingkan dengan membungkusnya dalam kelas-anonim yang hampir menjadi satu-satunya pilihan sebelum memperkenalkan invokedynamic). Kemungkinan besar banyak bahasa pemrograman fungsional di atas JVM akan memilih untuk mengkompilasi ini daripada kelas-dalam-dalam.
Nader Ghanbari
2
Hanya peringatan kecil, bahwa posting blog dari 2008 sudah usang dan tidak mencerminkan keadaan rilis yang sebenarnya (2011).
Holger
9

Beberapa waktu lalu, C # menambahkan fitur keren, sintaks dinamis dalam C #

Object obj = ...; // no static type available 
dynamic duck = obj;
duck.quack(); // or any method. no compiler checking.

Anggap saja sebagai sintaks gula untuk panggilan metode reflektif. Ini dapat memiliki aplikasi yang sangat menarik. lihat http://www.infoq.com/presentations/Statically-Dynamic-Typing-Neal-Gafter

Neal Gafter, yang bertanggung jawab untuk tipe dinamis C #, baru saja membelot dari SUN ke MS. Jadi tidak masuk akal untuk berpikir bahwa hal yang sama telah dibahas di dalam SUN.

Saya ingat segera setelah itu, beberapa pria Jawa mengumumkan sesuatu yang serupa

InvokeDynamic duck = obj;
duck.quack(); 

Sayangnya, fitur ini tidak dapat ditemukan di Java 7. Sangat kecewa. Untuk programmer Java, mereka tidak memiliki cara mudah untuk memanfaatkan invokedynamicprogram mereka.

tak dapat disangkal
sumber
41
invokedynamictidak pernah dimaksudkan untuk digunakan untuk programmer Java. IMO itu tidak sesuai dengan filosofi Java sama sekali. Itu ditambahkan sebagai fitur JVM untuk bahasa non-Jawa.
Mark Peters
5
@ Mark Tidak pernah dimaksudkan oleh siapa? Ini tidak seperti ada struktur kekuatan yang jelas dalam selebriti bahasa Jawa, atau ada "niat" kolektif yang didefinisikan dengan baik. Mengenai filosofi bahasa - cukup layak, lihat Neal Gafter (pengkhianat!) Penjelasan: infoq.com/presentations/Statically-Dynamic-Typing-Neal-Gafter
irreputable
3
@mark peters: invokedynamic sebenarnya juga ditujukan untuk programmer java saja tidak dapat diakses secara langsung. Ini adalah dasar untuk penutupan Java 8.
M Platvoet
2
@irreputable: Tidak pernah dimaksudkan oleh kontributor JSR. Ini memberi tahu bahwa nama JSR adalah "Mendukung Bahasa yang Diketik Secara Dinamis di Platform Java". Java bukan bahasa yang diketik secara dinamis.
Mark Peters
5
@M Platvoet: Saya belum mengetahui tentang penutupan, tetapi tentu saja tidak akan menjadi syarat mutlak untuk penutupan. Pilihan lain yang mereka diskusikan adalah hanya membuat penutupan gula sintaksis untuk kelas dalam anonim yang dapat dilakukan tanpa perubahan spesifikasi VM. Tapi maksud saya adalah bahwa JSR tidak pernah dimaksudkan untuk membawa pengetikan dinamis ke bahasa Java, yang jelas jika Anda membaca JSR.
Mark Peters
4

Ada dua konsep untuk dipahami sebelum melanjutkan ke invokedynamic.

1. Pengetikan Statis vs. Dynamin

Static - pengecekan tipe preforms pada waktu kompilasi (misal Java)

Dynamic - pemeriksaan tipe preforms saat runtime (mis. JavaScript)

Pengecekan tipe adalah proses memverifikasi bahwa suatu program adalah tipe aman, ini adalah, memeriksa informasi yang diketik untuk variabel kelas dan instance, parameter metode, nilai kembali, dan variabel lainnya. Misalnya Java tahu tentang int, String, .. pada waktu kompilasi, sedangkan jenis objek dalam JavaScript hanya dapat ditentukan saat runtime

2. Mengetik Kuat vs Lemah

Strong - menetapkan batasan pada jenis nilai yang disediakan untuk operasinya (mis. Java)

Lemah - mengkonversi (melemparkan) argumen operasi jika argumen tersebut memiliki tipe yang tidak kompatibel (mis. Visual Basic)

Mengetahui bahwa Java adalah jenis yang Statically and Weakly typed, bagaimana Anda mengimplementasikan bahasa yang diketik secara Dinamis dan Kuat pada JVM?

Invokedynamic mengimplementasikan sistem runtime yang dapat memilih implementasi metode atau fungsi yang paling tepat - setelah program dikompilasi.

Contoh: Memiliki (a + b) dan tidak tahu apa-apa tentang variabel a, b pada waktu kompilasi, invokedynamic memetakan operasi ini ke metode yang paling tepat di Java saat runtime. Misalnya, jika ternyata a, b adalah Strings, maka panggil metode (String a, String b). Jika ternyata a, b adalah ints, maka panggil metode (int a, int b).

invokedynamic diperkenalkan dengan Java 7.

Sabina Orazem
sumber
4

Sebagai bagian dari artikel Java Records saya, saya mengartikulasikan tentang motivasi di balik Inoke Dynamic. Mari kita mulai dengan definisi kasar dari Indy.

Memperkenalkan Indy

Invoke Dynamic (Juga dikenal sebagai Indy ) adalah bagian dari JSR 292 yang bermaksud untuk meningkatkan dukungan JVM untuk Dynamic Type Languages. Setelah rilis pertama di Java 7, invokedynamicopcode beserta java.lang.invokekopernya digunakan cukup luas oleh bahasa berbasis JVM dinamis seperti JRuby.

Meskipun indy dirancang khusus untuk meningkatkan dukungan bahasa dinamis, indy menawarkan lebih dari itu. Sebenarnya, ini cocok untuk digunakan di mana pun perancang bahasa membutuhkan segala bentuk dinamika, dari akrobat tipe dinamis hingga strategi dinamis!

Misalnya, Java 8 Lambda Expressions sebenarnya diimplementasikan menggunakan invokedynamic, meskipun Java adalah bahasa yang diketik secara statis!

Bytecode yang Dapat Ditentukan Pengguna

Untuk beberapa waktu JVM mendukung empat tipe pemanggilan metode: invokestaticmemanggil metode statis, invokeinterfacememanggil metode antarmuka, invokespecialmemanggil konstruktor, super()atau metode pribadi dan invokevirtualmemanggil metode instan.

Terlepas dari perbedaan mereka, tipe doa ini memiliki satu sifat yang sama: kita tidak dapat memperkaya mereka dengan logika kita sendiri . Sebaliknya, invokedynamic memungkinkan kita untuk Bootstrap proses doa dengan cara apa pun yang kita inginkan. Kemudian JVM menangani memanggil Metode Bootstrapped secara langsung.

Bagaimana Indy Bekerja?

Pertama kali JVM melihat invokedynamicinstruksi, ia memanggil metode statis khusus yang disebut Metode Bootstrap . Metode bootstrap adalah bagian dari kode Java yang telah kami tulis untuk menyiapkan logika yang akan dipanggil:

masukkan deskripsi gambar di sini

Kemudian metode bootstrap mengembalikan instance dari java.lang.invoke.CallSite. Ini CallSitememegang referensi ke metode aktual, yaitu MethodHandle.

Mulai sekarang, setiap kali JVM melihat invokedynamicinstruksi ini lagi, ia melompati Slow Path dan secara langsung memanggil executable yang mendasarinya. JVM terus melewati jalur lambat kecuali ada perubahan.

Contoh: Java 14 Records

Java 14 Recordsmenyediakan sintaksis kompak yang bagus untuk mendeklarasikan kelas yang seharusnya menjadi pemegang data bodoh.

Mempertimbangkan catatan sederhana ini:

public record Range(int min, int max) {}

Bytecode untuk contoh ini akan menjadi sesuatu seperti:

Compiled from "Range.java"
public java.lang.String toString();
    descriptor: ()Ljava/lang/String;
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokedynamic #18,  0 // InvokeDynamic #0:toString:(LRange;)Ljava/lang/String;
         6: areturn

Dalam Tabel Metode Bootstrapnya :

BootstrapMethods:
  0: #41 REF_invokeStatic java/lang/runtime/ObjectMethods.bootstrap:
     (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;
     Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;
     Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
    Method arguments:
      #8 Range
      #48 min;max
      #50 REF_getField Range.min:I
      #51 REF_getField Range.max:I

Jadi metode bootstrap untuk Records disebut bootstrapyang berada di java.lang.runtime.ObjectMethodskelas. Seperti yang Anda lihat, metode bootstrap ini mengharapkan parameter berikut:

  • Contoh MethodHandles.Lookupmewakili konteks pencarian (Bagian Ljava/lang/invoke/MethodHandles$Lookup).
  • Nama metode (yaitu toString, equals, hashCode, dll) bootstrap yang akan untuk nge-link. Misalnya, ketika nilainya adalah toString, bootstrap akan mengembalikan a ConstantCallSite( CallSiteyang tidak pernah berubah) yang menunjuk ke toStringimplementasi aktual untuk Catatan khusus ini.
  • Untuk TypeDescriptormetode ( Ljava/lang/invoke/TypeDescriptor bagian).
  • Token tipe, yaitu Class<?>, mewakili tipe kelas Rekam. Dalam Class<Range>kasus ini.
  • Daftar yang dipisahkan semi-kolon dari semua nama komponen, yaitu min;max.
  • Satu MethodHandleper komponen. Dengan cara ini metode bootstrap dapat membuat MethodHandleberbasis pada komponen untuk implementasi metode khusus ini.

The invokedynamicinstruksi melewati semua argumen mereka dengan metode bootstrap. Metode bootstrap, pada gilirannya, mengembalikan sebuah instance dari ConstantCallSite. Ini ConstantCallSitememegang referensi untuk implementasi metode yang diminta, misalnya toString.

Mengapa indy

Berbeda dengan Reflection APIs, java.lang.invokeAPI ini cukup efisien karena JVM dapat sepenuhnya melihat semua permintaan. Karena itu, JVM dapat menerapkan segala macam optimasi selama kita menghindari jalan lambat sebanyak mungkin!

Selain argumen efisiensi, invokedynamicpendekatan ini lebih dapat diandalkan dan kurang rapuh karena kesederhanaannya .

Selain itu, bytecode yang dihasilkan untuk Java Records tidak tergantung pada jumlah properti. Jadi, bytecode lebih sedikit dan waktu startup lebih cepat.

Akhirnya, anggaplah versi baru Java menyertakan implementasi metode bootstrap baru dan lebih efisien. Dengan invokedynamic, aplikasi kami dapat memanfaatkan peningkatan ini tanpa kompilasi ulang. Dengan cara ini kita memiliki semacam Kompatibilitas Biner Teruskan . Juga, Itulah strategi dinamis yang sedang kita bicarakan!

Contoh lain

Selain Java Records, dinamis yang dipanggil telah digunakan untuk mengimplementasikan fitur-fitur seperti:

Ali Dehghani
sumber