Perbedaan dalam unboxing otomatis antara Java 6 vs Java 7

107

Saya telah mencatat perbedaan dalam perilaku unboxing otomatis antara Java SE 6 dan Java SE 7. Saya bertanya-tanya mengapa demikian, karena saya tidak dapat menemukan dokumentasi apa pun tentang perubahan perilaku ini antara kedua versi ini.

Berikut contoh sederhananya:

Object[] objs = new Object[2];
objs[0] = new Integer(5);
int myInt = (int)objs[0];

Ini mengkompilasi dengan baik dengan javac dari Java SE 7. Namun, jika saya memberikan kompilator argumen "-source 1.6" saya mendapatkan kesalahan pada baris terakhir:

inconvertible types
found   : java.lang.Object
required: int

Saya mencoba mengunduh Java SE 6 untuk dikompilasi dengan kompiler versi 6 asli (tanpa opsi -sumber). Itu setuju dan memberikan kesalahan yang sama seperti di atas.

Jadi apa yang menyebabkannya? Dari beberapa percobaan lagi terlihat bahwa unboxing di Java 6 hanya dapat membuka nilai-nilai yang jelas (pada waktu kompilasi) adalah tipe kotak. Misalnya, ini berfungsi di kedua versi:

Integer[] objs = new Integer[2];
objs[0] = new Integer(5);
int myInt = (int)objs[0];

Jadi tampaknya antara Java 6 dan 7, fitur unboxing telah ditingkatkan sehingga dapat mentransmisikan dan membuka kotak jenis objek dalam satu gerakan, tanpa mengetahui (pada waktu kompilasi) bahwa nilainya adalah jenis kotak yang sesuai. Namun, membaca Spesifikasi Bahasa Java atau posting blog yang ditulis saat Java 7 keluar, saya tidak dapat melihat perubahan apa pun dari hal ini, jadi saya bertanya-tanya apa perubahannya dan apa "fitur" ini disebut ?

Hanya keingintahuan: Karena perubahan tersebut, mungkin saja memicu unboxing yang "salah":

Object[] objs = new Float[2];
objs[0] = new Float(5);
int myInt = (int)objs[0];

Ini mengkompilasi dengan baik tetapi memberikan ClassCastException pada waktu proses.

Ada referensi tentang ini?

Morty
sumber
17
Menarik. Bahan baru untuk kekacauan autoboxing. Saya pikir contoh Anda bisa lebih sederhana dan jelas dengan satu objek daripada sebuah array. Integer obj = new Integer(2); int x = (int)obj;: bekerja pada Java 7, memberikan kesalahan pada Java 6.
leonbloy
1
JDK mana yang Anda gunakan? Mungkin juga ada hubungannya dengan vendor yang berbeda ...
barfuin
1
@leonbloy: Poin bagus tentang penyederhanaan, saya memang menyederhanakannya (dari kode asli saya) tetapi entah bagaimana berhenti terlalu dini!
Morty
@ Thomas: Itu adalah JDK terbaru (untuk setiap versi) dari Oracle yang saya gunakan.
Morty
2
Alasan lain untuk tidak pernah menggunakan autoboxing.
gyorgyabraham

Jawaban:

92

Sepertinya bahasa di bagian 5.5 Konversi Casting Java 7 JLS telah diperbarui dibandingkan dengan bagian yang sama di Java 5/6 JLS , mungkin untuk memperjelas konversi yang diizinkan.

Java 7 JLS mengatakan

Ekspresi dari jenis referensi dapat mengalami konversi casting ke jenis primitif tanpa kesalahan, dengan konversi unboxing.

Jawa 5/6:

Nilai dari jenis referensi dapat diubah menjadi jenis primitif dengan konversi unboxing (§5.1.8).

Java 7 JLS juga berisi tabel (tabel 5.1) konversi yang diizinkan (tabel ini tidak termasuk dalam Java 5/6 JLS) dari tipe referensi hingga primitif. Ini secara eksplisit mencantumkan cast dari Object ke primitif sebagai konversi referensi yang mempersempit dengan unboxing.

Alasannya dijelaskan di email ini :

Intinya: Jika spesifikasi. allow (Object) (int) itu juga harus memungkinkan (int) (Object).

Mark Rotteveel
sumber
35

Kamu benar; sederhananya:

Object o = new Integer(1234);
int x = (int) o;

Ini berfungsi di Java 7, tetapi memberikan kesalahan kompilasi di Java 6 dan yang lebih lama. Anehnya, fitur ini tidak didokumentasikan secara mencolok; misalnya, tidak disebutkan di sini . Masih bisa diperdebatkan apakah itu fitur baru atau perbaikan bug (atau bug baru?), Lihat beberapa info dan diskusi terkait . Konsensus tampaknya menunjukkan ambiguitas dalam spesifikasi asli, yang menyebabkan implementasi yang sedikit salah / tidak konsisten pada Java 5/6, yang diperbaiki pada 7, karena sangat penting untuk implementasi JSR 292 (Dynamically Typed Languages).

Autoboxing Java kini memiliki lebih banyak jebakan dan kejutan. Sebagai contoh

Object obj = new Integer(1234);
long x = (long)obj;

akan dikompilasi, tetapi gagal (dengan ClassCastException) saat runtime. Ini, sebagai gantinya, akan berfungsi:

long x = (long)(int)obj;

leonbloy.dll
sumber
2
Terima kasih atas jawabannya. Namun, ada satu hal yang tidak saya mengerti. Ini adalah klarifikasi JLS dan implementasi yang menyertainya (lih. Diskusi email), tetapi mengapa hal itu dilakukan untuk mengakomodasi bahasa yang diketik lainnya di JVM? Bagaimanapun, ini adalah perubahan pada bahasa, bukan VM: perilaku casting VM berfungsi seperti biasanya, compiler mengimplementasikan fitur ini menggunakan mekanisme yang ada untuk mentransmisikan ke Integer dan memanggil .intValue (). Jadi bagaimana ini bisa mengubah bahasa Java, membantu menjalankan bahasa lain di VM? Saya setuju tautan Anda menyarankan ini, hanya ingin tahu.
Morty