Fitur Bytecode tidak tersedia dalam bahasa Java

146

Adakah saat ini (Java 6) hal yang dapat Anda lakukan dalam bytecode Java yang tidak dapat Anda lakukan dari dalam bahasa Java?

Saya tahu keduanya Turing lengkap, jadi baca "dapat melakukan" karena "dapat melakukan secara signifikan lebih cepat / lebih baik, atau hanya dengan cara yang berbeda".

Saya berpikir tentang bytecodes tambahan invokedynamic, yang tidak dapat dibuat menggunakan Java, kecuali yang spesifik untuk versi masa depan.

Bart van Heukelom
sumber
3
Tentukan "hal-hal". Pada akhirnya, bahasa Jawa dan bytecode Java sama-sama menyelesaikan Turing ...
Michael Borgwardt
2
Apakah pertanyaan sebenarnya; apakah ada kelebihan pemrograman dalam kode byte misalnya menggunakan Jasmin, bukan Java?
Peter Lawrey
2
Seperti rolassembler, yang tidak bisa Anda tulis dalam C ++.
Martijn Courteaux
1
Ini adalah kompilator pengoptimal yang sangat buruk yang tidak dapat dikompilasi (x<<n)|(x>>(32-n))dengan rolinstruksi.
Random832

Jawaban:

62

Sejauh yang saya tahu tidak ada fitur utama dalam bytecodes yang didukung oleh Java 6 yang juga tidak dapat diakses dari kode sumber Java. Alasan utama untuk ini jelas bahwa bytecode Java dirancang dengan mempertimbangkan bahasa Jawa.

Namun, ada beberapa fitur yang tidak diproduksi oleh kompiler Java modern:

  • The ACC_SUPERflag :

    Ini adalah flag yang dapat diatur pada kelas dan menentukan bagaimana kasus sudut spesifik invokespecialbytecode ditangani untuk kelas ini. Ini diatur oleh semua kompiler Java modern (di mana "modern" adalah> = Java 1.1, jika saya ingat dengan benar) dan hanya kompiler Java kuno yang menghasilkan file kelas di mana ini tidak disetel. Bendera ini hanya ada untuk alasan kompatibilitas ke belakang. Perhatikan bahwa dimulai dengan Java 7u51, ACC_SUPER diabaikan sepenuhnya karena alasan keamanan.

  • The jsr/ retbytecodes.

    Bytecode ini digunakan untuk mengimplementasikan sub-rutin (kebanyakan untuk mengimplementasikan finallyblok). Mereka tidak lagi diproduksi sejak Java 6 . Alasan penghentian mereka adalah bahwa mereka banyak mempersulit verifikasi statis tanpa hasil yang besar (yaitu kode yang menggunakan hampir selalu dapat diimplementasikan kembali dengan lompatan normal dengan overhead yang sangat sedikit).

  • Memiliki dua metode dalam kelas yang hanya berbeda dalam tipe pengembalian.

    Spesifikasi bahasa Java tidak memungkinkan dua metode di kelas yang sama ketika mereka hanya berbeda dalam jenis kembali (yaitu nama yang sama, daftar argumen yang sama, ...). Namun spesifikasi JVM, tidak memiliki batasan seperti itu, sehingga file kelas dapat berisi dua metode seperti itu, tidak ada cara untuk menghasilkan file kelas seperti itu menggunakan kompiler Java normal. Ada contoh / penjelasan yang bagus dalam jawaban ini .

Joachim Sauer
sumber
5
Saya dapat menambahkan jawaban lain, tetapi kami mungkin juga menjadikan jawaban Anda sebagai jawaban kanonik. Anda mungkin ingin menyebutkan bahwa tanda tangan metode dalam bytecode termasuk tipe pengembalian . Artinya, Anda dapat memiliki dua metode dengan tipe parameter yang persis sama, tetapi tipe pengembalian yang berbeda. Lihat diskusi ini: stackoverflow.com/questions/3110014/is-this-valid-java/…
Adam Paynter
8
Anda dapat memiliki nama kelas, metode, dan bidang dengan karakter apa saja. Saya bekerja pada satu proyek di mana "ladang" memiliki ruang dan tanda hubung dalam nama mereka. : P
Peter Lawrey
3
@ Peter: Berbicara tentang karakter sistem file, saya bertemu dengan obfuscator yang telah mengubah nama kelas menjadi kelas alain ke Adalam file JAR. Butuh sekitar setengah jam unzip di mesin Windows sebelum saya menyadari di mana kelas yang hilang. :)
Adam Paynter
3
@JoachimSauer: diparafrasekan JVM spec, halaman 75: nama kelas, metode, bidang, dan variabel lokal dapat berisi setiap karakter kecuali '.', ';', '[', atau '/'. Nama metode sama, tetapi mereka juga tidak bisa mengandung '<'atau '>'. (Dengan pengecualian dari <init>dan <clinit>sebagai contoh dan konstruktor statis.) Saya harus menunjukkan bahwa jika Anda mengikuti spesifikasi secara ketat, nama-nama kelas sebenarnya jauh lebih terbatas, tetapi kendala tidak ditegakkan.
leviathanbadger
3
@ JoachimSauer: juga, tambahan saya sendiri yang tidak berdokumen: bahasa java termasuk "throws ex1, ex2, ..., exn"sebagai bagian dari tanda tangan metode; Anda tidak bisa menambahkan pengecualian melemparkan klausa ke metode yang diganti. TETAPI, JVM tidak peduli. Jadi, hanya finalmetode yang benar-benar dijamin oleh JVM sebagai pengecualian-bebas - selain dari RuntimeExceptions dan Errors, tentu saja. Begitu banyak untuk penanganan pengecualian yang diperiksa: D
leviathanbadger
401

Setelah bekerja dengan kode byte Java cukup lama dan melakukan beberapa penelitian tambahan tentang masalah ini, berikut adalah ringkasan dari temuan saya:

Jalankan kode dalam konstruktor sebelum memanggil konstruktor super atau konstruktor tambahan

Dalam bahasa pemrograman Java (JPL), pernyataan pertama konstruktor harus berupa doa dari konstruktor super atau konstruktor lain dari kelas yang sama. Ini tidak benar untuk kode byte Java (JBC). Dalam kode byte, sangat sah untuk mengeksekusi kode apa pun sebelum konstruktor, selama:

  • Konstruktor lain yang kompatibel dipanggil beberapa saat setelah blok kode ini.
  • Panggilan ini tidak dalam pernyataan bersyarat.
  • Sebelum panggilan konstruktor ini, tidak ada bidang instance yang dibangun dibaca dan tidak ada metode yang dipanggil. Ini menyiratkan item berikutnya.

Setel bidang contoh sebelum memanggil konstruktor super atau konstruktor bantu

Seperti disebutkan sebelumnya, sangat sah untuk menetapkan nilai bidang instance sebelum memanggil konstruktor lain. Bahkan ada hack warisan yang membuatnya dapat mengeksploitasi "fitur" ini dalam versi Java sebelum 6:

class Foo {
  public String s;
  public Foo() {
    System.out.println(s);
  }
}

class Bar extends Foo {
  public Bar() {
    this(s = "Hello World!");
  }
  private Bar(String helper) {
    super();
  }
}

Dengan cara ini, bidang dapat diatur sebelum konstruktor super dipanggil yang bagaimanapun tidak mungkin lagi. Di JBC, perilaku ini masih bisa diimplementasikan.

Cabang panggilan konstruktor super

Di Jawa, tidak mungkin mendefinisikan seperti panggilan konstruktor

class Foo {
  Foo() { }
  Foo(Void v) { }
}

class Bar() {
  if(System.currentTimeMillis() % 2 == 0) {
    super();
  } else {
    super(null);
  }
}

Hingga Java 7u23, verifier HotSpot VM tidak melewatkan pemeriksaan ini, itulah mengapa itu mungkin. Ini digunakan oleh beberapa alat pembuat kode sebagai semacam peretasan tetapi tidak lagi sah untuk mengimplementasikan kelas seperti ini.

Yang terakhir hanyalah bug dalam versi kompiler ini. Dalam versi kompiler yang lebih baru, ini dimungkinkan lagi.

Tentukan kelas tanpa konstruktor

Kompiler Java akan selalu menerapkan setidaknya satu konstruktor untuk kelas apa pun. Dalam kode byte Java, ini tidak diperlukan. Ini memungkinkan pembuatan kelas yang tidak dapat dibangun bahkan ketika menggunakan refleksi. Namun, menggunakan sun.misc.Unsafemasih memungkinkan untuk pembuatan instance seperti itu.

Tetapkan metode dengan tanda tangan yang identik tetapi dengan tipe pengembalian yang berbeda

Dalam JPL, metode diidentifikasi sebagai unik dengan namanya dan tipe parameter mentahnya. Di JBC, jenis pengembalian mentah juga dipertimbangkan.

Tetapkan bidang yang tidak berbeda dengan nama tetapi hanya berdasarkan jenis

File kelas dapat berisi beberapa bidang dengan nama yang sama selama mereka menyatakan jenis bidang yang berbeda. JVM selalu merujuk ke bidang sebagai tupel nama dan tipe.

Lemparkan pengecualian yang tidak dideklarasikan tanpa menangkapnya

Java runtime dan kode byte Java tidak mengetahui konsep pengecualian yang diperiksa. Hanya kompiler Java yang memverifikasi bahwa pengecualian yang diperiksa selalu ditangkap atau dideklarasikan jika dilempar.

Gunakan doa metode dinamis di luar ekspresi lambda

Apa yang disebut pemanggilan metode dinamis dapat digunakan untuk apa saja, tidak hanya untuk ekspresi lambda Java. Menggunakan fitur ini memungkinkan misalnya untuk menonaktifkan logika eksekusi saat runtime. Banyak bahasa pemrograman dinamis yang bermuara pada JBC meningkatkan kinerja mereka dengan menggunakan instruksi ini. Dalam kode byte Java, Anda juga bisa meniru ekspresi lambda di Java 7 di mana kompiler belum memungkinkan untuk penggunaan doa metode dinamis sementara JVM sudah memahami instruksi.

Gunakan pengidentifikasi yang biasanya tidak dianggap sah

Pernah membayangkan menggunakan spasi dan pemisah garis dalam nama metode Anda? Buat JBC Anda sendiri dan semoga sukses untuk review kode. Satu-satunya karakter ilegal untuk pengidentifikasi adalah ., ;, [dan /. Selain itu, metode yang tidak disebutkan namanya <init>atau <clinit>tidak dapat berisi <dan >.

Tetapkan ulang finalparameter atau thisreferensi

finalparameter tidak ada di JBC dan akibatnya dapat dipindahkan. Setiap parameter, termasuk thisreferensi hanya disimpan dalam array sederhana dalam JVM yang memungkinkan untuk menetapkan kembali thisreferensi pada indeks 0dalam kerangka metode tunggal.

Tetapkan ulang finalbidang

Selama bidang terakhir diberikan dalam konstruktor, adalah sah untuk menetapkan kembali nilai ini atau bahkan tidak memberikan nilai sama sekali. Oleh karena itu, dua konstruktor berikut ini legal:

class Foo {
  final int bar;
  Foo() { } // bar == 0
  Foo(Void v) { // bar == 2
    bar = 1;
    bar = 2;
  }
}

Untuk static finalbidang, bahkan diizinkan untuk menetapkan kembali bidang di luar penginisialisasi kelas.

Perlakukan konstruktor dan inisialisasi kelas seolah-olah mereka adalah metode

Ini lebih merupakan fitur konseptual tetapi konstruktor tidak diperlakukan secara berbeda dalam JBC daripada metode normal. Hanya verifikasi JVM yang memastikan bahwa konstruktor memanggil konstruktor hukum lain. Selain itu, itu hanya konvensi penamaan Java bahwa konstruktor harus dipanggil <init>dan bahwa initializer kelas dipanggil <clinit>. Selain perbedaan ini, representasi metode dan konstruktor identik. Seperti yang ditunjukkan Holger dalam komentar, Anda bahkan dapat mendefinisikan konstruktor dengan tipe kembali selain voidatau penginisialisasi kelas dengan argumen, meskipun tidak mungkin untuk memanggil metode ini.

Buat catatan asimetris * .

Saat membuat catatan

record Foo(Object bar) { }

javac akan menghasilkan file kelas dengan bidang tunggal bernama bar, metode accessor bernama bar()dan konstruktor mengambil satu Object. Selain itu, atribut catatan untuk barditambahkan. Dengan membuat catatan secara manual, dimungkinkan untuk membuat, bentuk konstruktor yang berbeda, untuk melewati bidang dan mengimplementasikan accessor secara berbeda. Pada saat yang sama, masih dimungkinkan untuk membuat API refleksi percaya bahwa kelas mewakili catatan aktual.

Panggil metode super apa pun (hingga Java 1.1)

Namun, ini hanya mungkin untuk Java versi 1 dan 1.1. Di JBC, metode selalu dikirim pada tipe target eksplisit. Ini berarti untuk

class Foo {
  void baz() { System.out.println("Foo"); }
}

class Bar extends Foo {
  @Override
  void baz() { System.out.println("Bar"); }
}

class Qux extends Bar {
  @Override
  void baz() { System.out.println("Qux"); }
}

itu mungkin untuk diterapkan Qux#bazuntuk memanggil Foo#bazsambil melompati Bar#baz. Meskipun masih mungkin untuk menetapkan permintaan eksplisit untuk memanggil implementasi metode super lain dari kelas super langsung, ini tidak lagi memiliki efek dalam versi Java setelah 1.1. Di Java 1.1, perilaku ini dikontrol dengan menetapkan ACC_SUPERbendera yang akan memungkinkan perilaku yang sama yang hanya memanggil implementasi kelas super langsung.

Tentukan panggilan non-virtual dari metode yang dideklarasikan di kelas yang sama

Di Jawa, tidak mungkin untuk mendefinisikan kelas

class Foo {
  void foo() {
    bar();
  }
  void bar() { }
}

class Bar extends Foo {
  @Override void bar() {
    throw new RuntimeException();
  }
}

Kode di atas akan selalu menghasilkan RuntimeExceptionsaat foodipanggil pada instance dari Bar. Tidak mungkin mendefinisikan Foo::foometode untuk memanggil metode sendiri bar yang didefinisikan dalam Foo. Seperti barmetode contoh non-pribadi, panggilan selalu virtual. Namun, dengan kode byte, seseorang dapat menentukan permohonan untuk menggunakan INVOKESPECIALopcode yang secara langsung menautkan barmetode panggilan Foo::fooke Fooversi. Opcode ini biasanya digunakan untuk menerapkan pemanggilan metode super tetapi Anda dapat menggunakan kembali opcode untuk menerapkan perilaku yang dijelaskan.

Anotasi jenis butiran halus

Di Jawa, anotasi diterapkan sesuai dengan @Targetanotasi yang dideklarasikan. Menggunakan manipulasi kode byte, dimungkinkan untuk menentukan anotasi secara independen dari kontrol ini. Juga, misalnya dimungkinkan untuk membuat anotasi tipe parameter tanpa membuat anotasi parameter bahkan jika @Targetanotasi berlaku untuk kedua elemen.

Tetapkan atribut apa pun untuk suatu tipe atau anggotanya

Di dalam bahasa Java, hanya dimungkinkan untuk mendefinisikan anotasi untuk bidang, metode atau kelas. Di JBC, Anda pada dasarnya dapat menanamkan informasi apa pun ke dalam kelas Java. Untuk memanfaatkan informasi ini, Anda tidak dapat lagi mengandalkan mekanisme pemuatan kelas Java tetapi Anda perlu mengekstrak informasi meta sendiri.

Overflow dan secara implisit menetapkan byte, short, chardan booleannilai-nilai

Tipe primitif terakhir biasanya tidak dikenal di JBC tetapi hanya didefinisikan untuk tipe array atau untuk deskriptor bidang dan metode. Dalam instruksi kode byte, semua jenis yang disebutkan mengambil ruang 32 bit yang memungkinkan untuk mewakili mereka sebagai int. Secara resmi, hanya int, float, longdan doublejenis ada dalam kode byte mana semua kebutuhan konversi eksplisit oleh aturan verifier JVM.

Tidak melepaskan monitor

Sebuah synchronizedblok sebenarnya terdiri dari dua pernyataan, satu untuk memperoleh dan satu untuk melepaskan monitor. Di JBC, Anda dapat memperoleh satu tanpa melepaskannya.

Catatan : Dalam implementasi terbaru dari HotSpot, ini malah mengarah IllegalMonitorStateExceptionpada akhir metode atau rilis implisit jika metode tersebut diakhiri oleh pengecualian itu sendiri.

Tambahkan lebih dari satu returnpernyataan ke inisialisasi tipe

Di Jawa, bahkan initializer jenis sepele seperti

class Foo {
  static {
    return;
  }
}

itu ilegal. Dalam kode byte, jenis penginisialisasi diperlakukan sama seperti metode lain, yaitu pernyataan pengembalian dapat didefinisikan di mana saja.

Buat loop tereduksi

Kompiler Java mengubah loop ke pernyataan goto dalam kode byte Java. Pernyataan seperti itu dapat digunakan untuk membuat loop tereduksi, yang tidak pernah dilakukan oleh kompiler Java.

Tentukan blok tangkapan rekursif

Dalam kode byte Java, Anda dapat mendefinisikan sebuah blok:

try {
  throw new Exception();
} catch (Exception e) {
  <goto on exception>
  throw Exception();
}

Pernyataan serupa dibuat secara implisit saat menggunakan synchronizedblok di Jawa di mana pengecualian saat melepaskan monitor kembali ke instruksi untuk melepaskan monitor ini. Biasanya, tidak ada pengecualian yang terjadi pada instruksi seperti itu tetapi jika itu akan (mis. Usang ThreadDeath), monitor masih akan dirilis.

Panggil metode default apa saja

Kompiler Java memerlukan beberapa kondisi yang harus dipenuhi untuk memungkinkan pemanggilan metode default:

  1. Metode harus yang paling spesifik (tidak boleh diganti oleh sub antarmuka yang diimplementasikan oleh jenis apa pun , termasuk tipe super).
  2. Jenis antarmuka metode default harus diimplementasikan secara langsung oleh kelas yang memanggil metode default. Namun, jika antarmuka Bmemperluas antarmuka Atetapi tidak mengesampingkan metode A, metode tersebut masih dapat dipanggil.

Untuk kode byte Java, hanya kondisi kedua yang diperhitungkan. Namun yang pertama tidak relevan.

Meminta metode super pada contoh yang tidak this

Kompiler Java hanya memungkinkan untuk memanggil metode super (atau antarmuka standar) pada contoh this. Dalam kode byte, bagaimanapun juga dimungkinkan untuk memanggil metode super pada instance dengan tipe yang sama seperti berikut ini:

class Foo {
  void m(Foo f) {
    f.super.toString(); // calls Object::toString
  }
  public String toString() {
    return "foo";
  }
}

Akses anggota sintetis

Dalam kode byte Java, dimungkinkan untuk mengakses anggota sintetis secara langsung. Misalnya, perhatikan bagaimana dalam contoh berikut ini instance luar dari Barinstance lain diakses:

class Foo {
  class Bar { 
    void bar(Bar bar) {
      Foo foo = bar.Foo.this;
    }
  }
}

Ini umumnya berlaku untuk bidang, kelas, atau metode sintetis apa pun.

Tetapkan informasi jenis umum tidak sinkron

Sementara Java runtime tidak memproses tipe generik (setelah compiler Java menerapkan tipe erasure), informasi ini masih dikaitkan dengan kelas yang dikompilasi sebagai informasi meta dan dapat diakses melalui API refleksi.

Verifier tidak memeriksa konsistensi dari Stringnilai-nilai meta data yang disandikan ini. Oleh karena itu dimungkinkan untuk mendefinisikan informasi tentang tipe generik yang tidak cocok dengan penghapusan. Sebagai rahasia, pernyataan berikut ini bisa benar:

Method method = ...
assertTrue(method.getParameterTypes() != method.getGenericParameterTypes());

Field field = ...
assertTrue(field.getFieldType() == String.class);
assertTrue(field.getGenericFieldType() == Integer.class);

Juga, tanda tangan dapat didefinisikan sebagai tidak valid sehingga pengecualian runtime dilemparkan. Pengecualian ini dilemparkan ketika informasi diakses untuk pertama kalinya karena dievaluasi dengan malas. (Mirip dengan nilai anotasi dengan kesalahan.)

Tambahkan informasi meta parameter hanya untuk metode tertentu

Kompiler Java memungkinkan untuk menyematkan nama parameter dan informasi pengubah ketika mengkompilasi kelas dengan parameterflag diaktifkan. Dalam format file kelas Java, informasi ini disimpan per-metode yang memungkinkan untuk hanya menyertakan informasi metode tersebut untuk metode tertentu.

Mengacaukan segalanya dan menghancurkan JVM Anda

Sebagai contoh, dalam kode byte Java, Anda dapat menentukan untuk memanggil metode apa pun pada jenis apa pun. Biasanya, pemverifikasi akan mengeluh jika suatu jenis tidak diketahui dari metode semacam itu. Namun, jika Anda memanggil metode yang tidak dikenal pada array, saya menemukan bug di beberapa versi JVM di mana verifier akan melewatkan ini dan JVM Anda akan selesai setelah instruksi dipanggil. Ini bukan fitur meskipun, tetapi secara teknis sesuatu yang tidak mungkin dengan javac dikompilasi Java. Java memiliki semacam validasi ganda. Validasi pertama diterapkan oleh kompiler Java, yang kedua oleh JVM ketika sebuah kelas dimuat. Dengan melewatkan kompiler, Anda mungkin menemukan titik lemah dalam validasi verifikasi. Ini lebih merupakan pernyataan umum daripada fitur.

Beri anotasi jenis penerima konstruktor ketika tidak ada kelas luar

Sejak Java 8, metode dan konstruktor non-statis kelas dalam dapat mendeklarasikan tipe penerima dan membubuhi keterangan jenis ini. Konstruktor dari kelas tingkat atas tidak dapat membubuhi keterangan jenis penerima mereka karena mereka paling tidak menyatakan satu.

class Foo {
  class Bar {
    Bar(@TypeAnnotation Foo Foo.this) { }
  }
  Foo() { } // Must not declare a receiver type
}

Sejak Foo.class.getDeclaredConstructor().getAnnotatedReceiverType()Namun tidak mengembalikan AnnotatedTypemewakili Foo, adalah mungkin untuk menyertakan anotasi tipe untuk Foo's konstruktor langsung dalam file kelas di mana penjelasan ini kemudian dibaca oleh refleksi API.

Gunakan instruksi kode byte yang tidak digunakan / lawas

Karena orang lain menamainya, saya akan memasukkannya juga. Java sebelumnya menggunakan subrutin oleh JSRdan RETpernyataan. JBC bahkan tahu jenis alamat pengirimnya sendiri untuk tujuan ini. Namun, penggunaan subrutin melakukan overcomplicate analisis kode statis yang mengapa instruksi ini tidak lagi digunakan. Sebaliknya, kompiler Java akan menduplikasi kode yang dikompilasinya. Namun, ini pada dasarnya menciptakan logika yang identik itulah sebabnya saya tidak benar-benar menganggapnya untuk mencapai sesuatu yang berbeda. Demikian pula, misalnya Anda dapat menambahkanNOOPinstruksi kode byte yang tidak digunakan oleh kompiler Java juga tetapi ini tidak akan benar-benar memungkinkan Anda untuk mencapai sesuatu yang baru juga. Seperti yang ditunjukkan dalam konteks, "petunjuk fitur" yang disebutkan ini sekarang dihapus dari himpunan opcode legal yang membuatnya lebih sedikit fitur.

Rafael Winterhalter
sumber
3
Mengenai nama metode, Anda dapat memiliki lebih dari satu <clinit>metode dengan mendefinisikan metode dengan nama <clinit>tetapi menerima parameter atau memiliki jenis yang tidak voidkembali. Tetapi metode ini tidak terlalu berguna, JVM akan mengabaikannya dan kode byte tidak dapat memohonnya. Satu-satunya penggunaan akan membingungkan pembaca.
Holger
2
Saya baru saja menemukan, bahwa Oracle JVM mendeteksi monitor yang belum pernah dirilis pada metode keluar dan melempar IllegalMonitorStateExceptionjika Anda menghilangkan monitorexitinstruksi. Dan jika ada metode keluar yang luar biasa yang gagal dilakukan monitorexit, ini akan me-reset monitor secara diam-diam.
Holger
1
@ Holger - tidak tahu itu, saya tahu bahwa ini mungkin di JVM sebelumnya setidaknya, JRockit bahkan memiliki penangan sendiri untuk implementasi semacam ini. Saya akan memperbarui entri.
Rafael Winterhalter
1
Nah, spesifikasi JVM tidak mengamanatkan perilaku seperti itu. Saya baru saja menemukannya karena saya mencoba membuat kunci intrinsik yang menggantung menggunakan kode byte non-standar tersebut.
Holger
3
Ok, saya menemukan spesifikasi yang relevan : “ Penguncian terstruktur adalah situasi ketika, selama pemanggilan metode, setiap jalan keluar pada monitor yang diberikan cocok dengan entri sebelumnya pada monitor itu. Karena tidak ada jaminan bahwa semua kode yang dikirimkan ke Java Virtual Machine akan melakukan penguncian terstruktur, implementasi Java Virtual Machine diizinkan tetapi tidak diharuskan untuk menegakkan kedua aturan berikut yang menjamin penguncian terstruktur. ... "
Holger
14

Berikut adalah beberapa fitur yang dapat dilakukan dalam bytecode Java tetapi tidak dalam kode sumber Java:

  • Melempar pengecualian yang diperiksa dari suatu metode tanpa menyatakan bahwa metode tersebut melemparkannya. Pengecualian yang dicentang dan tidak dicentang adalah hal yang hanya diperiksa oleh kompiler Java, bukan JVM. Karena itu misalnya Scala dapat membuang pengecualian yang diperiksa dari metode tanpa mendeklarasikannya. Meskipun dengan obat generik Java ada solusi yang disebut lemparan licik .

  • Memiliki dua metode dalam kelas yang hanya berbeda dalam jenis kembali, seperti yang telah disebutkan dalam jawaban Joachim : Spesifikasi bahasa Java tidak memungkinkan dua metode di kelas yang sama ketika mereka hanya berbeda dalam jenis kembali (yaitu nama yang sama, daftar argumen yang sama, ...). Namun spesifikasi JVM, tidak memiliki batasan seperti itu, sehingga file kelas dapat berisi dua metode seperti itu, tidak ada cara untuk menghasilkan file kelas seperti itu menggunakan kompiler Java normal. Ada contoh / penjelasan yang bagus dalam jawaban ini .

Esko Luontola
sumber
4
Perhatikan bahwa ada adalah cara untuk melakukan hal yang pertama di Jawa. Kadang-kadang disebut lemparan licik .
Joachim Sauer
Nah, itu licik! : D Terima kasih telah berbagi.
Esko Luontola
Saya pikir Anda juga bisa menggunakan Thread.stop(Throwable)lemparan licik. Saya menganggap yang sudah ditautkan lebih cepat.
Bart van Heukelom
2
Anda tidak dapat membuat instance tanpa memanggil konstruktor di bytecode Java. Verifier akan menolak kode apa pun yang mencoba menggunakan contoh yang tidak diinisialisasi. Implementasi deserialisasi objek menggunakan pembantu kode asli untuk membuat instance tanpa pemanggilan konstruktor.
Holger
Untuk kelas Foo yang memperpanjang Object, Anda tidak bisa membuat Instansi Foo dengan memanggil konstruktor yang dideklarasikan di Object. Verifikasi akan menolaknya. Anda dapat membuat konstruktor seperti itu menggunakan Java's ReflectionFactory tetapi ini hampir tidak merupakan fitur kode byte tetapi direalisasikan oleh Jni. Jawaban Anda salah dan Holger benar.
Rafael Winterhalter
8
  • GOTOdapat digunakan dengan label untuk membuat struktur kontrol Anda sendiri (selain for whiledll)
  • Anda bisa mengganti thisvariabel lokal di dalam suatu metode
  • Menggabungkan kedua ini Anda dapat membuat buat panggilan ekor dioptimalkan bytecode (saya melakukan ini di JCompilo )

Sebagai titik terkait Anda bisa mendapatkan nama parameter untuk metode jika dikompilasi dengan debug ( Paranamer melakukan ini dengan membaca bytecode

Daniel Worthington-Bodart
sumber
Bagaimana Anda overridevariabel lokal ini?
Michael
2
@Michael overriding adalah kata yang terlalu kuat. Pada level bytecode, semua variabel lokal diakses oleh indeks numerik dan tidak ada perbedaan antara menulis ke variabel yang ada atau menginisialisasi variabel baru (dengan cakupan yang terpisah), dalam kedua kasus itu, hanya menulis ke variabel lokal. The thisvariabel memiliki indeks nol, tapi selain menjadi pra-diinisialisasi dengan thisreferensi ketika memasuki metode contoh, itu hanya variabel lokal. Jadi, Anda dapat menulis nilai yang berbeda untuk itu, yang dapat bertindak seperti mengakhiri thislingkup atau mengubah thisvariabel, tergantung pada bagaimana Anda menggunakannya.
Holger
Saya melihat! Jadi benarkah itu yang thisbisa dipindahkan? Saya pikir itu hanya kata ganti yang membuat saya bertanya-tanya apa artinya sebenarnya.
Michael
5

Mungkin bagian 7A dalam dokumen ini menarik, meskipun ini tentang jebakan bytecode daripada fitur bytecode .

Eljenso
sumber
Menarik dibaca, tetapi sepertinya tidak ingin (ab) menggunakan hal-hal itu.
Bart van Heukelom
4

Dalam bahasa Jawa pernyataan pertama dalam konstruktor harus berupa panggilan ke konstruktor kelas super. Bytecode tidak memiliki batasan ini, sebaliknya aturannya adalah konstruktor kelas super atau konstruktor lain di kelas yang sama harus dipanggil untuk objek sebelum mengakses anggota. Ini harus memungkinkan lebih banyak kebebasan seperti:

  • Buat instance objek lain, simpan di variabel lokal (atau stack) dan berikan sebagai parameter ke konstruktor kelas super sambil tetap menyimpan referensi dalam variabel itu untuk penggunaan lain.
  • Panggil konstruktor lain yang berbeda berdasarkan kondisi. Ini harus dimungkinkan: Bagaimana memanggil konstruktor berbeda secara kondisional di Jawa?

Saya belum menguji ini, jadi tolong perbaiki saya jika saya salah.

msell
sumber
Anda bahkan dapat mengatur anggota instance sebelum memanggil konstruktor superclass-nya. Namun demikian, bidang membaca atau metode panggilan tidak mungkin dilakukan.
Rafael Winterhalter
3

Sesuatu yang dapat Anda lakukan dengan kode byte, bukan kode Java biasa, adalah menghasilkan kode yang dapat dimuat dan dijalankan tanpa kompiler. Banyak sistem memiliki JRE daripada JDK dan jika Anda ingin menghasilkan kode secara dinamis mungkin lebih baik, jika tidak lebih mudah, untuk menghasilkan kode byte daripada kode Java harus dikompilasi sebelum dapat digunakan.

Peter Lawrey
sumber
6
Tapi kemudian Anda hanya melewatkan kompiler, tidak menghasilkan sesuatu yang tidak dapat diproduksi menggunakan kompiler (jika tersedia).
Bart van Heukelom
2

Saya menulis pengoptimal bytecode ketika saya masih seorang I-Play, (itu dirancang untuk mengurangi ukuran kode untuk aplikasi J2ME). Salah satu fitur yang saya tambahkan adalah kemampuan untuk menggunakan bytecode inline (mirip dengan bahasa assembly inline di C ++). Saya berhasil mengurangi ukuran fungsi yang merupakan bagian dari metode perpustakaan dengan menggunakan instruksi DUP, karena saya membutuhkan nilai dua kali. Saya juga memiliki instruksi nol byte (jika Anda memanggil metode yang membutuhkan char dan Anda ingin melewatkan int, yang Anda tahu tidak perlu dilemparkan, saya menambahkan int2char (var) untuk mengganti char (var) dan itu akan menghapus instruksi i2c untuk mengurangi ukuran kode. Saya juga membuatnya melakukan float a = 2.3; float b = 3.4; float c = a + b; dan itu akan dikonversi ke titik tetap (lebih cepat, dan juga beberapa J2ME tidak mendukung floating point).

nharding
sumber
2

Di Jawa, jika Anda mencoba untuk menimpa metode publik dengan metode yang dilindungi (atau pengurangan akses lainnya), Anda mendapatkan kesalahan: "berusaha untuk menetapkan hak akses yang lebih lemah". Jika Anda melakukannya dengan bytecode JVM, verifikasi baik-baik saja dengan itu, dan Anda dapat memanggil metode ini melalui kelas induk seolah-olah mereka publik.

Joseph Sible-Reinstate Monica
sumber