Operator terner Java vs if / else dalam kompatibilitas <JDK8

113

Baru-baru ini saya membaca kode sumber Spring Framework. Sesuatu yang tidak saya mengerti diletakkan di sini:

public Member getMember() {
    // NOTE: no ternary expression to retain JDK <8 compatibility even when using
    // the JDK 8 compiler (potentially selecting java.lang.reflect.Executable
    // as common type, with that new base class not available on older JDKs)
    if (this.method != null) {
        return this.method;
    }
    else {
        return this.constructor;
    }
}

Metode ini adalah anggota kelas org.springframework.core.MethodParameter. Kode mudah dimengerti sementara komentarnya sulit.

CATATAN: tidak ada ekspresi terner untuk mempertahankan kompatibilitas JDK <8 bahkan ketika menggunakan kompiler JDK 8 (berpotensi memilih java.lang.reflect.Executablesebagai tipe umum, dengan kelas dasar baru itu tidak tersedia pada JDK lama)

Apa perbedaan antara menggunakan ekspresi terner dan menggunakan if...else...konstruk dalam konteks ini?

jddxf
sumber

Jawaban:

103

Saat Anda memikirkan tentang jenis operan, masalahnya menjadi lebih jelas:

this.method != null ? this.method : this.constructor

memiliki tipe umum yang paling terspesialisasi dari kedua operan, yaitu tipe paling terspesialisasi yang umum untuk keduanya this.methoddan this.constructor.

Di Java 7 ini java.lang.reflect.Member, namun pustaka kelas Java 8 memperkenalkan tipe baru java.lang.reflect.Executableyang lebih terspesialisasi daripada generik Member. Oleh karena itu dengan pustaka kelas Java 8 tipe hasil dari ekspresi terner adalah Executablebukan Member.

Beberapa versi (pra-rilis) dari kompilator Java 8 tampaknya telah menghasilkan referensi eksplisit ke Executabledalam kode yang dihasilkan saat mengompilasi operator terner. Ini akan memicu pemuatan kelas, dan karenanya pada gilirannya akan menjadi ClassNotFoundExceptionsaat runtime saat dijalankan dengan pustaka kelas <JDK 8, karena Executablehanya ada untuk JDK ≥ 8.

Seperti dicatat oleh Tagir Valeev dalam jawaban ini , ini sebenarnya adalah bug dalam versi pra-rilis JDK 8 dan telah diperbaiki, sehingga if-elsesolusi dan komentar penjelasannya sekarang sudah usang.

Catatan tambahan: Seseorang mungkin sampai pada kesimpulan bahwa bug kompilator ini ada sebelum Java 8. Namun, kode byte yang dihasilkan untuk terner oleh OpenJDK 7 sama dengan kode byte yang dihasilkan oleh OpenJDK 8. Sebenarnya, tipe dari ekspresi benar-benar tidak disebutkan pada saat runtime, kode tersebut benar-benar hanya test, branch, load, return tanpa pemeriksaan tambahan yang terjadi. Jadi yakinlah bahwa ini bukan masalah (lagi) dan memang sepertinya menjadi masalah sementara selama pengembangan Java 8.

dhke
sumber
1
Lalu bagaimana kode dapat dikompilasi dengan JDK 1.8 berjalan di JDK 1.7. Saya tahu bahwa kode yang dikompilasi dengan JDK versi lebih rendah dapat berjalan di JDK versi lebih tinggi tanpa masalah.
jddxf
1
@jddxf Semuanya baik-baik saja selama Anda menetapkan versi file kelas yang tepat dan tidak menggunakan fungsionalitas apa pun yang tidak tersedia di versi yang lebih baru. Masalah pasti akan terjadi, namun jika penggunaan tersebut terjadi secara implisit seperti dalam kasus ini.
dhke
13
@jddxf, gunakan opsi -source / -target javac
Tagir Valeev
1
Terima kasih semuanya, terutama dhke dan Tagir Valeev yang telah memberikan penjelasan yang lengkap
jddxf
30

Ini diperkenalkan di commit yang cukup lama pada tanggal 3 Mei 2013, hampir setahun sebelum rilis resmi JDK-8. Kompiler sedang dalam pengembangan berat pada waktu itu, sehingga masalah kompatibilitas dapat terjadi. Saya kira, tim Spring baru saja menguji build JDK-8 dan mencoba memperbaiki masalah, meskipun sebenarnya itu adalah masalah compiler. Dengan rilis resmi JDK-8 ini menjadi tidak relevan. Sekarang operator terner dalam kode ini berfungsi dengan baik seperti yang diharapkan (tidak ada referensi ke Executablekelas dalam file .class yang dikompilasi).

Saat ini hal serupa muncul di JDK-9: beberapa kode yang dapat dikompilasi dengan baik di JDK-8 gagal dengan JDK-9 javac. Saya kira, sebagian besar masalah seperti itu akan diperbaiki hingga rilis.

Tagir Valeev
sumber
2
+1. Jadi, apakah ini bug di kompiler awal? Apakah perilaku itu, sebagaimana dimaksud Executable, melanggar beberapa aspek spesifikasi? Atau apakah hanya karena Oracle menyadari bahwa mereka dapat mengubah perilaku ini dengan cara yang tetap sesuai dengan spesifikasi dan tanpa merusak kompatibilitas ke belakang?
ruakh
2
@ruakh, saya kira itu bugnya. Dalam bytecode (baik di Java-8 atau sebelumnya), sama sekali tidak perlu mentransmisikan secara eksplisit untuk Executablemengetik di antaranya. Di Java-8, konsep inferensi tipe ekspresi berubah secara drastis dan bagian ini telah sepenuhnya ditulis ulang, sehingga tidak mengherankan jika implementasi awal memiliki bug.
Tagir Valeev
7

Perbedaan utama adalah bahwa if elseblok adalah pernyataan sedangkan terner (lebih sering dikenal sebagai operator bersyarat di Jawa) adalah ekspresi .

Sebuah pernyataan dapat melakukan hal-hal seperti returnke pemanggil pada beberapa jalur kontrol. Sebuah ekspresi dapat digunakan dalam tugas:

int n = condition ? 3 : 2;

Jadi dua ekspresi di terner setelah kondisi perlu dipaksakan ke tipe yang sama. Hal ini dapat menyebabkan beberapa efek aneh di Java terutama dengan auto-boxing dan transmisi referensi otomatis - inilah yang dirujuk oleh komentar dalam kode yang Anda posting. Pemaksaan ekspresi dalam kasus Anda akan menjadi java.lang.reflect.Executabletipe (karena itu adalah tipe yang paling terspesialisasi ) dan itu tidak ada di versi Java yang lebih lama.

Secara gaya Anda harus menggunakan if elseblok jika kodenya seperti pernyataan, dan terner jika seperti ekspresi.

Tentu saja, Anda dapat membuat if elseblok berperilaku seperti ekspresi jika Anda menggunakan fungsi lambda.

Batsyeba
sumber
6

Jenis nilai yang dikembalikan dalam ekspresi terner dipengaruhi oleh kelas induk, yang diubah seperti yang dijelaskan di Java 8.

Sulit untuk melihat mengapa pemeran tidak bisa ditulis.

Marquis dari Lorne
sumber