Ada alasan untuk lebih suka getClass () daripada instanceof saat membuat .equals ()?

174

Saya menggunakan Eclipse untuk menghasilkan .equals()dan .hashCode(), dan ada opsi berlabel "Gunakan 'instanceof' untuk membandingkan jenis". Defaultnya adalah opsi ini tidak dicentang dan digunakan .getClass()untuk membandingkan jenis. Apakah ada alasan aku harus lebih .getClass()lebih instanceof?

Tanpa menggunakan instanceof:

if (obj == null)
  return false;
if (getClass() != obj.getClass())
  return false;

Menggunakan instanceof:

if (obj == null)
  return false;
if (!(obj instanceof MyClass))
  return false;

Saya biasanya memeriksa instanceofopsi, lalu masuk dan menghapus if (obj == null)centang " ". (Itu mubazir karena objek nol akan selalu gagal instanceof.) Apakah ada alasan itu ide yang buruk?

Tidur
sumber
1
Ekspresi x instanceof SomeClasssalah jika xitu null. Oleh karena itu, sintaks kedua tidak memerlukan pemeriksaan nol.
rds
5
@rds Ya, paragraf segera setelah cuplikan kode juga mengatakan ini. Itu ada dalam cuplikan kode karena itulah yang dihasilkan Eclipse.
Kip

Jawaban:

100

Jika Anda menggunakan instanceof, membuat Anda equalspelaksanaannya finalakan melestarikan kontrak simetri metode: x.equals(y) == y.equals(x). Jika finaltampaknya membatasi, hati-hati memeriksa gagasan Anda tentang kesetaraan objek untuk memastikan bahwa implementasi utama Anda sepenuhnya mempertahankan kontrak yang ditetapkan oleh Objectkelas.

erickson
sumber
persis yang terlintas di pikiran saya ketika saya membaca kutipan josh bloch di atas. +1 :)
Johannes Schaub - litb
13
pasti keputusan kuncinya. simetri harus berlaku, dan contohnya membuatnya sangat mudah untuk menjadi asimetris secara tidak sengaja
Scott Stanchfield
Saya juga akan menambahkan bahwa "jika finaltampaknya membatasi" (pada keduanya equalsdan hashCode) kemudian gunakan getClass()kesetaraan alih-alih instanceofuntuk menjaga persyaratan simetri dan transitivitas equalskontrak.
Shadow Man
176

Josh Bloch mendukung pendekatan Anda:

Alasan yang saya sukai instanceofpendekatan adalah bahwa ketika Anda menggunakan getClasspendekatan, Anda memiliki batasan bahwa objek hanya sama dengan objek lain dari kelas yang sama, tipe run time yang sama. Jika Anda memperluas kelas dan menambahkan beberapa metode tidak berbahaya ke dalamnya, maka periksa untuk melihat apakah beberapa objek subclass sama dengan objek kelas super, bahkan jika objek sama di semua aspek penting, Anda akan mendapatkan jawaban mengejutkan bahwa mereka tidak sama. Bahkan, ini melanggar interpretasi ketat dari prinsip substitusi Liskov , dan dapat menyebabkan perilaku yang sangat mengejutkan. Di Jawa, ini sangat penting karena sebagian besar koleksi (HashTable, dll.) didasarkan pada metode sama dengan. Jika Anda menempatkan anggota kelas super di tabel hash sebagai kunci dan kemudian mencarinya menggunakan contoh subclass, Anda tidak akan menemukannya, karena mereka tidak sama.

Lihat juga jawaban SO ini .

Java bab 3 yang efektif juga membahas hal ini.

Michael Myers
sumber
18
+1 Semua orang yang melakukan java harus membaca buku itu setidaknya 10 kali!
André
16
Masalah dengan pendekatan instanceof adalah bahwa ia merusak properti "simetris" dari Object.equals () dalam hal itu menjadi mungkin bahwa x.equals (y) == true tetapi y.equals (x) == false jika x.getClass ()! = y.getClass (). Saya lebih suka untuk tidak merusak properti ini kecuali benar-benar diperlukan (yaitu, sama dengan equals () untuk objek yang dikelola atau proxy).
Kevin Sitze
8
Instanceof pendekatan tepat ketika, dan hanya ketika, kelas dasar mendefinisikan apa persamaan harus diartikan sebagai objek subclass. Penggunaan getClasstidak melanggar LSP, karena LSP hanya terkait dengan apa yang dapat dilakukan dengan instance yang ada - bukan jenis instance apa yang dapat dibangun. Kelas yang dikembalikan oleh getClassadalah properti yang tidak dapat diubah dari instance objek. LSP tidak menyiratkan bahwa harus dimungkinkan untuk membuat subclass di mana properti itu menunjukkan kelas apa pun selain yang membuatnya.
supercat
66

Angelika Langers Rahasia yang sederajat membahas hal itu dengan diskusi panjang dan terperinci untuk beberapa contoh umum dan terkenal, termasuk oleh Josh Bloch dan Barbara Liskov, menemukan beberapa masalah di sebagian besar dari mereka. Dia juga masuk ke dalam instanceofvs getClass. Beberapa kutipan darinya

Kesimpulan

Setelah membedah empat contoh yang dipilih secara sewenang-wenang dari implementasi equals (), apa yang kita simpulkan?

Pertama-tama: ada dua cara yang berbeda secara substansial untuk melakukan pemeriksaan kecocokan jenis dalam implementasi equals (). Kelas dapat memungkinkan perbandingan tipe campuran antara objek super dan subkelas dengan menggunakan instance dari operator, atau kelas dapat memperlakukan objek dengan tipe yang berbeda sebagai tidak sama dengan menggunakan uji getClass (). Contoh di atas menggambarkan dengan baik bahwa implementasi equals () menggunakan getClass () umumnya lebih kuat daripada implementasi yang menggunakan instanceof.

Instanceof test hanya benar untuk kelas akhir atau jika setidaknya metode sama dengan () adalah final dalam superclass. Yang terakhir pada dasarnya menyiratkan bahwa tidak ada subclass yang harus memperpanjang status superclass, tetapi hanya dapat menambahkan fungsionalitas atau bidang yang tidak relevan untuk keadaan dan perilaku objek, seperti bidang transien atau statis.

Implementasi yang menggunakan uji getClass () di sisi lain selalu mematuhi kontrak equals (); mereka benar dan kuat. Mereka, bagaimanapun, secara semantik sangat berbeda dari implementasi yang menggunakan tes instanceof. Implementasi menggunakan getClass () tidak memungkinkan perbandingan objek sub-dengan superclass, bahkan ketika subkelas tidak menambahkan bidang apa pun dan bahkan tidak ingin menimpa sama dengan (). Ekstensi kelas "sepele" seperti itu misalnya akan menjadi tambahan metode debug-cetak dalam subkelas yang ditentukan untuk tujuan "sepele" ini. Jika superclass melarang perbandingan tipe campuran melalui cek getClass (), maka ekstensi sepele tidak akan sebanding dengan superclassnya. Apakah masalah ini sepenuhnya atau tidak tergantung pada semantik kelas dan tujuan ekstensi.

Johannes Schaub - litb
sumber
61

Alasan penggunaannya getClassadalah untuk memastikan properti simetris dari equalskontrak. Dari equals 'JavaDocs:

Ini simetris: untuk nilai referensi non-null x dan y, x.equals (y) harus mengembalikan true jika dan hanya jika y.equals (x) mengembalikan true.

Dengan menggunakan instanceof, mungkin tidak simetris. Perhatikan contohnya: Anjing memanjang Hewan. Animal equalsmelakukan instanceofpengecekan Animal. Anjing equalstidak sebuah instanceofcek dari anjing. Berikan Animal sebuah and Dog d (dengan bidang lain yang sama):

a.equals(d) --> true
d.equals(a) --> false

Ini melanggar properti simetris.

Untuk benar-benar mengikuti kontrak equal, simetri harus dipastikan, dan karenanya kelas harus sama.

Steve Kuo
sumber
8
Itulah jawaban pertama yang jelas dan ringkas untuk pertanyaan ini. Contoh kode bernilai seribu kata.
Johnride
Saya telah melalui semua jawaban verbal lainnya kecuali Anda menyelamatkan hari itu. Sederhana, ringkas, anggun, dan benar-benar jawaban terbaik dari pertanyaan ini.
1
Ada persyaratan simetri seperti yang Anda sebutkan. Tetapi ada juga persyaratan "transitivitas". Jika a.equals(c)dan b.equals(c), kemudian a.equals(b)(pendekatan naif untuk membuat kapan Dog.equalssaja tetapi memeriksa bidang tambahan ketika itu adalah contoh Anjing tidak akan melanggar simetri tetapi akan melanggar transitivitas)return super.equals(object)!(object instanceof Dog)
Shadow Man
Agar aman, Anda bisa menggunakan getClass()cek kesetaraan, atau Anda bisa menggunakan instanceofcek jika Anda equalsdan hashCodemetode final.
Shadow Man
26

Ini adalah semacam perdebatan agama. Kedua pendekatan memiliki masalah mereka.

  • Gunakan instanceof dan Anda tidak pernah dapat menambahkan anggota signifikan ke subclass.
  • Gunakan getClass dan Anda melanggar prinsip substitusi Liskov.

Bloch memiliki saran lain yang relevan dalam Effective Java Second Edition :

  • Butir 17: Desain dan dokumen untuk warisan atau melarangnya
McDowell
sumber
1
Tidak ada tentang menggunakan getClassakan melanggar LSP, kecuali kelas dasar secara khusus mendokumentasikan sarana yang memungkinkan untuk membuat instance dari subclass berbeda yang membandingkan sama. Pelanggaran LSP apa yang Anda lihat?
supercat
Mungkin itu harus diulangi sebagai Gunakan getClass dan Anda meninggalkan subtipe dalam bahaya pelanggaran LSP. Bloch memiliki contoh dalam Java yang Efektif - juga dibahas di sini . Alasannya sebagian besar adalah bahwa hal itu dapat mengakibatkan perilaku yang mengejutkan bagi pengembang yang menerapkan subtipe yang tidak menambahkan status. Saya ambil poin Anda - dokumentasi adalah kuncinya.
McDowell
2
Satu-satunya waktu dua contoh kelas berbeda yang seharusnya melaporkan diri mereka sendiri adalah sama adalah jika mereka mewarisi dari kelas dasar atau antarmuka yang sama yang mendefinisikan apa arti kesetaraan [sebuah filosofi yang diterapkan Java, dengan ragu-ragu IMHO, ke beberapa antarmuka koleksi]. Kecuali jika kontrak kelas dasar mengatakan bahwa getClass()tidak boleh dianggap bermakna di luar fakta bahwa kelas tersebut dapat dikonversi ke kelas dasar, pengembalian dari getClass()harus dianggap seperti properti lainnya yang harus cocok untuk instance yang sama.
supercat
1
@eyalzba Dimana instanceof_ digunakan jika subkelas menambahkan anggota ke kontrak yang sama ini akan melanggar persyaratan kesetaraan simetris . Menggunakan subclass getClass tidak pernah sama dengan tipe induk. Bukan berarti Anda harus mengimbangi sama saja, tapi ini adalah trade-off jika Anda melakukannya.
McDowell
2
"Merancang dan mendokumentasikan warisan atau melarangnya" Saya pikir ini sangat penting. Pemahaman saya adalah bahwa satu-satunya waktu Anda harus menimpa sama dengan adalah jika Anda membuat tipe nilai yang tidak dapat diubah, dalam hal ini kelas harus dinyatakan final. Karena tidak akan ada warisan, Anda bebas menerapkan sama dengan yang dibutuhkan. Dalam kasus di mana Anda mengizinkan warisan, maka objek harus membandingkan dengan referensi kesetaraan.
TheSecretSquad
23

Perbaiki saya jika saya salah, tetapi getClass () akan berguna ketika Anda ingin memastikan instance Anda BUKAN subkelas dari kelas yang Anda bandingkan. Jika Anda menggunakan instanceof dalam situasi itu Anda TIDAK bisa tahu itu karena:

class A { }

class B extends A { }

Object oA = new A();
Object oB = new B();

oA instanceof A => true
oA instanceof B => false
oB instanceof A => true // <================ HERE
oB instanceof B => true

oA.getClass().equals(A.class) => true
oA.getClass().equals(B.class) => false
oB.getClass().equals(A.class) => false // <===============HERE
oB.getClass().equals(B.class) => true
TraderJoeChicago
sumber
Bagi saya ini alasannya
karlihnos
5

Jika Anda ingin memastikan hanya kelas yang cocok maka gunakan getClass() ==. Jika Anda ingin mencocokkan subclass maka instanceofdiperlukan.

Juga, instanceof tidak akan cocok dengan nol tetapi aman untuk dibandingkan dengan nol. Jadi Anda tidak perlu membatalkannya.

if ( ! (obj instanceof MyClass) ) { return false; }
Clint
sumber
Kembali poin kedua Anda: Dia sudah menyebutkan hal itu dalam pertanyaan. "Saya biasanya memeriksa opsi instanceof, dan kemudian masuk dan menghapus centang 'jika (obj == null)'."
Michael Myers
5

Itu tergantung jika Anda mempertimbangkan apakah subkelas dari kelas yang diberikan sama dengan induknya.

class LastName
{
(...)
}


class FamilyName
extends LastName
{
(..)
}

di sini saya akan menggunakan 'instanceof', karena saya ingin LastName dibandingkan dengan FamilyName

class Organism
{
}

class Gorilla extends Organism
{
}

di sini saya akan menggunakan 'getClass', karena kelas sudah mengatakan bahwa dua instance tidak setara.

Pierre
sumber
3

instanceof berfungsi untuk instance dari kelas yang sama atau subkelasnya

Anda dapat menggunakannya untuk menguji apakah suatu objek adalah turunan dari kelas, turunan dari subkelas, atau turunan dari kelas yang mengimplementasikan antarmuka tertentu.

ArryaList dan RoleList keduanya adalah instance dari List

Sementara

getClass () == o.getClass () akan benar hanya jika kedua objek (ini dan o) milik kelas yang persis sama.

Jadi tergantung pada apa yang Anda perlu membandingkan Anda bisa menggunakan satu atau yang lain.

Jika logika Anda adalah: "Satu objek sama dengan yang lain hanya jika mereka berdua kelas yang sama" Anda harus pergi untuk "sama", yang saya pikir sebagian besar kasus.

OscarRyz
sumber
3

Kedua metode memiliki masalah mereka.

Jika subkelas mengubah identitas, maka Anda perlu membandingkan kelas mereka yang sebenarnya. Jika tidak, Anda melanggar properti simetris. Misalnya, berbagai jenis Persons tidak boleh dianggap setara, bahkan jika mereka memiliki nama yang sama.

Namun, beberapa subclass tidak mengubah identitas dan ini perlu digunakan instanceof. Sebagai contoh, jika kita memiliki banyak Shapeobjek yang tidak dapat diubah , maka a Rectangledengan panjang dan lebar 1 harus sama dengan unit Square.

Dalam praktiknya, saya pikir kasus sebelumnya lebih mungkin benar. Biasanya, subclassing adalah bagian mendasar dari identitas Anda dan menjadi persis seperti orang tua Anda, kecuali Anda dapat melakukan satu hal kecil tidak membuat Anda sama.

James
sumber
-1

Sebenarnya instanceof memeriksa di mana objek milik beberapa hierarki atau tidak. mis: Objek mobil milik kelas Vehical. Jadi "Mobil baru () instance dari Vehical" mengembalikan true. Dan "Mobil baru (). GetClass (). Sama dengan (Vehical.class)" mengembalikan false, meskipun objek Mobil milik kelas Vehical tetapi dikategorikan sebagai tipe yang terpisah.

Akash5288
sumber