เหตุผลใดที่จะชอบ getClass () มากกว่า instanceof เมื่อสร้าง. equals ()?


174

ฉันกำลังใช้ Eclipse เพื่อสร้าง.equals()และ.hashCode()มีตัวเลือกที่มีข้อความว่า "ใช้ 'อินสแตนซ์ของ' เพื่อเปรียบเทียบประเภท" ค่าเริ่มต้นสำหรับตัวเลือกนี้จะไม่ถูกตรวจสอบและใช้.getClass()เพื่อเปรียบเทียบประเภท มีเหตุผลใด ๆ ที่ฉันจะชอบ.getClass()มากกว่าinstanceof?

โดยไม่ต้องใช้instanceof:

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

การใช้instanceof:

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

ฉันมักจะตรวจสอบinstanceofตัวเลือกจากนั้นเข้าไปข้างในและลบเครื่องหมาย " if (obj == null)" (มันซ้ำซ้อนเนื่องจากวัตถุ null จะล้มเหลวเสมอinstanceof) มีเหตุผลใดบ้างที่เป็นความคิดที่ไม่ดี?


1
การแสดงออกx instanceof SomeClassเป็นเท็จถ้าเป็นx nullดังนั้นไวยากรณ์ที่สองไม่จำเป็นต้องมีการตรวจสอบเป็นโมฆะ
rds

5
@rds ใช่วรรคทันทีหลังจากข้อมูลโค้ดพูดเช่นนี้ มันอยู่ในข้อมูลโค้ดเพราะนั่นคือสิ่งที่ Eclipse สร้างขึ้น
กี

คำตอบ:


100

ถ้าคุณใช้instanceofทำให้การequalsดำเนินการจะรักษาสัญญาสมมาตรของวิธีการ:final x.equals(y) == y.equals(x)หากfinalดูเหมือนว่ามีข้อ จำกัด ให้ตรวจสอบความคิดเกี่ยวกับการเทียบเท่าวัตถุของคุณอย่างรอบคอบเพื่อให้แน่ใจว่าการใช้งานที่ถูกต้องของคุณจะรักษาสัญญาที่กำหนดโดยObjectชั้นเรียนอย่างเต็มที่


ที่อยู่ในใจของฉันเมื่อฉันอ่านข้อความอ้างอิงโจชโบลช +1 :)
Johannes Schaub - litb

13
การตัดสินใจที่สำคัญแน่นอน สมมาตรจะต้องนำไปใช้และ instanceof ทำให้มันง่ายมากที่จะไม่สมมาตรตั้งใจ
สกอตต์ Stanchfield

ฉันยังเพิ่มที่ "ถ้าfinalดูเหมือน จำกัด " (ทั้งสองequalsและhashCode) จากนั้นใช้getClass()ความเท่าเทียมกันแทนinstanceofเพื่อรักษาความต้องการสมมาตรและความแปรปรวนของequalsสัญญา
Shadow Man

176

Josh Blochสนับสนุนแนวทางของคุณ:

เหตุผลที่ฉันชอบinstanceofวิธีนี้คือเมื่อคุณใช้getClassวิธีนี้คุณมีข้อ จำกัด ว่าวัตถุจะเท่ากับวัตถุอื่น ๆ ในคลาสเดียวกันเท่านั้นประเภทเวลาทำงานเดียวกัน หากคุณขยายคลาสและเพิ่มวิธีการที่ไม่มีอันตรายสองสามอย่างให้ตรวจสอบเพื่อดูว่าวัตถุบางอย่างของคลาสย่อยนั้นเท่ากับวัตถุของคลาสซูเปอร์แม้ว่าวัตถุนั้นจะมีความสำคัญเท่าเทียมกันในทุกด้านคุณจะได้รับ คำตอบที่น่าประหลาดใจที่พวกเขาไม่เท่ากัน ในความเป็นจริงสิ่งนี้เป็นการละเมิดการตีความที่เข้มงวดของหลักการทดแทน Liskovและสามารถนำไปสู่พฤติกรรมที่น่าประหลาดใจมาก ใน Java มันมีความสำคัญอย่างยิ่งเพราะคอลเล็กชันส่วนใหญ่ (HashTableเป็นต้น) ขึ้นอยู่กับวิธีที่เท่าเทียมกัน หากคุณใส่สมาชิกของคลาส super ในตารางแฮชเป็นคีย์แล้วค้นหาโดยใช้อินสแตนซ์คลาสย่อยคุณจะไม่พบมันเพราะพวกมันไม่เท่ากัน

ดูคำตอบ SO นี้ด้วย

Java บทที่ 3 ที่มีประสิทธิภาพยังครอบคลุมถึงสิ่งนี้


18
+1 ทุกคนที่ทำ java ควรอ่านหนังสืออย่างน้อย 10 ครั้ง!
André

16
ปัญหาของวิธีการของ instanceof คือการแบ่งคุณสมบัติ "สมมาตร" ของ Object.equals () ซึ่งเป็นไปได้ที่ x.equals (y) == จริง แต่ y.equals (x) == false ถ้า x.getClass ()! = y.getClass () ฉันไม่ต้องการทำลายคุณสมบัตินี้เว้นแต่จำเป็นอย่างยิ่ง (เช่นการเอาชนะเท่ากับ () สำหรับวัตถุที่มีการจัดการหรือพร็อกซี)
Kevin Sitze

8
แนวทางของอินสแตนซ์นั้นเหมาะสมเมื่อและเมื่อใดระดับคลาสพื้นฐานจะกำหนดความเท่าเทียมกันของออบเจ็กต์คลาสย่อย การใช้getClassไม่ละเมิด LSP เนื่องจาก LSP เกี่ยวข้องกับสิ่งที่สามารถทำได้กับอินสแตนซ์ที่มีอยู่ไม่ใช่อินสแตนซ์ชนิดใดที่สามารถสร้างได้ คลาสที่ส่งกลับโดยgetClassเป็นคุณสมบัติที่ไม่เปลี่ยนรูปของวัตถุเช่น LSP ไม่ได้บอกเป็นนัยว่ามันควรจะเป็นไปได้ที่จะสร้างคลาสย่อยที่คุณสมบัตินั้นบ่งชี้ว่าคลาสใด ๆ นอกเหนือจากคลาสที่สร้างมันขึ้นมา
supercat

66

Angelika Langers ความลับของการเท่าเทียมกันนั้นมีการพูดคุยกันอย่างยาวนานและมีรายละเอียดสำหรับตัวอย่างทั่วไปและเป็นที่รู้จักเช่น Josh Bloch และ Barbara Liskov ค้นพบปัญหาสองสามอย่างในนั้น นอกจากนี้เธอยังได้รับในVSinstanceof getClassบางคนอ้างจากมัน

สรุปผลการวิจัย

เมื่อตัดตัวอย่างสี่ตัวอย่างที่เลือกโดยพลการของการใช้งานของ equals () เราจะสรุปได้อย่างไร

ประการแรก: มีสองวิธีที่แตกต่างกันอย่างมากในการดำเนินการตรวจสอบการจับคู่ประเภทในการดำเนินการเท่ากับ () คลาสสามารถอนุญาตให้ทำการเปรียบเทียบแบบผสมระหว่างออบเจ็กต์ super และ subclass โดยใช้ตัวดำเนินการ instanceof หรือคลาสสามารถจัดการกับออบเจ็กต์ประเภทต่าง ๆ ที่ไม่เท่ากันโดยใช้วิธีการทดสอบ getClass () ตัวอย่างข้างต้นแสดงให้เห็นอย่างชัดเจนว่าการใช้งานของ equals () โดยใช้ getClass () โดยทั่วไปมีความแข็งแกร่งกว่าการใช้งานเหล่านั้นโดยใช้ instanceof

การทดสอบ instanceof นั้นถูกต้องเฉพาะสำหรับคลาสสุดท้ายหรืออย่างน้อยเมธอดเท่ากับ () ถือเป็นที่สิ้นสุดในซูเปอร์คลาส ส่วนหลังหมายถึงว่าไม่มีคลาสย่อยต้องขยายสถานะของซูเปอร์คลาส แต่สามารถเพิ่มฟังก์ชันการทำงานหรือฟิลด์ที่ไม่เกี่ยวข้องกับสถานะและพฤติกรรมของวัตถุเช่นฟิลด์ชั่วคราวหรือสแตติก

การใช้งานโดยใช้การทดสอบ getClass () ในทางกลับกันปฏิบัติตามสัญญาเท่ากับ () เสมอ พวกเขาถูกต้องและแข็งแกร่ง อย่างไรก็ตามมันแตกต่างอย่างมากจากการนำไปใช้ที่ใช้การทดสอบอินสแตนซ์ การใช้งานโดยใช้ getClass () ไม่อนุญาตให้ทำการเปรียบเทียบ sub- กับออบเจ็กต์ superclass แม้กระทั่งเมื่อ subclass ไม่ได้เพิ่มฟิลด์ใด ๆ และไม่ต้องการแทนที่ overals () ตัวอย่างเช่นการขยายคลาส "เล็กน้อย" จะเป็นการเพิ่มวิธีการแก้ไขข้อบกพร่องในคลาสย่อยที่กำหนดไว้สำหรับจุดประสงค์ "เล็กน้อย" นี้ หากซูเปอร์คลาสห้ามทำการเปรียบเทียบแบบผสมผ่านการตรวจสอบ getClass () แสดงว่าส่วนขยายเล็กน้อยจะไม่สามารถเทียบเคียงกับซูเปอร์คลาสได้ ปัญหานี้หรือไม่นั้นขึ้นอยู่กับความหมายของคลาสและจุดประสงค์ของการขยาย


61

เหตุผลที่ใช้getClassคือเพื่อให้แน่ใจว่าคุณสมบัติสมมาตรของequalsสัญญา จาก JavaDocs เท่ากับ

มันสมมาตร: สำหรับค่าอ้างอิงที่ไม่ใช่ค่าว่างใด ๆ x และ y, x.equals (y) ควรกลับค่าจริงถ้าหาก y.equals (x) คืนค่าจริง

โดยใช้อินสแตนซ์ของมันเป็นไปได้ที่จะไม่สมมาตร ลองพิจารณาตัวอย่าง: สุนัขขยายสัตว์ สัตว์equalsใช้instanceofเช็คของสัตว์ สุนัขequalsทำการinstanceofตรวจสอบสุนัข ให้สัตว์และสุนัขd (กับสาขาอื่น ๆ เดียวกัน):

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

สิ่งนี้ละเมิดคุณสมบัติสมมาตร

ในการปฏิบัติตามสัญญาที่เท่าเทียมกันอย่างเคร่งครัดนั้นจะต้องมั่นใจในความสมมาตรดังนั้นชั้นเรียนจึงต้องเหมือนกัน


8
นั่นเป็นคำตอบแรกที่ชัดเจนและรัดกุมสำหรับคำถามนี้ ตัวอย่างโค้ดมีค่าหนึ่งพันคำ
Johnride

ฉันกำลังอ่านคำตอบ verbose อื่น ๆ ทั้งหมดที่คุณได้บันทึกไว้ในวันนั้นคำตอบที่ง่ายกระชับรัดกุมและสมบูรณ์ที่สุดของคำถามนี้

1
มีความต้องการสมมาตรตามที่คุณกล่าวถึง แต่ก็ยังมีข้อกำหนด "transitivity" ถ้าa.equals(c)และb.equals(c)แล้วa.equals(b)(วิธีการที่ไร้เดียงสาในการทำDog.equalsเพียงreturn super.equals(object)เมื่อ!(object instanceof Dog)แต่การตรวจสอบช่องเพิ่มเติมเมื่อมันเป็นเช่นสุนัขจะไม่ละเมิดสมมาตร แต่จะละเมิดกริยา)
ชาโดว์แมน

เพื่อความปลอดภัยคุณสามารถใช้การgetClass()ตรวจสอบความเท่าเทียมกันหรือคุณสามารถใช้การinstanceofตรวจสอบหากทำequalsและhashCodeวิธีการของfinalคุณ
Shadow Man

26

นี่เป็นสิ่งที่ถกเถียงทางศาสนา ทั้งสองวิธีมีปัญหา

  • ใช้instanceofและคุณจะไม่สามารถเพิ่มสมาชิกสำคัญลงในคลาสย่อยได้
  • ใช้getClassและคุณละเมิดหลักการทดแทน Liskov

Blochมีคำแนะนำอื่นที่เกี่ยวข้องในEffective Java Second Edition :

  • รายการที่ 17: การออกแบบและเอกสารสำหรับการสืบทอดหรือการห้าม

1
ไม่มีสิ่งใดเกี่ยวกับการใช้getClassจะเป็นการละเมิด LSP ยกเว้นว่าคลาสพื้นฐานจะระบุวิธีการที่ควรเป็นไปได้ที่จะสร้างอินสแตนซ์ของคลาสย่อยที่แตกต่างกันซึ่งเปรียบเทียบกัน คุณเห็นการละเมิด LSP ใด
supercat

บางทีที่ควรได้รับการใช้ถ้อยคำใหม่เป็นUse getClass และคุณอาจปล่อยให้ชนิดย่อยตกอยู่ในอันตรายจากการละเมิด LSP โบลชมีผลบังคับใช้เช่นใน Java - การยังกล่าวถึงที่นี่ เหตุผลของเขาส่วนใหญ่คือมันอาจส่งผลให้เกิดพฤติกรรมที่น่าแปลกใจสำหรับนักพัฒนาที่ใช้ชนิดย่อยที่ไม่เพิ่มรัฐ ฉันเข้าใจประเด็นของคุณ - เอกสารเป็นกุญแจสำคัญ
McDowell

2
ครั้งเดียวเท่านั้นที่คลาสของอินสแตนซ์ที่แตกต่างกันสองคลาสควรรายงานตัวเองว่าเท่ากันคือถ้าพวกมันสืบทอดจากคลาสพื้นฐานทั่วไปหรืออินเตอร์เฟสซึ่งกำหนดความเท่าเทียมกันหมายถึงอะไร [ปรัชญาที่ Java ใช้, IMHO ที่น่าสงสัย ถ้าสัญญาคลาสพื้นฐานบอกว่าgetClass()ไม่ควรถือว่ามีความหมายนอกเหนือจากความจริงที่ว่าคลาสที่มีปัญหานั้นสามารถแปลงเป็นคลาสพื้นฐานได้การส่งคืนจากgetClass()ควรได้รับการพิจารณาเหมือนทรัพย์สินอื่น ๆ ที่ต้องตรงกับอินสแตนซ์ที่เท่ากัน
supercat

1
@eyalzba โดยที่ instanceof_ ถูกใช้หาก subclass เพิ่มสมาชิกในสัญญาเท่ากับซึ่งจะเป็นการละเมิดข้อกำหนดความเท่าเทียมกันของสมมาตร การใช้คลาสย่อยgetClassต้องไม่เท่ากับชนิดพาเรนต์ ไม่ว่าคุณควรจะเอาชนะเท่ากับ แต่นี่คือการแลกเปลี่ยนถ้าคุณทำ
McDowell

2
"การออกแบบและเอกสารสำหรับการสืบทอดหรือห้าม" ฉันคิดว่ามีความสำคัญมาก ความเข้าใจของฉันคือว่าครั้งเดียวที่คุณควรแทนที่เท่ากับคือถ้าคุณกำลังสร้างชนิดค่าที่ไม่เปลี่ยนรูปแบบซึ่งในกรณีนี้ชั้นควรจะประกาศสุดท้าย เนื่องจากจะไม่มีการสืบทอดคุณมีอิสระที่จะใช้เท่ากับที่ต้องการ ในกรณีที่คุณอนุญาตให้มีการสืบทอดดังนั้นวัตถุควรเปรียบเทียบตามความเท่าเทียมกันอ้างอิง
TheSecretSquad

23

แก้ไขให้ถูกต้องหากฉันผิด แต่ getClass () จะมีประโยชน์เมื่อคุณต้องการตรวจสอบให้แน่ใจว่าอินสแตนซ์ของคุณไม่ใช่คลาสย่อยของคลาสที่คุณกำลังเปรียบเทียบ หากคุณใช้ instanceof ในสถานการณ์นั้นคุณจะไม่สามารถรู้ได้เพราะ:

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

สำหรับฉันนี่คือเหตุผล
karlihnos

5

getClass() ==ถ้าคุณต้องการเพื่อให้แน่ใจว่าเพียงว่าชั้นจะตรงกับการใช้งานแล้ว หากคุณต้องการจับคู่คลาสย่อยinstanceofจำเป็นต้องมี

นอกจากนี้อินสแตนซ์ของจะไม่จับคู่กับโมฆะ แต่ปลอดภัยที่จะเปรียบเทียบกับโมฆะ ดังนั้นคุณไม่จำเป็นต้องตรวจสอบ null

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

ประเด็นที่สองของคุณ: เขาพูดถึงเรื่องนี้แล้วในคำถาม "ฉันมักจะตรวจสอบตัวเลือกของอินสแตนซ์จากนั้นไปและลบการตรวจสอบ 'if (obj == null)'
Michael Myers

5

มันขึ้นอยู่กับว่าคุณพิจารณาว่าคลาสย่อยของคลาสที่กำหนดนั้นเท่ากับพาเรนต์หรือไม่

class LastName
{
(...)
}


class FamilyName
extends LastName
{
(..)
}

ที่นี่ฉันจะใช้ 'instanceof' เพราะฉันต้องการใช้นามสกุลเพื่อเปรียบเทียบกับ FamilyName

class Organism
{
}

class Gorilla extends Organism
{
}

ที่นี่ฉันจะใช้ 'getClass' เพราะชั้นเรียนบอกว่าทั้งสองกรณีนั้นไม่เท่ากัน


3

instanceof ใช้งานได้กับอินสแตนซ์ของคลาสเดียวกันหรือคลาสย่อย

คุณสามารถใช้มันเพื่อทดสอบว่าวัตถุเป็นอินสแตนซ์ของคลาสอินสแตนซ์ของคลาสย่อยหรืออินสแตนซ์ของคลาสที่ใช้อินเทอร์เฟซเฉพาะ

ArryaList และ RoleList เป็นทั้งสองอินสแตนซ์ของรายการ

ในขณะที่

getClass () == o.getClass ()จะเป็นจริงเฉพาะในกรณีที่วัตถุทั้งสอง (นี้และ o) เป็นของชั้นเดียวกัน

ดังนั้นขึ้นอยู่กับสิ่งที่คุณต้องการเปรียบเทียบคุณสามารถใช้อย่างใดอย่างหนึ่ง

หากตรรกะของคุณคือ: "วัตถุหนึ่งเท่ากับอีกอย่างหนึ่งก็ต่อเมื่อพวกเขาทั้งคู่เหมือนกัน" คุณควรไปที่ "เท่ากับ" ซึ่งฉันคิดว่าเป็นกรณีส่วนใหญ่


3

ทั้งสองวิธีมีปัญหา

หากคลาสย่อยเปลี่ยนข้อมูลประจำตัวคุณต้องเปรียบเทียบคลาสจริงของคลาสนั้น มิฉะนั้นคุณละเมิดคุณสมบัติสมมาตร ยกตัวอย่างเช่นประเภทต่าง ๆPersonไม่ควรได้รับการพิจารณาเทียบเท่าแม้ว่าพวกเขาจะมีชื่อเดียวกัน

อย่างไรก็ตาม subclasses instanceofบางคนไม่เปลี่ยนอัตลักษณ์และความจำเป็นเหล่านี้ไปใช้ ตัวอย่างเช่นถ้าเรามีพวงของการเปลี่ยนรูปShapeวัตถุนั้นRectangleมีความยาวและความกว้างของ 1 Squareควรจะเท่ากับหน่วย

ในทางปฏิบัติฉันคิดว่ากรณีในอดีตน่าจะเป็นจริงมากกว่า โดยปกติแล้วการแบ่งคลาสย่อยเป็นส่วนพื้นฐานของตัวตนของคุณและการเป็นเหมือนพ่อแม่ของคุณยกเว้นว่าคุณสามารถทำสิ่งเล็ก ๆ น้อย ๆ ไม่ทำให้คุณเท่าเทียมกัน


-1

อินสแตนซ์จริงของการตรวจสอบที่วัตถุอยู่ในลำดับชั้นบางส่วนหรือไม่ ตัวอย่าง: วัตถุรถยนต์เป็นของคลาส Vehical ดังนั้น "รถใหม่ () ตัวอย่างของ Vehical" จึงกลับมาจริง และ "new Car (). getClass (). equals (Vehical.class)" คืนค่าเท็จแม้ว่าวัตถุ Car นั้นเป็นของคลาส Vehical แต่มันถูกจัดหมวดหมู่เป็นประเภทแยกต่างหาก

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.