Bất kỳ lý do nào để thích getClass () hơn thể hiện khi tạo .equals ()?


174

Tôi đang sử dụng Eclipse để tạo .equals().hashCode(), và có một tùy chọn có nhãn "Sử dụng 'instanceof' để so sánh các loại". Mặc định là tùy chọn này được bỏ chọn và sử dụng .getClass()để so sánh các loại. Có bất kỳ lý do tôi nên thích .getClass()hơn instanceof?

Không sử dụng instanceof:

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

Sử dụng instanceof:

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

Tôi thường kiểm tra instanceoftùy chọn, sau đó đi vào và loại bỏ if (obj == null)kiểm tra "". (Nó là dư thừa vì các đối tượng null sẽ luôn luôn thất bại instanceof.) Có lý do nào đó là một ý tưởng tồi không?


1
Biểu thức x instanceof SomeClasslà sai nếu xnull. Do đó, cú pháp thứ hai không cần kiểm tra null.
rds

5
@rds Vâng, đoạn ngay sau đoạn mã cũng nói điều này. Nó nằm trong đoạn mã bởi vì đó là những gì Eclipse tạo ra.
Kip

Câu trả lời:


100

Nếu bạn sử dụng instanceof, equalsthực hiện việc triển khai của bạn finalsẽ duy trì hợp đồng đối xứng của phương thức : x.equals(y) == y.equals(x). Nếu finalcó vẻ hạn chế, hãy kiểm tra cẩn thận khái niệm tương đương đối tượng của bạn để đảm bảo rằng các triển khai ghi đè của bạn duy trì đầy đủ hợp đồng được thiết lập bởi Objectlớp.


chính xác điều đó xuất hiện trong tâm trí của tôi khi tôi đọc đoạn trích josh bloch ở trên. +1 :)
Johannes Schaub - litb

13
chắc chắn là quyết định quan trọng tính đối xứng phải được áp dụng, và thể hiện của nó rất dễ trở thành sự bất đối xứng vô tình
Scott Stanchfield

Tôi cũng nói thêm rằng "nếu finalcó vẻ hạn chế" (trên cả hai equalshashCode) thì hãy sử dụng getClass()sự bình đẳng thay vì instanceofđể duy trì các yêu cầu về tính đối xứng và tính chuyển đổi của equalshợp đồng.
Shadow Man

176

Josh Bloch ủng hộ cách tiếp cận của bạn:

Lý do mà tôi ủng hộ instanceofcách tiếp cận là khi bạn sử dụng getClasscách tiếp cận, bạn có hạn chế rằng các đối tượng chỉ bằng với các đối tượng khác cùng lớp, cùng loại thời gian chạy. Nếu bạn mở rộng một lớp và thêm một vài phương thức vô hại vào nó, sau đó kiểm tra xem liệu một số đối tượng của lớp con có bằng với một đối tượng của siêu lớp hay không, ngay cả khi các đối tượng bằng nhau trong tất cả các khía cạnh quan trọng, bạn sẽ nhận được câu trả lời đáng ngạc nhiên rằng họ không bằng nhau. Trên thực tế, điều này vi phạm một cách giải thích chặt chẽ về nguyên tắc thay thế Liskov , và có thể dẫn đến hành vi rất đáng ngạc nhiên. Trong Java, nó đặc biệt quan trọng vì hầu hết các bộ sưu tập (HashTable, v.v.) dựa trên phương pháp đẳng thức. Nếu bạn đặt một thành viên của siêu lớp trong bảng băm làm khóa và sau đó tìm kiếm nó bằng một thể hiện của lớp con, bạn sẽ không tìm thấy nó, bởi vì chúng không bằng nhau.

Xem thêm câu trả lời SO này .

Chương 3 Java hiệu quả cũng đề cập đến điều này.


18
+1 Mọi người làm java nên đọc cuốn sách đó ít nhất 10 lần!
André

16
Vấn đề với cách tiếp cận instanceof là nó phá vỡ thuộc tính "đối xứng" của Object.equals () ở chỗ có thể x.equals (y) == true nhưng y.equals (x) == false nếu x.getClass ()! = y.getClass (). Tôi không muốn phá vỡ thuộc tính này trừ khi thực sự cần thiết (nghĩa là ghi đè bằng () cho các đối tượng được quản lý hoặc proxy).
Kevin Sitze

8
Cách tiếp cận instanceof là đúng khi và chỉ khi nào, lớp cơ sở xác định đẳng thức giữa các đối tượng của lớp con có nghĩa là gì. Việc sử dụng getClasskhông vi phạm LSP, vì LSP chỉ liên quan đến những gì có thể được thực hiện với các thể hiện có - không phải là loại thể hiện nào có thể được xây dựng. Lớp được trả về bởi getClasslà một thuộc tính bất biến của một thể hiện đối tượng. LSP không ngụ ý rằng có thể tạo một lớp con trong đó thuộc tính đó chỉ ra bất kỳ lớp nào khác ngoài lớp đã tạo ra nó.
supercat

66

Angelika Langers Bí mật của sự bình đẳng được đưa vào đó với một cuộc thảo luận dài và chi tiết cho một vài ví dụ phổ biến và nổi tiếng, bao gồm Josh Bloch và Barbara Liskov, phát hiện ra một vài vấn đề trong hầu hết chúng. Cô cũng được vào instanceofvs getClass. Một số trích dẫn từ nó

Kết luận

Sau khi mổ xẻ bốn ví dụ được lựa chọn tùy ý về việc thực hiện bằng (), chúng ta kết luận điều gì?

Trước hết: có hai cách khác nhau để thực hiện kiểm tra đối sánh loại trong việc thực hiện bằng (). Một lớp có thể cho phép so sánh kiểu hỗn hợp giữa các đối tượng siêu và lớp con bằng toán tử thể hiện hoặc một lớp có thể coi các đối tượng thuộc loại khác là không bằng nhau bằng phép thử getClass (). Các ví dụ ở trên minh họa độc đáo rằng việc triển khai bằng () bằng cách sử dụng getClass () thường mạnh hơn so với các triển khai sử dụng instanceof.

Kiểm tra thể hiện chỉ đúng cho các lớp cuối cùng hoặc nếu ít nhất phương thức bằng () là cuối cùng trong một siêu lớp. Về cơ bản, hàm ý rằng không có lớp con nào phải mở rộng trạng thái của lớp cha, mà chỉ có thể thêm chức năng hoặc các trường không liên quan đến trạng thái và hành vi của đối tượng, chẳng hạn như trường tạm thời hoặc trường tĩnh.

Mặt khác, việc sử dụng thử nghiệm getClass () luôn tuân thủ hợp đồng bằng (); họ đúng và mạnh mẽ Tuy nhiên, chúng rất khác biệt về mặt ngữ nghĩa so với việc triển khai sử dụng thử nghiệm thể hiện. Việc triển khai bằng getClass () không cho phép so sánh giữa các đối tượng siêu lớp, ngay cả khi lớp con không thêm bất kỳ trường nào và thậm chí không muốn ghi đè bằng (). Ví dụ, một phần mở rộng lớp "tầm thường" như vậy sẽ là sự bổ sung của phương thức in gỡ lỗi trong một lớp con được xác định chính xác cho mục đích "tầm thường" này. Nếu siêu lớp cấm so sánh kiểu hỗn hợp thông qua kiểm tra getClass (), thì phần mở rộng tầm thường sẽ không thể so sánh với siêu lớp của nó. Đây có phải là một vấn đề hoàn toàn hay không phụ thuộc vào ngữ nghĩa của lớp học và mục đích của phần mở rộng.


61

Lý do để sử dụng getClasslà để đảm bảo tài sản đối xứng của equalshợp đồng. Từ JavaDocs bằng:

Đó là đối xứng: đối với mọi giá trị tham chiếu không null x và y, x.equals (y) sẽ trả về true khi và chỉ khi y.equals (x) trả về true.

Bằng cách sử dụng instanceof, có thể không đối xứng. Hãy xem xét ví dụ: Chó kéo dài Động vật. Động vật equalslàm một instanceofkiểm tra Thú. Chó equalslàm một instanceofkiểm tra của con chó. Cho Animal a và Dog d (với các trường khác giống nhau):

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

Điều này vi phạm tài sản đối xứng.

Để tuân thủ nghiêm ngặt hợp đồng của nhau, tính đối xứng phải được đảm bảo, và do đó, lớp cần phải giống nhau.


8
Đó là câu trả lời rõ ràng và súc tích đầu tiên cho câu hỏi này. Một mẫu mã có giá trị một ngàn từ.
Johnride

Tôi đã trải qua tất cả các câu trả lời dài dòng khác sử dụng bạn đã lưu trong ngày. Đơn giản, súc tích, thanh lịch và hoàn toàn là câu trả lời hay nhất của câu hỏi này.

1
Có yêu cầu đối xứng như bạn đã đề cập. Nhưng đó cũng là yêu cầu "siêu việt". Nếu a.equals(c)b.equals(c), sau đó a.equals(b)(cách tiếp cận ngây thơ của việc tạo ra khi Dog.equalschỉ kiểm tra các trường bổ sung khi đó là trường hợp Dog sẽ không vi phạm tính đối xứng nhưng sẽ vi phạm tính chuyển đổi)return super.equals(object)!(object instanceof Dog)
Shadow Man

Để an toàn, bạn có thể sử dụng getClass()kiểm tra bình đẳng hoặc bạn có thể sử dụng instanceofkiểm tra nếu thực hiện equalshashCodephương pháp của bạn final.
Shadow Man

26

Đây là một cái gì đó của một cuộc tranh luận tôn giáo. Cả hai phương pháp đều có vấn đề của họ.

  • Sử dụng thể hiện và bạn không bao giờ có thể thêm các thành viên quan trọng vào các lớp con.
  • Sử dụng getClass và bạn vi phạm nguyên tắc thay thế Liskov.

Bloch có một lời khuyên khác có liên quan trong Ấn bản thứ hai hiệu quả của Java :

  • Mục 17: Thiết kế và tài liệu để thừa kế hoặc cấm nó

1
Không có gì về việc sử dụng getClasssẽ vi phạm LSP, trừ khi lớp cơ sở ghi lại một cách cụ thể một phương tiện để có thể tạo ra các thể hiện của các lớp con khác nhau có thể so sánh bằng nhau. Bạn thấy vi phạm LSP nào?
supercat

Có lẽ điều đó nên được nhắc lại là Sử dụng getClass và bạn để lại các kiểu con có nguy cơ vi phạm LSP. Bloch có một ví dụ trong Java hiệu quả - cũng được thảo luận ở đây . Cơ sở lý luận của ông chủ yếu là nó có thể dẫn đến hành vi đáng ngạc nhiên cho các nhà phát triển triển khai các kiểu con không thêm trạng thái. Tôi lấy quan điểm của bạn - tài liệu là chính.
McDowell

2
Lần duy nhất hai trường hợp của các lớp riêng biệt tự báo cáo là bằng nhau nếu chúng kế thừa từ một lớp cơ sở chung hoặc giao diện xác định nghĩa của đẳng thức là gì [một triết lý mà Java áp dụng, được gọi là IMHO, cho một số giao diện bộ sưu tập]. Trừ khi hợp đồng của lớp cơ sở nói rằng getClass()không nên được coi là có ý nghĩa ngoài thực tế là lớp được đề cập có thể chuyển đổi thành lớp cơ sở, thì lợi nhuận từ getClass()nên được coi như bất kỳ tài sản nào khác phải khớp với các thể hiện bằng nhau.
supercat

1
@eyalzba Trường hợp instanceof_ được sử dụng nếu một lớp con thêm thành viên vào hợp đồng bằng, điều này sẽ vi phạm yêu cầu bình đẳng đối xứng . Sử dụng các lớp con getClass không bao giờ có thể bằng với kiểu cha. Không phải là bạn nên ghi đè bằng dù sao, nhưng đây là sự đánh đổi nếu bạn làm.
McDowell

2
"Thiết kế và tài liệu để thừa kế hoặc cấm nó" Tôi nghĩ là rất quan trọng. Hiểu biết của tôi là lần duy nhất bạn nên ghi đè bằng là nếu bạn đang tạo một loại giá trị bất biến, trong trường hợp đó, lớp sẽ được khai báo là cuối cùng. Vì sẽ không có sự kế thừa, bạn có thể tự do thực hiện bằng khi cần thiết. Trong trường hợp bạn cho phép thừa kế, thì các đối tượng nên so sánh bằng đẳng thức tham chiếu.
TheSecretSquad 17/03/2015

23

Sửa lỗi cho tôi nếu tôi sai, nhưng getClass () sẽ hữu ích khi bạn muốn đảm bảo cá thể của bạn KHÔNG phải là một lớp con của lớp mà bạn đang so sánh. Nếu bạn sử dụng instanceof trong tình huống đó, bạn KHÔNG thể biết điều đó bởi vì:

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

Đối với tôi đây là lý do
karlihnos

5

Nếu bạn muốn đảm bảo chỉ có lớp đó sẽ phù hợp thì hãy sử dụng getClass() ==. Nếu bạn muốn phù hợp với các lớp con thì instanceofcần thiết.

Ngoài ra, instanceof sẽ không khớp với null nhưng an toàn để so sánh với null. Vì vậy, bạn không cần phải kiểm tra nó.

if ( ! (obj instanceof MyClass) ) { return false; }

Điểm thứ hai của bạn: Ông đã đề cập đến điều đó trong câu hỏi. "Tôi thường kiểm tra tùy chọn instanceof, sau đó đi vào và xóa kiểm tra 'if (obj == null)'."
Michael Myers

5

Nó phụ thuộc nếu bạn xem xét nếu một lớp con của một lớp nhất định bằng với lớp cha của nó.

class LastName
{
(...)
}


class FamilyName
extends LastName
{
(..)
}

Ở đây tôi sẽ sử dụng 'instanceof', vì tôi muốn LastName được so sánh với FamilyName

class Organism
{
}

class Gorilla extends Organism
{
}

Ở đây tôi sẽ sử dụng 'getClass', vì lớp đã nói rằng hai trường hợp này không tương đương nhau.


3

instanceof hoạt động cho các bản năng của cùng một lớp hoặc các lớp con của nó

Bạn có thể sử dụng nó để kiểm tra xem một đối tượng là một thể hiện của một lớp, một thể hiện của một lớp con hay một thể hiện của một lớp thực hiện một giao diện cụ thể.

ArryaList và RoleList đều là thể hiện Danh sách

Trong khi

getClass () == o.getClass () sẽ chỉ đúng nếu cả hai đối tượng (this và o) thuộc cùng một lớp.

Vì vậy, tùy thuộc vào những gì bạn cần so sánh, bạn có thể sử dụng cái này hay cái khác.

Nếu logic của bạn là: "Một đối tượng chỉ bằng nhau nếu cả hai cùng một lớp" thì bạn nên chọn "bằng", mà tôi nghĩ là hầu hết các trường hợp.


3

Cả hai phương pháp đều có vấn đề của họ.

Nếu lớp con thay đổi danh tính, thì bạn cần so sánh các lớp thực tế của chúng. Nếu không, bạn vi phạm tài sản đối xứng. Ví dụ, các loại khác nhau củaPerson s không nên được coi là tương đương, ngay cả khi chúng có cùng tên.

Tuy nhiên, một số lớp con không thay đổi danh tính và những thứ này cần sử dụng instanceof. Chẳng hạn, nếu chúng ta có một loạt các Shapeđối tượng bất biến , thì a Rectanglecó chiều dài và chiều rộng bằng 1 phải bằng đơn vị Square.

Trong thực tế, tôi nghĩ trường hợp trước có nhiều khả năng là sự thật. Thông thường, phân lớp là một phần cơ bản của danh tính của bạn và giống hệt cha mẹ của bạn ngoại trừ bạn có thể làm một việc nhỏ không khiến bạn trở nên bình đẳng.


-1

Trên thực tế, hãy kiểm tra xem một đối tượng có thuộc phân cấp nào đó hay không. ví dụ: Đối tượng ô tô thuộc lớp Vehical. Vì vậy, "xe mới () của Vehical" trả về đúng. Và "Xe mới (). GetClass (). Bằng (Vehical. Class)" trả về sai, mặc dù đối tượng Xe thuộc lớp Vehical nhưng nó được phân loại thành một loại riêng biệt.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.