Metode default Java 8 sebagai ciri: aman?

116

Apakah praktik yang aman untuk menggunakan metode default sebagai sifat versi pria yang buruk di Java 8?

Beberapa orang mengklaim mungkin membuat panda sedih jika Anda menggunakannya hanya untuk kepentingan saja, karena itu keren, tapi itu bukan niat saya. Juga sering diingatkan bahwa metode default diperkenalkan untuk mendukung evolusi API dan kompatibilitas mundur, yang memang benar, tetapi ini tidak membuatnya salah atau dipelintir untuk menggunakannya sebagai ciri-ciri itu sendiri.

Saya memiliki kasus penggunaan praktis berikut ini :

public interface Loggable {
    default Logger logger() {
        return LoggerFactory.getLogger(this.getClass());
    }
}

Atau mungkin, tentukan PeriodTrait:

public interface PeriodeTrait {
    Date getStartDate();
    Date getEndDate();
    default isValid(Date atDate) {
        ...
    }
}

Memang, komposisi dapat digunakan (atau bahkan kelas pembantu) tetapi tampaknya lebih bertele-tele dan berantakan dan tidak memungkinkan untuk mendapatkan keuntungan dari polimorfisme.

Jadi, apakah boleh / aman menggunakan metode default sebagai ciri dasar , atau haruskah saya khawatir tentang efek samping yang tidak terduga?

Beberapa pertanyaan tentang SO terkait dengan ciri-ciri Java vs Scala; bukan itu intinya di sini. Saya tidak hanya meminta pendapat saja. Sebaliknya, saya mencari jawaban yang berwibawa atau setidaknya wawasan lapangan: jika Anda telah menggunakan metode default sebagai ciri-ciri pada proyek perusahaan Anda, apakah itu berubah menjadi bom waktu?

Andai
sumber
Menurut saya, Anda bisa mendapatkan manfaat yang sama dengan mewarisi kelas abstrak dan tidak perlu khawatir membuat panda menangis ... Satu-satunya alasan saya dapat menggunakan metode default dalam Antarmuka adalah karena Anda memerlukan fungsionalitas dan tidak bisa memodifikasi sekumpulan kode warisan yang didasarkan pada Antarmuka.
Deven Phillips
1
Saya setuju dengan @ infosec812 tentang memperluas kelas abstrak yang mendefinisikan bidang logger statisnya sendiri. Bukankah metode logger () Anda akan membuat instance logger baru setiap kali dipanggil?
Eidan Spiegel
Untuk logging, Anda mungkin ingin melihat Projectlombok.org dan anotasi @ Slf4j mereka.
Deven Phillips

Jawaban:

120

Jawaban singkatnya adalah: aman jika Anda menggunakannya dengan aman :)

Jawaban yang tajam: beri tahu saya apa yang Anda maksud dengan sifat, dan mungkin saya akan memberikan jawaban yang lebih baik :)

Secara serius, istilah "sifat" tidak didefinisikan dengan baik. Banyak pengembang Java yang paling akrab dengan ciri-ciri seperti yang diungkapkan dalam Scala, tetapi Scala jauh dari bahasa pertama yang memiliki ciri-ciri, baik dalam nama atau efeknya.

Misalnya, di Scala, sifat bersifat stateful (dapat memiliki varvariabel); di Fortress mereka adalah perilaku murni. Antarmuka Java dengan metode default tidak memiliki kewarganegaraan; apakah ini berarti mereka bukan sifat? (Petunjuk: itu adalah pertanyaan jebakan.)

Sekali lagi, di Scala, ciri-ciri disusun melalui linierisasi; jika kelas Amemperluas ciri X- ciri dan Y, maka urutan di mana Xdan Ydicampur menentukan bagaimana konflik antara Xdan Ydiselesaikan. Di Java, mekanisme linierisasi ini tidak ada (sebagian ditolak karena terlalu "tidak mirip Java".)

Alasan terdekat untuk menambahkan metode default ke antarmuka adalah untuk mendukung evolusi antarmuka , tetapi kami sangat menyadari bahwa kami melampaui itu. Apakah Anda menganggap itu sebagai "evolusi antarmuka ++" atau "sifat--" adalah masalah interpretasi pribadi. Jadi, untuk menjawab pertanyaan Anda tentang keselamatan ... selama Anda tetap berpegang pada apa yang sebenarnya didukung oleh mekanisme tersebut, daripada mencoba meregangkannya ke sesuatu yang tidak didukungnya, Anda akan baik-baik saja.

Tujuan desain utama adalah bahwa, dari perspektif klien antarmuka, metode default harus dibedakan dari metode antarmuka "biasa". Default-an suatu metode, oleh karena itu, hanya menarik bagi perancang dan pelaksana antarmuka.

Berikut beberapa kasus penggunaan yang sesuai dengan tujuan desain:

  • Evolusi antarmuka. Di sini, kami menambahkan metode baru ke antarmuka yang ada, yang memiliki implementasi default yang masuk akal dalam kaitannya dengan metode yang ada pada antarmuka itu. Contohnya adalah menambahkan forEachmetode ke Collection, di mana implementasi default ditulis dalam istilah iterator()metode.

  • Metode "opsional". Di sini, perancang antarmuka mengatakan "Pelaksana tidak perlu mengimplementasikan metode ini jika mereka bersedia untuk hidup dengan keterbatasan dalam fungsionalitas yang diperlukan". Misalnya, Iterator.removediberi default yang melempar UnsupportedOperationException; karena sebagian besar implementasi Iteratormemiliki perilaku ini, default membuat metode ini pada dasarnya opsional. (Jika perilaku dari AbstractCollectiondiekspresikan sebagai default pada Collection, kita mungkin melakukan hal yang sama untuk metode mutatif.)

  • Metode kenyamanan. Ini adalah metode yang hanya untuk kenyamanan, sekali lagi umumnya diimplementasikan dalam istilah metode non-default di kelas. The logger()metode dalam contoh pertama Anda adalah ilustrasi yang wajar ini.

  • Kombinator. Ini adalah metode komposisi yang membuat instance baru dari antarmuka berdasarkan instance saat ini. Misalnya metode Predicate.and()atau Comparator.thenComparing()contoh kombinator.

Jika Anda memberikan implementasi default, Anda juga harus memberikan beberapa spesifikasi untuk default (di JDK, kami menggunakan @implSpectag javadoc untuk ini) untuk membantu pelaksana dalam memahami apakah mereka ingin mengganti metode atau tidak. Beberapa default, seperti metode kemudahan dan kombinator, hampir tidak pernah diganti; yang lain, seperti metode opsional, sering diganti. Anda perlu memberikan spesifikasi yang cukup (bukan hanya dokumentasi) tentang apa yang dijanjikan default untuk dilakukan, sehingga implementor dapat membuat keputusan yang masuk akal tentang apakah mereka perlu menimpanya.

Brian Goetz
sumber
9
Terima kasih Brian atas jawaban komprehensif ini. Sekarang saya dapat menggunakan metode default dengan hati yang ringan. Pembaca: info lebih lanjut dari Brian Goetz tentang evolusi antarmuka dan metode default dapat ditemukan di NightHacking Worldwide Lambdas misalnya.
youri
Terima kasih @ brian-goetz. Dari apa yang Anda katakan, saya pikir metode default Java lebih dekat dengan gagasan tentang sifat tradisional seperti yang didefinisikan dalam makalah Ducasse et al ( scg.unibe.ch/archive/papers/Duca06bTOPLASTraits.pdf ). Scala "sifat" sama sekali tidak tampak seperti sifat bagi saya, karena mereka memiliki status, menggunakan linierisasi dalam komposisinya, dan tampaknya mereka juga secara implisit menyelesaikan konflik metode - dan ini semua adalah hal yang tidak dimiliki oleh ciri tradisional memiliki. Faktanya, saya akan mengatakan ciri-ciri Scala lebih seperti campuran daripada ciri. Bagaimana menurut anda? PS: Saya tidak pernah membuat kode di Scala.
adino
Bagaimana jika menggunakan implementasi default kosong untuk metode dalam antarmuka yang bertindak sebagai pendengar? Implementasi listener mungkin hanya tertarik untuk mendengarkan beberapa metode antarmuka, jadi dengan menjadikan metode default, pelaksana hanya perlu mengimplementasikan metode yang perlu didengarkan.
Lahiru Chandima
1
@LahiruChandima Suka dengan MouseListener? Sejauh gaya API ini masuk akal, ini cocok dengan keranjang "metode opsional". Pastikan untuk mendokumentasikan opsional-an dengan jelas!
Brian Goetz
Iya. Sama seperti di MouseListener. Terima kasih atas tanggapannya.
Lahiru Chandima