Java8: Mengapa dilarang mendefinisikan metode default untuk metode dari java.lang.Object

130

Metode default adalah alat baru yang bagus di kotak alat Java kami. Namun, saya mencoba menulis antarmuka yang mendefinisikan defaultversi toStringmetode ini. Java memberi tahu saya bahwa ini dilarang, karena metode yang dideklarasikan di java.lang.Objectmungkin tidak defaultdiedit. Mengapa demikian?

Saya tahu bahwa ada aturan "kelas dasar selalu menang", jadi secara default (pun;), setiap defaultimplementasi Objectmetode akan ditimpa oleh metode dari Objectpokoknya. Namun, saya tidak melihat alasan mengapa seharusnya tidak ada pengecualian untuk metode dari Objectdalam spesifikasi. Khusus untuk toStringitu mungkin sangat berguna untuk memiliki implementasi standar.

Jadi, apa alasan mengapa desainer Java memutuskan untuk tidak mengizinkan defaultmetode yang menimpa metode Object?

gexicide
sumber
1
Saya merasa sangat baik tentang diri saya sekarang, menaikkan ini untuk 100 kali dan dengan demikian lencana emas. Pertanyaan bagus!
Eugene

Jawaban:

186

Ini adalah satu lagi masalah desain bahasa yang tampaknya "jelas merupakan ide yang bagus" sampai Anda mulai menggali dan Anda menyadari bahwa itu sebenarnya ide yang buruk.

Email ini memiliki banyak hal tentang subjek (dan juga subjek lainnya.) Ada beberapa kekuatan desain yang berkumpul untuk membawa kami ke desain saat ini:

  • Keinginan untuk menjaga model warisan tetap sederhana;
  • Fakta bahwa setelah Anda melihat contoh-contoh yang jelas (misalnya, berubah AbstractListmenjadi antarmuka), Anda menyadari bahwa mewarisi sama dengan / kode hash / toString sangat terkait dengan warisan tunggal dan negara, dan antarmuka multipel warisan dan stateless;
  • Bahwa itu berpotensi membuka pintu ke beberapa perilaku mengejutkan.

Anda telah menyentuh tujuan "tetap sederhana"; aturan pewarisan dan resolusi konflik dirancang untuk menjadi sangat sederhana (kelas menang atas antarmuka, antarmuka turunan menang atas superinterfaces, dan konflik lainnya diselesaikan oleh kelas pelaksana.) Tentu saja aturan ini dapat diubah untuk membuat pengecualian, tetapi Saya pikir Anda akan menemukan ketika Anda mulai menarik tali itu, bahwa kompleksitas tambahannya tidak sekecil yang Anda kira.

Tentu saja, ada beberapa tingkat manfaat yang akan membenarkan lebih banyak kerumitan, tetapi dalam kasus ini tidak ada. Metode yang kita bicarakan di sini adalah sama, kode hash, dan toString. Semua metode ini secara intrinsik tentang status objek, dan kelaslah yang memiliki status, bukan antarmuka, yang berada dalam posisi terbaik untuk menentukan apa arti kesetaraan untuk kelas itu (terutama karena kontrak untuk kesetaraan cukup kuat; lihat Efektif Java untuk beberapa konsekuensi yang mengejutkan); penulis antarmuka terlalu jauh dihapus.

Sangat mudah untuk menarik AbstractListcontohnya; alangkah baiknya jika kita bisa menyingkirkan AbstractListdan menempatkan perilaku ke Listantarmuka. Tetapi begitu Anda melangkah melampaui contoh nyata ini, tidak ada banyak contoh bagus lainnya yang dapat ditemukan. Pada dasarnya, AbstractListdirancang untuk pewarisan tunggal. Tetapi interface harus dirancang untuk multiple inheritance.

Selanjutnya, bayangkan Anda menulis kelas ini:

class Foo implements com.libraryA.Bar, com.libraryB.Moo { 
    // Implementation of Foo, that does NOT override equals
}

The Foopenulis terlihat di supertypes, tidak melihat pelaksanaan equals, dan menyimpulkan bahwa untuk mendapatkan referensi kesetaraan, semua yang dia perlu lakukan adalah sama dengan mewarisi dari Object. Kemudian, minggu depan, pengelola perpustakaan untuk Bar "membantu" menambahkan equalsimplementasi default . Ups! Sekarang semantik Footelah dipecah oleh sebuah antarmuka di domain pemeliharaan lain "sangat membantu" menambahkan default untuk metode umum.

Default seharusnya adalah default. Menambahkan default ke antarmuka di mana tidak ada (di mana pun dalam hierarki) seharusnya tidak memengaruhi semantik kelas implementasi konkret. Tetapi jika default bisa "menimpa" metode objek, itu tidak benar.

Jadi, walaupun tampak seperti fitur yang tidak berbahaya, sebenarnya cukup berbahaya: ia menambahkan banyak kerumitan untuk sedikit ekspresif tambahan, dan itu membuatnya terlalu mudah untuk perubahan yang bermaksud baik, tampak tidak berbahaya ke antarmuka yang dikompilasi secara terpisah untuk melemahkan semantik yang dimaksudkan dari kelas implementasi.

Brian Goetz
sumber
13
Saya senang Anda meluangkan waktu untuk menjelaskan hal ini, dan saya menghargai semua faktor yang dipertimbangkan. Saya setuju bahwa ini akan berbahaya untuk hashCodedan equals, tetapi saya pikir ini akan sangat berguna toString. Sebagai contoh, beberapa Displayableantarmuka mungkin mendefinisikan sebuah String display()metode, dan itu akan menghemat satu ton boilerplate untuk dapat menentukan default String toString() { return display(); }di Displayable, bukan membutuhkan setiap satu Displayableuntuk melaksanakan toString()atau memperpanjang DisplayableToStringkelas dasar.
Brandon
8
@Brandon Anda benar bahwa mengizinkan mewarisi toString () tidak akan berbahaya dengan cara yang sama dengan equals () dan hashCode (). Di sisi lain, sekarang fitur akan menjadi lebih tidak teratur - dan Anda masih akan mengalami semua kompleksitas tambahan yang sama tentang aturan warisan, demi metode yang satu ini ... tampaknya lebih baik untuk menarik garis dengan rapi di mana kami melakukan .
Brian Goetz
5
@ geksisida Jika toString()hanya didasarkan pada metode antarmuka, Anda bisa menambahkan sesuatu seperti default String toStringImpl()antarmuka, dan menimpa toString()di setiap subkelas untuk memanggil implementasi antarmuka - sedikit jelek, tetapi berfungsi, dan lebih baik daripada tidak sama sekali. :) Cara lain untuk melakukannya adalah membuat sesuatu seperti Objects.hash(), Arrays.deepEquals()dan Arrays.deepToString(). Memberi +1 pada jawaban @ BrianGoetz!
Siu Ching Pong -Asuka Kenji-
3
Perilaku default toString lambda () benar-benar menjengkelkan. Saya tahu pabrik lambda dirancang untuk menjadi sangat sederhana dan cepat, tetapi meludah nama kelasnya benar-benar tidak membantu. Memiliki default toString()override dalam antarmuka fungsional akan memungkinkan kita untuk - setidaknya - melakukan sesuatu seperti memuntahkan tanda tangan fungsi dan kelas induk dari implementer. Lebih baik lagi, jika kita bisa membawa beberapa strategi pengulangan ke String untuk ditanggung kita bisa berjalan melalui penutupan untuk mendapatkan deskripsi yang sangat baik tentang lambda, dan dengan demikian meningkatkan kurva belajar lambda secara drastis.
Groostav
Perubahan apa pun ke toString di kelas, subclass, anggota contoh apa pun dapat memiliki efek pada penerapan kelas atau pengguna kelas. Selain itu, setiap perubahan pada salah satu metode default juga akan mempengaruhi semua kelas implementasi. Jadi apa yang istimewa dengan toString, kode hash ketika berbicara tentang seseorang yang mengubah perilaku antarmuka? Jika suatu kelas memperluas kelas lain, mereka dapat berubah daripada juga. Atau jika mereka menggunakan pola delegasi. Seseorang yang menggunakan antarmuka Java 8 harus melakukannya dengan memutakhirkan. Peringatan / kesalahan yang dapat ditekan pada subkelas bisa saja diberikan.
mmm
30

Dilarang mendefinisikan metode default di antarmuka untuk metode dalam java.lang.Object, karena metode default tidak akan pernah "terjangkau".

Metode antarmuka default dapat ditimpa dalam kelas yang mengimplementasikan antarmuka dan implementasi kelas metode memiliki prioritas lebih tinggi daripada implementasi antarmuka, bahkan jika metode ini diterapkan dalam superclass. Karena semua kelas mewarisi java.lang.Object, metode di java.lang.Objectakan lebih diutamakan daripada metode default di antarmuka dan dipanggil sebagai gantinya.

Brian Goetz dari Oracle memberikan beberapa rincian lebih lanjut tentang keputusan desain di pos milis ini .

jarnbjo
sumber
3

Saya tidak melihat ke kepala penulis bahasa Jawa, jadi kami mungkin hanya menebak. Tetapi saya melihat banyak alasan dan sepenuhnya setuju dengan mereka dalam masalah ini.

Alasan utama untuk memperkenalkan metode default adalah untuk dapat menambahkan metode baru ke antarmuka tanpa melanggar kompatibilitas mundur dari implementasi yang lebih lama. Metode default juga dapat digunakan untuk menyediakan metode "kenyamanan" tanpa keharusan untuk mendefinisikannya di setiap kelas implementasi.

Tak satu pun dari ini berlaku untuk toString dan metode Object lainnya. Sederhananya, metode default dirancang untuk memberikan perilaku default di mana tidak ada definisi lain. Tidak menyediakan implementasi yang akan "bersaing" dengan implementasi lain yang ada.

Aturan "kelas dasar selalu menang" memiliki alasan kuat juga. Seharusnya kelas mendefinisikan implementasi nyata , sementara antarmuka mendefinisikan implementasi standar , yang agak lemah.

Juga, memperkenalkan pengecualian APAPUN pada aturan umum menyebabkan kompleksitas yang tidak perlu dan mengajukan pertanyaan lain. Obyek adalah (kurang lebih) kelas seperti yang lain, jadi mengapa harus memiliki perilaku yang berbeda?

Semua dan semua, solusi yang Anda usulkan mungkin akan membawa lebih banyak kontra daripada pro.

Marwin
sumber
Saya tidak melihat paragraf kedua dari jawaban gexicide ketika memposting milik saya Ini berisi tautan yang menjelaskan masalah ini secara lebih rinci.
Marwin
1

Alasannya sangat sederhana, itu karena Object adalah kelas dasar untuk semua kelas Java. Jadi, bahkan jika kita memiliki metode Object didefinisikan sebagai metode default di beberapa antarmuka, itu akan sia-sia karena metode Object akan selalu digunakan. Itulah sebabnya untuk menghindari kebingungan, kita tidak dapat memiliki metode default yang mengesampingkan metode kelas Objek.

Kumar Abhishek
sumber
1

Untuk memberikan jawaban yang sangat bagus, hanya dilarang mendefinisikan defaultmetode untuk metode publikjava.lang.Object . Ada 11 metode untuk dipertimbangkan, yang dapat dikategorikan dalam tiga cara untuk menjawab pertanyaan ini.

  1. Enam dari Objectmetode tidak dapat memiliki defaultmetode karena mereka finaldan tidak dapat ditimpa sama sekali: getClass(), notify(), notifyAll(), wait(), wait(long), dan wait(long, int).
  2. Tiga dari Objectmetode tidak dapat memiliki defaultmetode untuk alasan yang diberikan di atas oleh Brian Goetz: equals(Object), hashCode(), dan toString().
  3. Dua Objectmetode dapat memiliki defaultmetode, meskipun nilai default seperti itu dipertanyakan di terbaik: clone()dan finalize().

    public class Main {
        public static void main(String... args) {
            new FOO().clone();
            new FOO().finalize();
        }
    
        interface ClonerFinalizer {
            default Object clone() {System.out.println("default clone"); return this;}
            default void finalize() {System.out.println("default finalize");}
        }
    
        static class FOO implements ClonerFinalizer {
            @Override
            public Object clone() {
                return ClonerFinalizer.super.clone();
            }
            @Override
            public void finalize() {
                ClonerFinalizer.super.finalize();
            }
        }
    }
jaco0646
sumber
.Apa gunanya? Anda masih belum menjawab bagian MENGAPA - "Jadi, apa alasan mengapa perancang Java memutuskan untuk tidak mengizinkan metode standar mengganti metode dari Objek?"
pro_cheats