การเอาชนะวิธี java เท่ากับ () วิธี - ไม่ทำงาน?


150

ฉันพบปัญหาที่น่าสนใจ (และน่าผิดหวังมาก) กับequals()วิธีการในวันนี้ซึ่งทำให้สิ่งที่ฉันคิดว่าเป็นคลาสที่ผ่านการทดสอบที่ดีและทำให้เกิดข้อผิดพลาดซึ่งทำให้ฉันใช้เวลานานมากในการติดตาม

เพียงเพื่อความสมบูรณ์ฉันไม่ได้ใช้ IDE หรือดีบักเกอร์ - เป็นโปรแกรมแก้ไขข้อความแบบเก่าที่ดีและ System.out เวลามี จำกัด มากและเป็นโครงการโรงเรียน

อย่างไรก็ตาม -

ผมได้รับการพัฒนารถเข็นพื้นฐานซึ่งจะประกอบด้วยArrayListของBookวัตถุ เพื่อที่จะใช้addBook(), removeBook()และhasBook()วิธีการของรถเข็นที่ฉันต้องการที่จะตรวจสอบว่ามีอยู่แล้วในBook Cartฉันไปแล้ว -

public boolean equals(Book b) {
    ... // More code here - null checks
    if (b.getID() == this.getID()) return true;
    else return false;
}

ทั้งหมดทำงานได้ดีในการทดสอบ ฉันสร้างวัตถุ 6 ชิ้นและเติมข้อมูล ดำเนินการเพิ่มลบลบมี () จำนวนมากCartและทุกอย่างทำงานได้ดี ฉันอ่านว่าคุณสามารถมีequals(TYPE var)หรือequals(Object o) { (CAST) var }คิดเอาเองว่ามันทำงานได้ไม่สำคัญมาก

แล้วผมก็วิ่งเข้าไปในปัญหา - ฉันจำเป็นในการสร้างBookวัตถุที่มีเพียงIDในนั้นจากภายในชั้นหนังสือ จะไม่มีการป้อนข้อมูลอื่นเข้าไป โดยทั่วไปต่อไปนี้:

public boolean hasBook(int i) {
    Book b = new Book(i);
    return hasBook(b);
}

public boolean hasBook(Book b) {
    // .. more code here
    return this.books.contains(b);
}

ทันใดนั้นequals(Book b)วิธีการก็ใช้ไม่ได้อีกต่อไป การติดตามนี้ใช้เวลานานมากโดยไม่มีดีบักเกอร์ที่ดีและสมมติว่าCartคลาสนั้นผ่านการทดสอบและแก้ไขอย่างถูกต้อง หลังจากการแลกเปลี่ยนequals()วิธีต่อไปนี้:

public boolean equals(Object o) {
    Book b = (Book) o;
    ... // The rest goes here   
}

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


1
ฉันรู้ว่าฉันละเมิด 'สัญญา' เกี่ยวกับการเอาชนะวิธีการที่เท่าเทียมกันโดยการไตร่ตรอง - แต่ฉันต้องการวิธีที่รวดเร็วในการตรวจสอบว่าวัตถุที่มีอยู่ใน ArrayList โดยไม่ต้องใช้ข้อมูลทั่วไปหรือไม่
Josh Smeaton

1
นี่เป็นบทเรียนที่ดีในการเรียนรู้เกี่ยวกับ Java และสิ่งที่เท่าเทียมกัน
jjnguy

คำตอบ:


329

ใน Java equals()วิธีการที่สืบทอดมาObjectคือ:

public boolean equals(Object other);

ในคำอื่น ๆ Objectพารามิเตอร์จะต้องเป็นชนิด นี้เรียกว่าเอาชนะ ; วิธีการของคุณpublic boolean equals(Book other)ทำในสิ่งที่เรียกว่ามากไปequals()วิธีการ

การArrayListใช้equals()วิธีการแทนที่เพื่อเปรียบเทียบเนื้อหา (เช่นสำหรับcontains()และequals()วิธีการ) ไม่ใช้มากเกินไป ในส่วนของรหัสของคุณเรียกคนที่ไม่ได้ถูกแทนที่Object's เท่ากับถูกปรับ ArrayListแต่ไม่เข้ากันกับ

ดังนั้นการไม่แทนที่วิธีการอย่างถูกต้องอาจทำให้เกิดปัญหาได้

ฉันแทนที่เท่ากับทุกครั้งดังต่อไปนี้:

@Override
public boolean equals(Object other){
    if (other == null) return false;
    if (other == this) return true;
    if (!(other instanceof MyClass)) return false;
    MyClass otherMyClass = (MyClass)other;
    ...test other properties here...
}

การใช้@Overrideคำอธิบายประกอบสามารถช่วยตันด้วยความผิดพลาดโง่ ๆ

ใช้งานได้ทุกเมื่อที่คุณคิดว่าคุณกำลังเอาชนะวิธีการที่คลาสสุดหรืออินเทอร์เฟซ ด้วยวิธีนี้หากคุณทำผิดวิธีคุณจะได้รับข้อผิดพลาดในการคอมไพล์


31
นี้เป็นข้อโต้แย้งที่ดีในความโปรดปรานของคำอธิบายประกอบ @Override ... ถ้า OP ได้ใช้ @Override คอมไพเลอร์ของเขาจะได้บอกเขาว่าเขาไม่ได้จริงเอาชนะวิธีการเรียนผู้ปกครอง ...
แวนส์

1
ไม่เคยรับรู้ถึง @Override ขอบคุณสำหรับสิ่งนั้น! ฉันต้องการเพิ่ม hashCode () ที่เอาชนะได้จริงและควรจะพบข้อผิดพลาดในไม่ช้า
Josh Smeaton

5
IDEs บางตัว (เช่น Eclipse) สามารถแม้แต่วิธีสร้างอัตโนมัติเท่ากับ () และ hashcode () สำหรับคุณตามตัวแปรสมาชิกระดับ
sk.

1
if (!(other instanceof MyClass))return false;ผลตอบแทนfalseถ้าMyClassขยายชั้นอื่น ๆ แต่มันจะไม่กลับfalseถ้าระดับอื่น ๆ MyClassขยาย ไม่ควรequalขัดแย้งน้อยกว่านี้ใช่ไหม
Robert

19
เมื่อใช้อินสแตนซ์ของ nullcheck ก่อนหน้านี้ซ้ำซ้อน
Mateusz Dymczyk

108

หากคุณใช้ eclipse เพียงไปที่เมนูด้านบน

แหล่งที่มา -> สร้างเท่ากับ () และ hashCode ()


ฉันเห็นด้วย! อันนี้ฉันไม่เคยรู้มาก่อนและสร้างมันทำให้เกิดข้อผิดพลาดได้ง่ายน้อยลง
บอย

กันที่นี่ ขอบคุณ Fred!
Anila

16
ใน IntelliJ คุณจะพบสิ่งนี้ภายใต้รหัส→สร้าง ... หรือควบคุม + N :)
rightfold

ใน Netbeans คุณไปที่แถบเมนู> แหล่งที่มา (หรือคลิกขวา)> ใส่รหัส (หรือ Ctrl-I) และคลิกสร้างเท่ากับ () ...
โซโลมอน

11

ไม่ใช่หัวข้อของคำถามของคุณเล็กน้อย แต่ก็น่าจะกล่าวถึงต่อไป:

คอมมอนส์ Langมีวิธีการที่ยอดเยี่ยมที่คุณสามารถใช้ในการเอาชนะและแฮชโค้ด ตรวจสอบEqualsBuilder.reflectionEquals ( ... )และHashCodeBuilder.reflectionHashCode ( ... ) ในอดีตที่ผ่านมาฉันมีอาการปวดหัวมากมายแม้ว่าแน่นอนว่าถ้าคุณเพียงแค่ต้องการ "เท่ากับ" ใน ID มันอาจไม่เหมาะกับสถานการณ์ของคุณ

ฉันยอมรับด้วยเช่นกันว่าคุณควรใช้@Overrideคำอธิบายประกอบเมื่อใดก็ตามที่คุณมีค่าเท่ากับ (หรือวิธีอื่นใด)


4
หากคุณเป็นผู้ใช้ eclipse คุณสามารถไปได้right click -> source -> generate hashCode() and equals()ด้วย
tunaranch

1
ฉันถูกต้องหรือไม่ที่วิธีการเหล่านี้ถูกสั่งการในขณะทำงาน เราจะมีปัญหาด้านประสิทธิภาพหรือไม่ในกรณีที่เราสำรวจคอลเลกชันขนาดใหญ่ที่มีรายการที่ตรวจสอบพวกเขาเพื่อความเท่าเทียมกันกับรายการอื่นเนื่องจากการสะท้อน
Gaket

4

อีกวิธีที่จะช่วยประหยัดได้อย่างรวดเร็วรหัสสำเร็จรูปเป็นลอมบอก EqualsAndHashCode คำอธิบายประกอบ มันง่ายสง่างามและปรับแต่งได้ และไม่ได้ขึ้นอยู่กับการ IDE ตัวอย่างเช่น;

import lombok.EqualsAndHashCode;

@EqualsAndHashCode(of={"errorNumber","messageCode"}) // Will only use this fields to generate equals.
public class ErrorMessage{

    private long        errorNumber;
    private int         numberOfParameters;
    private Level       loggingLevel;
    private String      messageCode;

ดูตัวเลือกที่มีให้เลือกเพื่อปรับแต่งฟิลด์ที่จะใช้ในเท่ากับ ลอมบอกเป็น avalaible ในMaven เพียงเพิ่มด้วยขอบเขตที่มีให้ :

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.14.8</version>
    <scope>provided</scope>
</dependency>

1

ใน Android Studio เป็น alt + insert ---> เท่ากับและ hashCode

ตัวอย่าง:

    @Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    Proveedor proveedor = (Proveedor) o;

    return getId() == proveedor.getId();

}

@Override
public int hashCode() {
    return getId();
}

1

พิจารณา:

Object obj = new Book();
obj.equals("hi");
// Oh noes! What happens now? Can't call it with a String that isn't a Book...

1
@Elazar เป็นไงเหรอ? มีการประกาศเป็นobj Objectจุดของมรดกคือการที่คุณนั้นสามารถกำหนดเพื่อBook objหลังจากนั้นเว้นแต่คุณจะขอแนะนำว่าObjectไม่ควรจะเปรียบได้กับStringผ่านรหัสนี้ควรจะสมบูรณ์ตามกฎหมายและผลตอบแทนequals() false
bcsb1001

ฉันขอแนะนำอย่างนั้น ฉันเชื่อว่ามันเป็นที่ยอมรับอย่างกว้างขวาง
Elazar

0

instanceOfคำสั่งมักจะถูกนำมาใช้ในการดำเนินการเท่ากับ

นี่เป็นข้อผิดพลาดยอดนิยม!

ปัญหาคือการใช้instanceOfละเมิดกฎของสมมาตร:

(object1.equals(object2) == true) ถ้าและเพียงถ้า (object2.equals(object1))

ถ้าเท่ากับแรกเป็นจริงและ object2 เป็นตัวอย่างของคลาสย่อยของคลาสที่ obj1 เป็นของแล้วเท่ากับที่สองจะกลับเท็จ!

หากคลาสที่ได้รับการยกย่องซึ่ง ob1 เป็นสมาชิกถูกประกาศให้เป็นที่สิ้นสุดแล้วปัญหานี้จะไม่สามารถเกิดขึ้นได้ แต่โดยทั่วไปคุณควรทดสอบดังนี้:

this.getClass() != otherObject.getClass(); ถ้าไม่ใช่ให้คืนค่าเท็จมิฉะนั้นทดสอบฟิลด์เพื่อเปรียบเทียบความเท่าเทียมกัน!


3
ดูที่ Bloch, Java ที่มีประสิทธิภาพ,รายการที่ 8, ส่วนใหญ่พูดถึงปัญหาเกี่ยวกับการแทนที่equals()เมธอด getClass()เขาแนะนำให้ใช้ เหตุผลหลักคือการทำเช่นนั้นจะทำลายหลักการการทดแทน Liskov สำหรับคลาสย่อยที่ไม่ส่งผลกระทบต่อความเท่าเทียมกัน
Stuart Marks

-1

recordId เป็นคุณสมบัติของวัตถุ

@Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Nai_record other = (Nai_record) obj;
        if (recordId == null) {
            if (other.recordId != null)
                return false;
        } else if (!recordId.equals(other.recordId))
            return false;
        return true;
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.