Di Java 8, antarmuka dapat berisi metode yang diimplementasikan, metode statis, dan apa yang disebut metode "default" (yang tidak perlu ditimpa oleh kelas implementasi).
Dalam pandangan saya (mungkin naif), tidak perlu melanggar antarmuka seperti ini. Antarmuka selalu menjadi kontrak yang harus Anda penuhi, dan ini adalah konsep yang sangat sederhana dan murni. Sekarang ini adalah campuran dari beberapa hal. Menurutku:
- metode statis bukan milik antarmuka. Mereka termasuk kelas utilitas.
- Metode "default" seharusnya tidak diizinkan di antarmuka sama sekali. Anda selalu dapat menggunakan kelas abstrak untuk tujuan ini.
Pendeknya:
Sebelum Java 8:
- Anda bisa menggunakan kelas abstrak dan reguler untuk menyediakan metode statis dan default. Peran antarmuka jelas.
- Semua metode dalam antarmuka harus diganti dengan menerapkan kelas.
- Anda tidak dapat menambahkan metode baru di antarmuka tanpa memodifikasi semua implementasi, tetapi ini sebenarnya adalah hal yang baik.
Setelah Java 8:
- Hampir tidak ada perbedaan antara antarmuka dan kelas abstrak (selain multiple inheritance). Bahkan Anda bisa meniru kelas reguler dengan antarmuka.
- Saat memprogram implementasi, pemrogram mungkin lupa untuk mengganti metode default.
- Ada kesalahan kompilasi jika kelas mencoba menerapkan dua atau lebih antarmuka yang memiliki metode default dengan tanda tangan yang sama.
- Dengan menambahkan metode default ke antarmuka, setiap kelas pelaksana secara otomatis mewarisi perilaku ini. Beberapa dari kelas-kelas ini mungkin belum dirancang dengan fungsionalitas baru itu dalam pikiran, dan ini dapat menyebabkan masalah. Misalnya, jika seseorang menambahkan metode default baru
default void foo()
ke antarmukaIx
, maka kelas yangCx
mengimplementasikanIx
dan memilikifoo
metode pribadi dengan tanda tangan yang sama tidak dapat dikompilasi.
Apa alasan utama untuk perubahan besar seperti itu, dan apa manfaat baru (jika ada) yang mereka tambahkan?
java
programming-languages
interfaces
java8
Tuan Smith
sumber
sumber
@Deprecated
kategori! metode statis adalah salah satu konstruksi paling disalahgunakan di Jawa, karena ketidaktahuan dan kemalasan. Banyak metode statis biasanya berarti programmer tidak kompeten, meningkatkan kopling dengan beberapa urutan besarnya dan merupakan mimpi buruk untuk unit test dan refactor ketika Anda menyadari mengapa mereka adalah ide yang buruk!Jawaban:
Contoh motivasi yang baik untuk metode default adalah di perpustakaan standar Java, di mana Anda sekarang miliki
dari pada
Saya tidak berpikir mereka bisa melakukan itu jika tanpa lebih dari satu implementasi yang identik
List.sort
.sumber
IEnumerable<Byte>.Append
untuk bergabung dengan mereka, lalu panggilCount
, lalu ceritakan bagaimana metode ekstensi menyelesaikan masalah. JikaCountIsKnown
danCount
adalah anggotaIEnumerable<T>
, pengembalian dariAppend
dapat beriklanCountIsKnown
jika koleksi konstituen melakukannya, tetapi tanpa metode seperti itu tidak mungkin.Jawaban yang benar sebenarnya ditemukan dalam Dokumentasi Java , yang menyatakan:
Ini telah lama menjadi sumber rasa sakit di Jawa, karena antarmuka cenderung tidak mungkin untuk berevolusi setelah dipublikasikan. (Konten dalam dokumentasi terkait dengan makalah yang Anda tautkan dalam komentar: Evolusi antarmuka melalui metode ekstensi virtual .) Selain itu, adopsi cepat fitur baru (misalnya lambda dan API aliran baru) hanya dapat dilakukan dengan memperluas antarmuka koleksi yang ada dan menyediakan implementasi standar. Melanggar kompatibilitas biner atau memperkenalkan API baru akan berarti bahwa beberapa tahun akan berlalu sebelum fitur-fitur terpenting Java 8 akan digunakan secara umum.
Alasan untuk mengizinkan metode statis di antarmuka sekali lagi diungkapkan oleh dokumentasi: [t] itu membuatnya lebih mudah bagi Anda untuk mengatur metode pembantu di perpustakaan Anda; Anda dapat menyimpan metode statis khusus untuk antarmuka di antarmuka yang sama daripada di kelas yang terpisah. Dengan kata lain, kelas utilitas statis seperti
java.util.Collections
sekarang dapat (akhirnya) dianggap sebagai anti-pola, secara umum (tentu saja tidak selalu ). Dugaan saya adalah menambahkan dukungan untuk perilaku ini sepele setelah metode ekstensi virtual diterapkan, jika tidak, mungkin tidak akan dilakukan.Pada catatan yang sama, contoh bagaimana fitur baru ini dapat bermanfaat adalah dengan mempertimbangkan satu kelas yang baru - baru ini mengganggu saya
java.util.UUID
,. Itu tidak benar-benar memberikan dukungan untuk UUID tipe 1, 2, atau 5, dan tidak dapat dengan mudah dimodifikasi untuk melakukannya. Itu juga macet dengan generator acak yang sudah ditentukan sebelumnya yang tidak bisa diganti. Menerapkan kode untuk jenis UUID yang tidak didukung membutuhkan ketergantungan langsung pada API pihak ketiga dan bukan antarmuka, atau pemeliharaan kode konversi dan biaya pengumpulan sampah tambahan. Dengan metode statis,UUID
bisa saja didefinisikan sebagai antarmuka, yang memungkinkan implementasi pihak ketiga nyata dari bagian yang hilang. (JikaUUID
awalnya didefinisikan sebagai antarmuka, kita mungkin akan memiliki semacam kikukUuidUtil
kelas dengan metode statis, yang akan mengerikan juga.) Banyak API inti Java mengalami degradasi karena gagal mendasarkan diri pada antarmuka, tetapi pada Java 8 jumlah alasan untuk perilaku buruk ini telah berkurang.Tidak benar untuk mengatakan bahwa [t] di sini sebenarnya tidak ada perbedaan antara antarmuka dan kelas abstrak , karena kelas abstrak dapat memiliki status (yaitu, menyatakan bidang) sedangkan antarmuka tidak dapat. Oleh karena itu tidak setara dengan pewarisan berganda atau bahkan warisan gaya campuran. Mixin yang tepat (seperti sifat Groovy 2.3 ) memiliki akses ke negara. (Groovy juga mendukung metode ekstensi statis.)
Ini juga bukan ide yang baik untuk mengikuti contoh Doval , menurut pendapat saya. Antarmuka seharusnya mendefinisikan kontrak, tetapi tidak seharusnya menegakkan kontrak. (Lagipula tidak di Jawa.) Verifikasi yang tepat atas suatu implementasi adalah tanggung jawab test suite atau alat lain. Menentukan kontrak dapat dilakukan dengan anotasi, dan OVal adalah contoh yang baik, tapi saya tidak tahu apakah itu mendukung batasan yang didefinisikan pada antarmuka. Sistem seperti itu layak, bahkan jika saat ini belum ada. (Strategi mencakup penyesuaian waktu kompilasi
javac
melalui prosesor anotasiAPI dan pembuatan bytecode run-time.) Idealnya, kontrak akan diberlakukan pada waktu kompilasi, dan kasus terburuk menggunakan suite uji, tetapi pemahaman saya adalah bahwa penerapan runtime tidak disukai. Alat lain yang menarik yang mungkin membantu pemrograman kontrak di Jawa adalah Kerangka Pemeriksa .sumber
default
metode tidak dapat menimpaequals
,hashCode
dantoString
. Analisis biaya / manfaat yang sangat informatif tentang mengapa hal ini dilarang dapat ditemukan di sini: mail.openjdk.java.net/pipermail/lambda-dev/2013-March/…equals
dan satuhashCode
metode karena ada dua jenis persamaan yang mungkin perlu diuji oleh koleksi, dan item yang akan mengimplementasikan banyak antarmuka mungkin terjebak dengan persyaratan kontrak yang saling bertentangan. Mampu menggunakan daftar yang tidak akan berubah sebagaihashMap
kunci sangat membantu, tetapi ada juga saat-saat ketika akan sangat membantu untuk menyimpan koleksi dihashMap
mana yang cocok dengan hal-hal yang didasarkan pada kesetaraan daripada keadaan saat ini [ekivalensi menyiratkan keadaan yang cocok dan tidak dapat diubah ] .Karena Anda hanya dapat mewarisi satu kelas. Jika Anda memiliki dua antarmuka yang implementasinya cukup kompleks sehingga Anda memerlukan kelas dasar abstrak, kedua antarmuka tersebut saling eksklusif dalam praktiknya.
Alternatifnya adalah mengubah kelas-kelas dasar abstrak menjadi kumpulan metode statis dan mengubah semua bidang menjadi argumen. Itu akan memungkinkan setiap implementator antarmuka untuk memanggil metode statis dan mendapatkan fungsionalitas, tetapi itu adalah banyak sekali dalam bahasa yang sudah terlalu verbose.
Sebagai contoh yang memotivasi mengapa bisa menyediakan implementasi dalam antarmuka bisa bermanfaat, pertimbangkan antarmuka Stack ini:
Tidak ada cara untuk menjamin bahwa ketika seseorang mengimplementasikan antarmuka,
pop
akan mengeluarkan pengecualian jika tumpukan kosong. Kita dapat menegakkan aturan ini dengan memisahkanpop
menjadi dua metode:public final
metode yang menegakkan kontrak danprotected abstract
metode yang melakukan popping yang sebenarnya.Kami tidak hanya memastikan bahwa semua implementasi mematuhi kontrak, kami juga membebaskan mereka dari keharusan memeriksa apakah tumpukan kosong dan melempar pengecualian. Ini adalah kemenangan besar! ... kecuali fakta bahwa kami harus mengubah antarmuka menjadi kelas abstrak. Dalam bahasa dengan pewarisan tunggal, itu kehilangan fleksibilitas yang besar. Itu membuat calon antarmuka Anda saling eksklusif. Mampu memberikan implementasi yang hanya mengandalkan metode antarmuka sendiri akan menyelesaikan masalah.
Saya tidak yakin apakah pendekatan Java 8 untuk menambahkan metode ke antarmuka memungkinkan menambahkan metode akhir atau metode abstrak yang dilindungi, tapi saya tahu bahasa D memungkinkan dan menyediakan dukungan asli untuk Desain berdasarkan Kontrak . Tidak ada bahaya dalam teknik ini karena
pop
bersifat final, sehingga tidak ada kelas pelaksana yang bisa menimpanya.Adapun implementasi standar dari metode yang dapat ditimpa, saya menganggap implementasi standar apa pun yang ditambahkan ke Java API hanya bergantung pada kontrak antarmuka yang ditambahkan, sehingga setiap kelas yang mengimplementasikan antarmuka dengan benar juga akan berperilaku benar dengan implementasi default.
Bahkan,
Ini tidak sepenuhnya benar karena Anda tidak dapat mendeklarasikan bidang dalam antarmuka. Metode apa pun yang Anda tulis di antarmuka tidak dapat mengandalkan detail implementasi apa pun.
Sebagai contoh yang mendukung metode statis dalam antarmuka, pertimbangkan kelas utilitas seperti Koleksi di API Java. Kelas itu hanya ada karena metode statis tidak dapat dideklarasikan di antarmuka masing-masing.
Collections.unmodifiableList
bisa saja dinyatakan diList
antarmuka, dan itu akan lebih mudah ditemukan.sumber
Stack
antarmuka dan Anda ingin memastikan bahwa ketikapop
dipanggil dengan tumpukan kosong, pengecualian dilemparkan. Diberikan metode abstrakboolean isEmpty()
danprotected T pop_impl()
, Anda dapat menerapkanfinal T pop() { isEmpty()) throw PopException(); else return pop_impl(); }
Ini memberlakukan kontrak pada SEMUA pelaksana.static
.Mungkin maksudnya adalah untuk menyediakan kemampuan untuk membuat kelas mixin dengan mengganti kebutuhan untuk menyuntikkan informasi atau fungsi statis melalui ketergantungan.
Gagasan ini tampaknya terkait dengan bagaimana Anda dapat menggunakan metode ekstensi dalam C # untuk menambahkan fungsionalitas yang diimplementasikan ke antarmuka.
sumber
list.sort(ordering);
formulir yang mudah .IEnumerable
antarmuka dalam C #, Anda dapat melihat bagaimana menerapkan metode ekstensi ke antarmuka itu (sepertiLINQ to Objects
halnya) menambahkan fungsionalitas untuk setiap kelas yang mengimplementasikanIEnumerable
. Itulah yang saya maksud dengan menambahkan fungsionalitas.Dua tujuan utama yang saya lihat dalam
default
metode (beberapa kasus penggunaan melayani kedua tujuan):Jika itu hanya tentang tujuan kedua, Anda tidak akan melihatnya di antarmuka baru seperti
Predicate
. Semua@FunctionalInterface
antarmuka beranotasi wajib memiliki tepat satu metode abstrak agar lambda dapat mengimplementasikannya. Ditambahkandefault
metode sepertiand
,or
,negate
hanya utilitas, dan Anda tidak seharusnya menimpa mereka. Namun, terkadang metode statis akan lebih baik .Adapun ekstensi dari antarmuka yang ada - bahkan di sana, beberapa metode baru hanya gula sintaks. Metode
Collection
sepertistream
,forEach
,removeIf
- pada dasarnya, itu hanya utilitas Anda tidak perlu menimpa. Dan kemudian ada metode sepertispliterator
. Implementasi defaultnya adalah suboptimal, tapi hei, setidaknya kompilasi kode. Gunakan hanya ini jika antarmuka Anda sudah diterbitkan dan digunakan secara luas.Adapun
static
metode, saya kira yang lain cukup baik: Ini memungkinkan antarmuka untuk menjadi kelas utilitas sendiri. Mungkin kita bisa menyingkirkanCollections
masa depan Jawa?Set.empty()
akan bergoyang.sumber