Alasan bagus untuk melarang warisan di Jawa?

178

Apa alasan bagus untuk melarang pewarisan di Jawa, misalnya dengan menggunakan kelas akhir atau kelas menggunakan konstruktor tanpa parameter pribadi? Apa alasan bagus membuat metode menjadi final?

cretzel
sumber
1
Pedantry: Konstruktor default adalah yang dibuat untuk Anda oleh kompiler Java jika Anda tidak menuliskannya secara eksplisit dalam kode Anda. Maksud Anda konstruktor tanpa argumen.
John Topley
Bukankah itu "konstruktor default implisit"?
cretzel
2
"Anda tidak harus menyediakan konstruktor untuk kelas Anda, tetapi Anda harus berhati-hati ketika melakukan ini. Kompilator secara otomatis menyediakan konstruktor default tanpa argumen, untuk kelas apa pun tanpa konstruktor." java.sun.com/docs/books/tutorial/java/javaOO/constructors.html - John Topley
John Topley

Jawaban:

184

Referensi terbaik Anda di sini adalah Butir 19 dari buku luar biasa Joshua Bloch "Java Efektif", yang disebut "Desain dan dokumen untuk warisan atau melarangnya". (Ini item 17 di edisi kedua dan item 15 di edisi pertama.) Anda harus benar-benar membacanya, tetapi saya akan meringkasnya.

Interaksi kelas yang diwariskan dengan orang tua mereka dapat mengejutkan dan tidak dapat diprediksi jika leluhur tidak dirancang untuk diwarisi.

Kelas karenanya harus datang dalam dua jenis:

  1. Kelas dirancang untuk diperpanjang , dan dengan dokumentasi yang cukup untuk menggambarkan bagaimana hal itu harus dilakukan

  2. Kelas ditandai final

Jika Anda menulis kode internal murni ini mungkin sedikit berlebihan. Namun upaya ekstra yang terlibat dalam menambahkan lima karakter ke file kelas sangat kecil. Jika Anda menulis hanya untuk konsumsi internal, maka pembuat kode di masa depan selalu dapat menghapus 'final' - Anda dapat menganggapnya sebagai peringatan yang mengatakan "kelas ini tidak dirancang dengan mempertimbangkan warisan".

DJClayworth
sumber
6
Dalam pengalaman saya ini tidak berlebihan kalau ada orang selain saya yang akan menggunakan kode. Saya tidak pernah mengerti mengapa Java memiliki metode yang dapat ditimpa secara default.
Eric Weilnau
9
Untuk memahami beberapa Java yang Efektif, Anda perlu memahami desain Josh. Dia mengatakan [sesuatu seperti] Anda harus selalu mendesain antarmuka kelas seolah-olah mereka adalah API publik. Saya pikir pendapat mayoritas adalah bahwa pendekatan ini biasanya terlalu berat dan tidak fleksibel. (YAGNI, dll.)
Tom Hawtin - tackline
9
Jawaban yang bagus. (Saya tidak bisa menolak menunjukkan bahwa itu adalah enam karakter, karena ada juga spasi ... ;-)
joel.neely
36
Harap dicatat, bahwa membuat kelas kekuatan akhir make pengujian lebih sulit (sulit untuk rintisan atau pura-pura)
notnoop
10
Jika kelas terakhir menulis ke antarmuka, maka mengejek seharusnya tidak menjadi masalah.
Fred Haslam
26

Anda mungkin ingin membuat metode final agar kelas utama tidak bisa mengubah perilaku yang dihitung dalam metode lain. Metode yang disebut konstruktor sering dinyatakan final sehingga Anda tidak mendapatkan kejutan yang tidak menyenangkan saat membuat objek.

Bill the Lizard
sumber
Tidak mengerti jawaban ini. Jika Anda tidak keberatan, dapatkah Anda menjelaskan apa yang Anda maksud dengan "kelas utama tidak dapat mengubah perilaku yang dihitung dengan metode lain"? Saya bertanya pertama karena saya tidak mengerti kalimatnya, dan kedua karena Netbeans merekomendasikan bahwa salah satu fungsi yang saya panggil dari konstruktor, seharusnya final.
Nav
1
@Nav Jika Anda membuat metode final, maka kelas utama tidak akan dapat memiliki versi sendiri dari metode itu (atau itu akan menjadi kesalahan kompilasi). Dengan begitu Anda tahu bahwa perilaku yang Anda terapkan dalam metode itu tidak akan berubah nanti ketika orang lain memperluas kelas.
Bill the Lizard
19

Salah satu alasan untuk membuat final kelas adalah jika Anda ingin memaksakan komposisi atas warisan. Ini umumnya diinginkan dalam menghindari kopling ketat antar kelas.

John Topley
sumber
15

Ada 3 kasus penggunaan di mana Anda dapat pergi untuk metode terakhir.

  1. Untuk menghindari kelas turunan dari menimpa fungsionalitas kelas dasar tertentu.
  2. Ini untuk tujuan keamanan, di mana kelas dasar memberikan beberapa fungsionalitas inti penting dari kerangka kerja di mana kelas turunan tidak seharusnya mengubahnya.
  3. Metode final lebih cepat daripada metode instan, karena tidak ada penggunaan konsep tabel virtual untuk metode final dan pribadi. Jadi, di mana pun ada kemungkinan, cobalah menggunakan metode terakhir.

Tujuan membuat final kelas:

Sehingga tidak ada yang bisa memperpanjang kelas-kelas itu dan mengubah perilaku mereka.

Contoh: Integer kelas Wrapper adalah kelas akhir. Jika kelas itu belum final, maka siapa pun dapat memperluas Integer ke kelasnya sendiri dan mengubah perilaku dasar kelas integer. Untuk menghindari hal ini, java membuat semua kelas pembungkus sebagai kelas akhir.

pengguna1923551
sumber
Tidak terlalu yakin dengan teladan Anda. Jika itu masalahnya, mengapa tidak membuat metode dan variabel final daripada membuat seluruh kelas final. Tolong edit jawaban Anda dengan detail.
Rajkiran
4
Bahkan jika Anda menjadikan semua variabel dan metode sebagai final, yang lain lagi dapat mewarisi kelas Anda dan menambahkan fungsionalitas tambahan dan mengubah perilaku dasar kelas Anda.
user1923551
3
> Jika kelas itu belum final, maka siapa pun dapat memperluas Integer ke kelasnya sendiri dan mengubah perilaku dasar kelas integer. Katakanlah ini diizinkan dan kelas baru ini dipanggil DerivedInteger, itu masih tidak mengubah kelas Integer asli, dan siapa pun yang menggunakannya DerivedIntegermelakukannya dengan risiko sendiri, jadi saya masih tidak mengerti mengapa itu menjadi masalah.
Siddhartha
7

Warisan seperti gergaji mesin - sangat kuat, tetapi mengerikan di tangan yang salah. Entah Anda merancang kelas untuk diwarisi (yang dapat membatasi fleksibilitas dan membutuhkan waktu lebih lama) atau Anda harus melarangnya.

Lihat Java Edisi ke-2 yang efektif, item 16 dan 17, atau posting blog saya "Pajak Warisan" .

Jon Skeet
sumber
Tautan ke posting blog rusak. Juga, apakah Anda pikir Anda bisa menguraikan lebih banyak tentang ini?
@MichaelT: Memperbaiki tautan, terima kasih - ikuti untuk penjelasan lebih lanjut :) (Saya tidak punya waktu untuk menambahkan ini sekarang, saya khawatir.)
Jon Skeet
@ JonSkeet, Tautan ke posting blog rusak.
Lucifer
@Kedarnath: Ia bekerja untuk saya ... itu telah rusak untuk sementara waktu, tapi sekarang menunjuk ke halaman yang benar, pada codeblog.jonskeet.uk ...
Jon Skeet
4

Hmmm ... Saya bisa memikirkan dua hal:

Anda mungkin memiliki kelas yang membahas masalah keamanan tertentu. Dengan mensubclassingnya dan memberi makan sistem Anda versi subclassenya, penyerang dapat menghindari batasan keamanan. Misalnya aplikasi Anda mungkin mendukung plugin dan jika sebuah plugin hanya dapat mensubklasifikasikan kelas keamanan Anda yang relevan, ia dapat menggunakan trik ini untuk menyelundupkan versi subclassnya ke tempatnya. Namun, ini adalah sesuatu yang harus dihadapi Sun mengenai applet dan sejenisnya, mungkin bukan kasus yang realistis.

Yang jauh lebih realistis adalah untuk menghindari objek menjadi bisa berubah. Misalnya, karena String tidak dapat diubah, kode Anda dapat menyimpan referensi dengan aman

 String blah = someOtherString;

alih-alih menyalin string terlebih dahulu. Namun, jika Anda dapat mensubkripsikan String, Anda dapat menambahkan metode ke dalamnya yang memungkinkan nilai string untuk dimodifikasi, sekarang tidak ada kode yang dapat mengandalkan lagi bahwa string akan tetap sama jika hanya menyalin string seperti di atas, alih-alih harus menduplikasi tali.

Mecki
sumber
2

Juga, jika Anda menulis kelas sumber tertutup komersial, Anda mungkin tidak ingin orang dapat mengubah fungsionalitas, terutama jika Anda perlu memberikan dukungan untuk itu dan orang-orang telah menimpa metode Anda dan mengeluh bahwa memanggil itu memberikan hasil yang tak terduga.

DavidG
sumber
2

Jika Anda menandai kelas dan metode sebagai final, Anda mungkin melihat kenaikan kinerja kecil, karena runtime tidak harus mencari metode kelas yang tepat untuk memanggil objek tertentu. Metode non-final ditandai sebagai virtual sehingga dapat diperluas dengan benar jika diperlukan, metode final dapat langsung dihubungkan atau dikompilasi secara inline di kelas.

secara keseluruhan
sumber
1
Saya percaya ini adalah mitos, karena generasi VM saat ini harus dapat mengoptimalkan dan deoptimisasi sesuai kebutuhan. Saya ingat melihat beberapa bencharmking ini dan mengklasifikasikannya sebagai mitos.
Miguel Ping
1
Perbedaan dalam pembuatan kode bukanlah mitos, tetapi mungkin keuntungan kinerjanya. Seperti yang saya katakan itu keuntungan kecil, satu situs menyebutkan kenaikan kinerja 3,5%, beberapa lebih tinggi, yang dalam kebanyakan kasus tidak layak untuk semua Nazi pada kode Anda.
seanalltogether
1
Itu bukan mitos. Bahkan, kompiler hanya bisa sebaris metode final V. Saya tidak yakin apakah ada JIT yang "inline" hal-hal runtime art tapi saya ragu itu bisa karena Anda mungkin memuat kelas turunan nanti.
Uri
1
Microbenchmark == butir garam. Yang mengatakan, setelah pemanasan siklus 10B, rata-rata lebih dari 10 panggilan panggilan pada dasarnya tidak ada perbedaan di bawah Eclipse di laptop saya. Dua metode mengembalikan String, final adalah 6.9643us, non-final 6.9641us. Delta itu adalah kebisingan latar belakang, IMHO.
joel.neely
1

Untuk menghentikan orang dari melakukan hal-hal yang dapat membingungkan diri sendiri dan orang lain. Bayangkan sebuah perpustakaan fisika di mana Anda memiliki konstanta atau perhitungan yang pasti. Tanpa menggunakan kata kunci terakhir, seseorang dapat datang dan mendefinisikan kembali perhitungan dasar atau konstanta yang TIDAK PERNAH berubah.

Anson Smith
sumber
2
Ada sesuatu yang saya tidak mengerti tentang argumen itu - jika seseorang mengubah perhitungan / konstanta yang seharusnya tidak berubah - bukankah ada kegagalan karena 100% kesalahan mereka? Dengan kata lain, bagaimana perubahan itu bermanfaat?
matt b
Ya, itu kesalahan teknis "mereka" tetapi bayangkan orang lain menggunakan perpustakaan yang tidak menyadari perubahan tersebut, dapat menyebabkan kebingungan. Saya selalu berpikir bahwa itulah alasan banyak kelas Java Base ditandai sebagai final, untuk menghentikan orang mengubah fungsi dasar.
Anson Smith
@matt b - Anda tampaknya menganggap bahwa pengembang yang melakukan perubahan melakukannya dengan sadar atau bahwa mereka akan peduli bahwa itu adalah kesalahan mereka. Jika seseorang selain saya akan menggunakan kode saya, saya akan menandainya sudah final kecuali dimaksudkan untuk diubah.
Eric Weilnau
Ini sebenarnya merupakan penggunaan 'final' yang berbeda dari yang ditanyakan.
DJClayworth
Jawaban ini tampaknya membingungkan pewarisan dan mutabilitas bidang individu dengan kemampuan untuk menulis subkelas. Anda dapat menandai masing-masing bidang dan metode sebagai final (mencegah redefinisi mereka) tanpa melarang pewarisan.
joel.neely
0

Anda ingin membuat metode final agar kelas utama tidak mengubah perilakunya. Ketika Anda ingin dapat mengubah perilaku, buat metode ini untuk umum. Saat Anda mengganti metode publik, metode itu dapat diubah.

Alexander Robinson
sumber