Kapan antarmuka dengan metode default diinisialisasi?

94

Sementara mencari melalui Java Language Specification untuk menjawab pertanyaan ini , saya belajar bahwa

Sebelum kelas diinisialisasi, superclass langsungnya harus diinisialisasi, tetapi antarmuka yang diimplementasikan oleh kelas tersebut tidak diinisialisasi. Demikian pula, superinterfaces dari sebuah antarmuka tidak diinisialisasi sebelum antarmuka diinisialisasi.

Untuk rasa ingin tahu saya sendiri, saya mencobanya dan, seperti yang diharapkan, antarmuka InterfaceTypetidak diinisialisasi.

public class Example {
    public static void main(String[] args) throws Exception {
        InterfaceType foo = new InterfaceTypeImpl();
        foo.method();
    }
}

class InterfaceTypeImpl implements InterfaceType {
    @Override
    public void method() {
        System.out.println("implemented method");
    }
}

class ClassInitializer {
    static {
        System.out.println("static initializer");
    }
}

interface InterfaceType {
    public static final ClassInitializer init = new ClassInitializer();

    public void method();
}

Program ini mencetak

implemented method

Namun, jika antarmuka mendeklarasikan defaultmetode, maka inisialisasi memang terjadi. Pertimbangkan InterfaceTypeantarmuka yang diberikan sebagai

interface InterfaceType {
    public static final ClassInitializer init = new ClassInitializer();

    public default void method() {
        System.out.println("default method");
    }
}

maka program yang sama di atas akan dicetak

static initializer  
implemented method

Dengan kata lain, staticbidang antarmuka diinisialisasi ( langkah 9 dalam Prosedur Inisialisasi Terperinci ) dan staticpenginisialisasi jenis yang diinisialisasi dijalankan. Ini berarti antarmuka telah diinisialisasi.

Saya tidak dapat menemukan apa pun di JLS yang menunjukkan bahwa ini harus terjadi. Jangan salah paham, saya mengerti bahwa hal ini harus terjadi jika kelas pelaksana tidak menyediakan implementasi untuk metode tersebut, tetapi bagaimana jika demikian? Apakah kondisi ini hilang dari Spesifikasi Bahasa Java, apakah saya melewatkan sesuatu, atau saya salah menafsirkannya?

Sotirios Delimanolis
sumber
4
Dugaan saya adalah - antarmuka seperti itu dianggap kelas abstrak dalam hal urutan inisialisasi. Saya menulis ini sebagai komentar karena saya tidak yakin apakah pernyataan ini benar :)
Alexey Malev
Ini harus ada di bagian 12.4 JLS, tetapi tampaknya tidak ada. Saya akan mengatakan itu hilang.
Warren Dew
1
Nevermind .... sebagian besar waktu ketika mereka tidak mengerti atau tidak memiliki penjelasan mereka akan downvote :(. Hal ini terjadi pada SO secara umum.
NeverGiveUp161
Saya pikir interfacedi Jawa tidak harus mendefinisikan metode konkret. Jadi saya terkejut bahwa InterfaceTypekode telah terkompilasi.
MaxZoom
@MaxZoom Java 8 memungkinkan defaultmetode .
Sotirios Delimanolis

Jawaban:

85

Ini adalah masalah yang sangat menarik!

Sepertinya JLS bagian 12.4.1 harus membahas ini secara definitif. Namun, perilaku Oracle JDK dan OpenJDK (javac dan HotSpot) berbeda dari yang ditentukan di sini. Secara khusus, Contoh 12.4.1-3 dari bagian ini mencakup inisialisasi antarmuka. Contohnya sebagai berikut:

interface I {
    int i = 1, ii = Test.out("ii", 2);
}
interface J extends I {
    int j = Test.out("j", 3), jj = Test.out("jj", 4);
}
interface K extends J {
    int k = Test.out("k", 5);
}
class Test {
    public static void main(String[] args) {
        System.out.println(J.i);
        System.out.println(K.j);
    }
    static int out(String s, int i) {
        System.out.println(s + "=" + i);
        return i;
    }
}

Output yang diharapkan adalah:

1
j=3
jj=4
3

dan memang saya mendapatkan hasil yang diharapkan. Namun, jika metode default ditambahkan ke antarmuka I,

interface I {
    int i = 1, ii = Test.out("ii", 2);
    default void method() { } // causes initialization!
}

output berubah menjadi:

1
ii=2
j=3
jj=4
3

yang dengan jelas menunjukkan antarmuka itu I sedang diinisialisasi di tempat yang tidak sebelumnya! Kehadiran metode default saja sudah cukup untuk memicu inisialisasi. Metode default tidak harus dipanggil atau diganti atau bahkan disebutkan, juga tidak adanya metode abstrak yang memicu inisialisasi.

Spekulasi saya adalah bahwa implementasi HotSpot ingin menghindari penambahan kelas / antarmuka inisialisasi memeriksa ke jalur kritis invokevirtualpanggilan. Sebelum Java 8 dan metode default, invokevirtualtidak akan pernah bisa mengeksekusi kode di antarmuka, jadi ini tidak muncul. Orang mungkin mengira ini adalah bagian dari tahap persiapan kelas / antarmuka ( JLS 12.3.2 ) yang menginisialisasi hal-hal seperti tabel metode. Tapi mungkin ini berjalan terlalu jauh dan secara tidak sengaja melakukan inisialisasi penuh.

Saya telah mengangkat pertanyaan ini di milis compiler-dev OpenJDK. Ada balasan dari Alex Buckley (editor JLS) di mana dia mengajukan lebih banyak pertanyaan yang ditujukan pada tim implementasi JVM dan lambda. Dia juga mencatat bahwa ada bug dalam spesifikasi di sini di mana dikatakan "T adalah kelas dan metode statis yang dideklarasikan oleh T dipanggil" juga harus diterapkan jika T adalah antarmuka. Jadi, mungkin saja ada bug spesifikasi dan HotSpot di sini.

Pengungkapan : Saya bekerja untuk Oracle di OpenJDK. Jika orang berpikir ini memberi saya keuntungan yang tidak adil karena mendapatkan hadiah yang melekat pada pertanyaan ini, saya bersedia bersikap fleksibel tentang itu.

Stuart Marks
sumber
6
Saya meminta sumber resmi. Saya tidak berpikir itu menjadi lebih resmi dari ini. Beri waktu dua hari untuk melihat semua perkembangan.
Sotirios Delimanolis
48
@StuartMarks " Jika orang berpikir ini memberi saya keuntungan yang tidak adil, dll " => kami di sini untuk mendapatkan jawaban atas pertanyaan dan ini adalah jawaban yang sempurna!
assylias
2
Catatan tambahan: Spesifikasi JVM berisi deskripsi yang mirip dengan JLS: docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.5 Ini juga harus diperbarui .
Marco13
2
@assylias dan Sotirios, terima kasih atas komentarnya. Mereka, bersama dengan 14 suara positif (saat tulisan ini dibuat) pada komentar assylias, telah mengurangi kekhawatiran saya tentang potensi ketidakadilan.
Stuart Marks
1
@SotiriosDelimanolis Ada beberapa bug yang tampaknya relevan, JDK-8043275 dan JDK-8043190 , dan telah ditandai sebagai diperbaiki di 8u40. Namun, perilakunya tampaknya sama. Ada juga beberapa perubahan Spesifikasi JVM yang terkait dengan ini, jadi mungkin perbaikannya adalah sesuatu selain "kembalikan urutan inisialisasi yang lama."
Stuart Marks
13

Antarmuka tidak diinisialisasi karena bidang konstanta InterfaceType.init, yang diinisialisasi oleh nilai non konstan (pemanggilan metode), tidak digunakan di mana pun.

Diketahui pada waktu kompilasi bahwa bidang antarmuka konstan tidak digunakan di mana pun, dan antarmuka tidak berisi metode default apa pun (Dalam java-8) sehingga tidak perlu menginisialisasi atau memuat antarmuka.

Antarmuka akan diinisialisasi dalam kasus berikut,

  • bidang konstan digunakan dalam kode Anda.
  • Antarmuka berisi metode default (Java 8)

Dalam kasus Metode Default , Anda sedang menerapkan InterfaceType. Jadi, Jika InterfaceTypeakan berisi metode default apa pun, Ini akan DIBERI (digunakan) dalam mengimplementasikan kelas. Dan Inisialisasi akan menjadi gambar.

Tetapi, Jika Anda mengakses bidang antarmuka yang konstan (yang diinisialisasi dengan cara biasa), Inisialisasi antarmuka tidak diperlukan.

Pertimbangkan kode berikut.

public class Example {
    public static void main(String[] args) throws Exception {
        InterfaceType foo = new InterfaceTypeImpl();
        System.out.println(InterfaceType.init);
        foo.method();
    }
}

class InterfaceTypeImpl implements InterfaceType {
    @Override
    public void method() {
        System.out.println("implemented method");
    }
}

class ClassInitializer {
    static {
        System.out.println("static initializer");
    }
}

interface InterfaceType {
    public static final ClassInitializer init = new ClassInitializer();

    public void method();
}

Dalam kasus di atas, Antarmuka akan diinisialisasi dan dimuat karena Anda menggunakan bidang InterfaceType.init .

Saya tidak memberikan contoh metode default seperti yang sudah Anda berikan dalam pertanyaan Anda.

Spesifikasi dan contoh bahasa Java diberikan di JLS 12.4.1 (Contoh tidak berisi metode default.)


Saya tidak dapat menemukan JLS untuk metode Default, mungkin ada dua kemungkinan

  • Orang jawa lupa untuk mempertimbangkan kasus metode default. (Spesifikasi Bug dokumen.)
  • Mereka hanya merujuk metode default sebagai anggota antarmuka yang tidak konstan. (Tapi tidak disebutkan di mana, lagi-lagi Bug Spesifikasi Doc.)
Bukan serangga
sumber
Saya mencari referensi untuk metode default. Bidang itu hanya untuk menunjukkan bahwa antarmuka itu diinisialisasi atau tidak.
Sotirios Delimanolis
@SotiriosDelimanolis Saya menyebutkan alasan jawaban untuk metode default ... tapi sayangnya JLS belum ditemukan untuk metode default.
Bukan bug
Sayangnya, itulah yang saya cari. Saya merasa jawaban Anda hanya mengulangi hal-hal yang telah saya nyatakan dalam pertanyaan, yaitu. bahwa antarmuka akan diinisialisasi jika berisi defaultmetode dan kelas yang mengimplementasikan antarmuka diinisialisasi.
Sotirios Delimanolis
Saya pikir orang java lupa untuk mempertimbangkan kasus metode default, Atau mereka hanya merujuk metode default sebagai anggota antarmuka non-konstan (asumsi saya, tidak dapat menemukan di dokumen manapun).
Bukan bug
1
@KishanSarsechaGajjar: Apa yang Anda maksud dengan bidang non konstan dalam antarmuka? Variabel / bidang apa pun dalam antarmuka adalah final statis secara default.
Lokesh
10

File instanceKlass.cpp dari OpenJDK berisi metode inisialisasi InstanceKlass::initialize_implyang sesuai dengan Prosedur Inisialisasi Terperinci di JLS, yang secara analog ditemukan di Inisialisasi di Spesifikasi JVM.

Ini berisi langkah baru yang tidak disebutkan di JLS dan bukan di buku JVM yang dirujuk dalam kode:

// refer to the JVM book page 47 for description of steps
...

if (this_oop->has_default_methods()) {
  // Step 7.5: initialize any interfaces which have default methods
  for (int i = 0; i < this_oop->local_interfaces()->length(); ++i) {
    Klass* iface = this_oop->local_interfaces()->at(i);
    InstanceKlass* ik = InstanceKlass::cast(iface);
    if (ik->has_default_methods() && ik->should_be_initialized()) {
      ik->initialize(THREAD);
    ....
    }
  }
}

Jadi inisialisasi ini telah diterapkan secara eksplisit sebagai Langkah 7.5 baru . Ini menunjukkan bahwa implementasi ini mengikuti beberapa spesifikasi, tetapi tampaknya spesifikasi tertulis di situs web belum diperbarui sebagaimana mestinya.

EDIT: Sebagai referensi, komit (mulai Oktober 2012!) Di mana langkah terkait telah disertakan dalam implementasi: http://hg.openjdk.java.net/jdk8/build/hotspot/rev/4735d2c84362

EDIT2: Secara kebetulan, saya menemukan Dokumen ini tentang metode default di hotspot yang berisi catatan tambahan yang menarik di bagian akhir:

3.7 Lain-lain

Karena antarmuka sekarang memiliki bytecode di dalamnya, kita harus menginisialisasi mereka pada saat kelas pelaksana diinisialisasi.

Marco13
sumber
1
Terima kasih telah menggali ini. (+1) Mungkin saja "langkah 7.5" yang baru secara tidak sengaja dihilangkan dari spesifikasi, atau telah diusulkan dan ditolak dan penerapannya tidak pernah diperbaiki untuk menghapusnya.
Stuart Marks
1

Saya akan mencoba untuk membuat kasus bahwa inisialisasi antarmuka tidak boleh menyebabkan efek samping saluran samping yang bergantung pada subtipe, oleh karena itu, apakah ini bug atau bukan, atau dengan cara apa pun Java memperbaikinya, itu tidak masalah. aplikasi tempat antarmuka pesanan diinisialisasi.

Dalam kasus a class, diterima dengan baik bahwa hal itu dapat menyebabkan efek samping yang bergantung pada subkelas. Sebagai contoh

class Foo{
    static{
        Bank.deposit($1000);
...

Setiap subclass dari Fooakan berharap bahwa mereka akan melihat $ 1000 di bank, dimanapun dalam kode subclass. Oleh karena itu superclass diinisialisasi sebelum subclass tersebut.

Bukankah kita harus melakukan hal yang sama untuk superintefaces juga? Sayangnya, urutan superinterfaces tidak dianggap signifikan, oleh karena itu tidak ada urutan yang jelas untuk menginisialisasi mereka.

Jadi sebaiknya kita tidak menetapkan efek samping semacam ini dalam inisialisasi antarmuka. Bagaimanapun, interfaceini tidak dimaksudkan untuk fitur-fitur ini (bidang / metode statis) yang kami kumpulkan untuk kenyamanan.

Oleh karena itu, jika kita mengikuti prinsip itu, tidak akan menjadi masalah bagi kita di mana antarmuka pesanan diinisialisasi.

ZhongYu
sumber