วิธีการแทนที่วิธีการเท่ากับใน Java


108

ฉันกำลังพยายามแทนที่วิธีการเท่ากับใน Java ฉันมีคลาสPeopleซึ่งโดยทั่วไปมี 2 ช่องข้อมูลnameและage. ตอนนี้ฉันต้องการแทนที่equalsวิธีการเพื่อให้ฉันสามารถตรวจสอบระหว่างวัตถุ 2 คนได้

รหัสของฉันมีดังนี้

public boolean equals(People other){
    boolean result;
    if((other == null) || (getClass() != other.getClass())){
        result = false;
    } // end if
    else{
        People otherPeople = (People)other;
        result = name.equals(other.name) &&  age.equals(other.age);
    } // end else

    return result;
} // end equals

แต่เมื่อฉันเขียนage.equals(other.age)มันทำให้ฉันมีข้อผิดพลาดเป็นวิธีการเท่ากับสามารถเปรียบเทียบสตริงเท่านั้นและอายุคือจำนวนเต็ม

สารละลาย

ฉันใช้==ตัวดำเนินการตามที่แนะนำและปัญหาของฉันได้รับการแก้ไขแล้ว


3
เฮ้เกี่ยวกับ this.age == other.age? :)
denis.solonenko

1
ประเภทข้อมูลสำหรับอายุคืออะไร? int หรือจำนวนเต็ม? นอกจากนี้คุณใช้ JDK เวอร์ชันใดอยู่
Manish

2
"as equals method สามารถเปรียบเทียบ String ได้เท่านั้น" - ใครบอกคุณว่า method equals สามารถเปรียบเทียบ String ได้เท่านั้น? วิธีการเท่ากับเป็นของคลาส Object และคลาสใด ๆ ที่สร้างขึ้นจะมีการใช้งานเท่ากับโดยค่าเริ่มต้น คุณสามารถเรียกเท่ากับในคลาส Java ใดก็ได้
Manish

คำตอบ:


128
//Written by K@stackoverflow
public class Main {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        // TODO code application logic here
        ArrayList<Person> people = new ArrayList<Person>();
        people.add(new Person("Subash Adhikari", 28));
        people.add(new Person("K", 28));
        people.add(new Person("StackOverflow", 4));
        people.add(new Person("Subash Adhikari", 28));

        for (int i = 0; i < people.size() - 1; i++) {
            for (int y = i + 1; y <= people.size() - 1; y++) {
                boolean check = people.get(i).equals(people.get(y));

                System.out.println("-- " + people.get(i).getName() + " - VS - " + people.get(y).getName());
                System.out.println(check);
            }
        }
    }
}

//written by K@stackoverflow
public class Person {
    private String name;
    private int age;

    public Person(String name, int age){
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }

        if (obj.getClass() != this.getClass()) {
            return false;
        }

        final Person other = (Person) obj;
        if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) {
            return false;
        }

        if (this.age != other.age) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        int hash = 3;
        hash = 53 * hash + (this.name != null ? this.name.hashCode() : 0);
        hash = 53 * hash + this.age;
        return hash;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

เอาท์พุต:

วิ่ง:

- Subash Adhikari - VS - K false

- Subash Adhikari - VS - StackOverflow เท็จ

- Subash Adhikari - VS - Subash Adhikari จริง

- K - VS - StackOverflow เท็จ

- K - VS - Subash Adhikari เท็จ

- StackOverflow - VS - Subash Adhikari false

- BUILD SUCCESSFUL (เวลารวม: 0 วินาที)


7
สิ่งที่เป็นhash = 53 * hashเหตุผลที่คุณจะใช้ที่?
kittu

2
การใช้getClass()จะทำให้เกิดปัญหาหากคลาสได้รับคลาสย่อยและถูกเปรียบเทียบกับอ็อบเจ็กต์ของซูเปอร์คลาส
Tuxdude

1
อาจจะเป็น bcoz 53เป็นจำนวนเฉพาะลองดูที่คำตอบนี้stackoverflow.com/a/27609/3425489เขาแสดงความคิดเห็นขณะเลือกตัวเลขในhashCode()
Shantaram Tupe

1
คำตอบที่ชนะสำหรับคำถามนี้มีคำอธิบายที่ดีเยี่ยมว่าเหตุใดคุณจึงแทนที่ hashCode () stackoverflow.com/a/27609/1992108
Pegasaurus

7
พิจารณาใช้ if (getClass ()! = obj.getClass ()) ... แทนที่จะใช้ตัวinstanceofดำเนินการหรือisAssignableFrom. สิ่งนี้จะต้องมีการจับคู่ประเภทตรงทั้งหมดมากกว่าการจับคู่ประเภทย่อย - ข้อกำหนดสมมาตร นอกจากนี้ยังเพื่อเปรียบเทียบStringหรือชนิดวัตถุอื่น ๆ Objects.equals(this.name,other.name)ที่คุณสามารถใช้
YoYo

22

แนะนำลายเซ็นวิธีการใหม่ที่เปลี่ยนชนิดพารามิเตอร์เรียกว่าโอเวอร์โหลด :

public boolean equals(People other){

ที่นี่Peopleแตกต่างจากObject.

เมื่อลายเซ็นเมธอดยังคงเหมือนกันกับของซุปเปอร์คลาสจะเรียกว่าการลบล้างและ@Overrideคำอธิบายประกอบจะช่วยแยกความแตกต่างทั้งสองในเวลาคอมไพล์:

@Override
public boolean equals(Object other){

หากไม่เห็นคำประกาศที่แท้จริงageก็ยากที่จะบอกว่าทำไมข้อผิดพลาดจึงปรากฏขึ้น


18

ฉันไม่แน่ใจในรายละเอียดเนื่องจากคุณไม่ได้โพสต์รหัสทั้งหมด แต่:

  • อย่าลืมลบล้างhashCode()ด้วย
  • equalsวิธีการควรจะมีObjectไม่ได้Peopleเป็นชนิดอาร์กิวเมนต์ ในขณะที่คุณทำงานหนักเกินไปไม่ได้ลบล้างวิธีการเท่ากับซึ่งอาจไม่ใช่สิ่งที่คุณต้องการโดยเฉพาะอย่างยิ่งเมื่อคุณตรวจสอบประเภทในภายหลัง
  • คุณสามารถใช้instanceofเพื่อตรวจสอบว่าเป็นวัตถุ People เช่นif (!(other instanceof People)) { result = false;}
  • equalsใช้สำหรับวัตถุทั้งหมด แต่ไม่ใช่วัตถุดั้งเดิม ฉันคิดว่าคุณหมายถึงอายุเป็นint(ดั้งเดิม) ==ซึ่งในกรณีนี้เป็นเพียงแค่การใช้งาน โปรดทราบว่าจำนวนเต็ม (ที่มีตัวพิมพ์ใหญ่ 'I') เป็นวัตถุที่ควรเปรียบเทียบด้วยเท่ากับ

ดูประเด็นใดที่ควรพิจารณาเมื่อลบล้างค่าเท่ากับและ hashCode ใน Java สำหรับรายละเอียดเพิ่มเติม


12
@Override
public boolean equals(Object that){
  if(this == that) return true;//if both of them points the same address in memory

  if(!(that instanceof People)) return false; // if "that" is not a People or a childclass

  People thatPeople = (People)that; // than we can cast it to People safely

  return this.name.equals(thatPeople.name) && this.age == thatPeople.age;// if they have the same name and same age, then the 2 objects are equal unless they're pointing to different memory adresses
}

12

ข้อ 10: ปฏิบัติตามสัญญาทั่วไปเมื่อลบล้างความเท่าเทียมกัน

ตามผลของ Java การลบล้างequalsวิธีการนี้ดูเหมือนง่าย แต่มีหลายวิธีที่จะทำให้ผิดพลาดและผลที่ตามมาอาจเลวร้าย วิธีที่ง่ายที่สุดในการหลีกเลี่ยงปัญหาคืออย่าลบล้างequalsเมธอดซึ่งในกรณีนี้แต่ละอินสแตนซ์ของคลาสจะเท่ากับตัวมันเองเท่านั้น นี่เป็นสิ่งที่ถูกต้องที่จะทำหากมีเงื่อนไขต่อไปนี้:

  • ตัวอย่างของการเรียนแต่ละคนมีความเป็นเอกลักษณ์โดยเนื้อแท้ นี่เป็นจริงสำหรับคลาสเช่นเธรดที่แสดงถึงเอนทิตีที่ใช้งานอยู่แทนที่จะเป็นค่า การใช้งานที่เท่าเทียมกันโดย Object มีลักษณะการทำงานที่เหมาะสมสำหรับคลาสเหล่านี้

  • ชั้นเรียนไม่จำเป็นต้องจัดให้มีการทดสอบ "ความเท่าเทียมทางตรรกะ" ตัวอย่างเช่น java.util.regex.Pattern อาจมีการแทนที่เท่ากับเพื่อตรวจสอบว่าอินสแตนซ์ Pattern สองรายการแสดงนิพจน์ทั่วไปที่เหมือนกันหรือไม่ แต่นักออกแบบไม่คิดว่าลูกค้าจะต้องการหรือต้องการฟังก์ชันนี้ ภายใต้สถานการณ์เหล่านี้การนำไปใช้อย่างเท่าเทียมกันที่สืบทอดมาจาก Object นั้นเหมาะอย่างยิ่ง

  • ซูเปอร์คลาสได้ลบล้างค่าเท่ากับแล้วและพฤติกรรมของคลาสระดับสูงนั้นเหมาะสมสำหรับคลาสนี้ ตัวอย่างเช่นการใช้งาน Set ส่วนใหญ่จะสืบทอดการใช้งานที่เท่าเทียมกันจาก AbstractSet รายการการใช้งานจาก AbstractList และการใช้งานแผนที่จาก AbstractMap

  • คลาสเป็นแบบส่วนตัวหรือแบบแพ็กเกจและคุณมั่นใจว่าจะไม่มีการเรียกใช้เมธอด equals หากคุณไม่ชอบความเสี่ยงอย่างยิ่งคุณสามารถลบล้างวิธีการเท่ากับเพื่อให้แน่ใจว่าจะไม่ถูกเรียกใช้โดยไม่ได้ตั้งใจ:

equalsวิธีการดำเนินการความสมดุล มีคุณสมบัติเหล่านี้:

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

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

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

  • สอดคล้องกัน: สำหรับค่าอ้างอิงที่ไม่เป็นค่าว่างxและyการเรียกใช้หลายรายการx.equals(y)ต้องส่งคืนtrueอย่างสม่ำเสมอหรือส่งคืนอย่างสม่ำเสมอfalseหากไม่มีการแก้ไขข้อมูลที่ใช้ในการเปรียบเทียบเท่ากับ

  • สำหรับค่าอ้างอิงใด ๆ ที่ไม่ใช่ null x, ต้องกลับx.equals(null)false

นี่คือสูตรสำหรับวิธีการเท่ากับคุณภาพสูง:

  1. ใช้ตัว==ดำเนินการเพื่อตรวจสอบว่าอาร์กิวเมนต์อ้างอิงถึงออบเจ็กต์นี้หรือไม่ ถ้าเป็นเช่นนั้นให้คืนค่าจริง นี่เป็นเพียงการเพิ่มประสิทธิภาพ แต่สิ่งหนึ่งที่คุ้มค่าหากการเปรียบเทียบอาจมีราคาแพง

  2. ใช้ตัวinstanceofดำเนินการเพื่อตรวจสอบว่าอาร์กิวเมนต์มีประเภทที่ถูกต้องหรือไม่ ถ้าไม่ให้ส่งคืนเท็จ โดยทั่วไปประเภทที่ถูกต้องคือคลาสที่เมธอดเกิดขึ้น ในบางครั้งอินเทอร์เฟซบางอย่างที่ใช้โดยคลาสนี้ ใช้อินเทอร์เฟซหากคลาสใช้อินเทอร์เฟซที่ปรับแต่งสัญญาเท่ากับเพื่ออนุญาตการเปรียบเทียบระหว่างคลาสที่ใช้อินเทอร์เฟซ อินเทอร์เฟซการรวบรวมเช่นชุดรายการแผนที่และแผนที่รายการมีคุณสมบัตินี้

  3. ส่งอาร์กิวเมนต์เป็นประเภทที่ถูกต้อง เนื่องจากนักแสดงนี้นำหน้าด้วยการทดสอบเช่นกันจึงรับประกันได้ว่าจะประสบความสำเร็จ

  4. สำหรับแต่ละฟิลด์ "สำคัญ" ในคลาสให้ตรวจสอบว่าฟิลด์ของอาร์กิวเมนต์นั้นตรงกับฟิลด์ที่เกี่ยวข้องของออบเจ็กต์นี้หรือไม่ หากการทดสอบทั้งหมดนี้สำเร็จให้คืนค่าจริง มิฉะนั้นให้คืนค่าเท็จ หากประเภทในขั้นตอนที่ 2 เป็นอินเทอร์เฟซคุณต้องเข้าถึงฟิลด์ของอาร์กิวเมนต์ผ่านวิธีการอินเตอร์เฟส หากประเภทเป็นคลาสคุณอาจเข้าถึงฟิลด์ได้โดยตรงขึ้นอยู่กับความสามารถในการเข้าถึง

  5. สำหรับเขตข้อมูลดั้งเดิมที่ไม่มีประเภทfloatหรือdoubleใช้ตัว==ดำเนินการเพื่อเปรียบเทียบ สำหรับฟิลด์อ้างอิงวัตถุให้เรียกใช้equalsเมธอดซ้ำ สำหรับfloatฟิลด์ให้ใช้Float.compare(float, float)วิธีการแบบคงที่ และสาขาที่ใช้double Double.compare(double, double)การรักษาพิเศษของลอยและสาขาสองครั้งที่จะทำสิ่งที่จำเป็นโดยการดำรงอยู่ของFloat.NaN, -0.0fและค่าคู่คล้าย; ในขณะที่คุณสามารถเปรียบเทียบfloatและdoubleฟิลด์ด้วยวิธีการคงที่Float.equalsและDouble.equalsสิ่งนี้จะนำไปสู่การทำ autoboxing ในทุกการเปรียบเทียบซึ่งจะมีประสิทธิภาพต่ำ สำหรับarrayช่องให้ใช้แนวทางเหล่านี้กับแต่ละองค์ประกอบ หากทุกองค์ประกอบในช่องอาร์เรย์มีความสำคัญให้ใช้Arrays.equalsวิธีใดวิธีหนึ่ง

  6. nullบางเขตข้อมูลอ้างอิงวัตถุถูกต้องตามกฎหมายอาจมี เพื่อหลีกเลี่ยงความเป็นไปได้ของการNullPointerExceptionตรวจสอบสาขาต่าง ๆ Objects.equals(Object, Object)เพื่อความเท่าเทียมกันโดยใช้วิธีการคง

    // Class with a typical equals method
    
    public final class PhoneNumber {
    
        private final short areaCode, prefix, lineNum;
    
        public PhoneNumber(int areaCode, int prefix, int lineNum) {
    
            this.areaCode = rangeCheck(areaCode,  999, "area code");
    
            this.prefix   = rangeCheck(prefix,    999, "prefix");
    
            this.lineNum  = rangeCheck(lineNum,  9999, "line num");
    
        }
    
        private static short rangeCheck(int val, int max, String arg) {
    
            if (val < 0 || val > max)
    
               throw new IllegalArgumentException(arg + ": " + val);
    
            return (short) val;
    
        }
    
        @Override public boolean equals(Object o) {
            if (o == this)
                return true;
            if (!(o instanceof PhoneNumber))
                return false;
            PhoneNumber pn = (PhoneNumber)o;
            return pn.lineNum == lineNum && pn.prefix == prefix
                    && pn.areaCode == areaCode;
        }
        ... // Remainder omitted
    
    }

1
อย่าลืมระบุว่าคุณต้องลบล้างhashCode()ด้วย นอกจากนี้ยังทราบว่าตั้งแต่ Java7 เขียนequals()และhashCode()วิธีการได้กลายเป็นเรื่องง่ายโดยใช้Objects.equals(), Arrays.equals()และ,Objects.hashCode() Arrays.hashCode()
Arnold Schrijver

3
พิจารณาใช้if (getClass() != obj.getClass()) ...แทนที่จะใช้ตัวดำเนินการ instanceof นี้จะต้องมีแน่นอนการแข่งขันประเภทมากกว่าการแข่งขันประเภทย่อย - ข้อกำหนดสมมาตร
YoYo

@YoYo ถูกต้อง ... การใช้ instanceof อาจทำให้คุณสมบัติสมมาตรล้มเหลว ถ้า o เป็นคลาสย่อยของ PhoneNumber เช่นอาจจะเป็น PhoneNumberWithExtension และจะแทนที่ด้วยวิธีเดียวกันโดยใช้ instanceof ดังนั้น o.equals (สิ่งนี้) จะล้มเหลวในการทดสอบอินสแตนซ์ในขณะที่ PhoneNumber.equals จะผ่านและส่งคืนจริง (สมมติว่าฟิลด์ PhoneNumber อื่น ๆ ทั้งหมด มีค่าเท่ากัน)
ldkronos

5

เนื่องจากฉันเดาว่าageเป็นประเภทint:

public boolean equals(Object other){
    boolean result;
    if((other == null) || (getClass() != other.getClass())){
        result = false;
    } // end if
    else{
        People otherPeople = (People)other;
        result = name.equals(otherPeople.name) &&  age == otherPeople.age;
    } // end else

    return result;
} // end equals

ซึ่งจะส่งผลในNullPointerExceptionถ้าเป็นname null
orien

@orien ไม่ใช่เรื่องใหญ่บางทีมันอาจจะอยู่ในสัญญาที่nameไม่เคยได้รับการกำหนดnullค่า ...
fortran

@fortran ดังนั้น ... อาจจะไม่ใช่เรื่องใหญ่;)
orien

5

เมื่อเปรียบเทียบอ็อบเจ็กต์ใน Java คุณทำการตรวจสอบความหมายเปรียบเทียบประเภทและสถานะการระบุอ็อบเจ็กต์กับ:

  • ตัวเอง (เช่นเดียวกัน)
  • ตัวเอง (โคลนหรือสำเนาที่สร้างขึ้นใหม่)
  • วัตถุอื่น ๆ ประเภทต่างๆ
  • วัตถุอื่น ๆ ประเภทเดียวกัน
  • null

กฎ:

  • สมมาตร :a.equals(b) == b.equals(a)
  • equals()มักจะมีอัตราผลตอบแทนtrueหรือfalseแต่ไม่เคยNullpointerException, ClassCastExceptionหรือ Throwable อื่น ๆ

เปรียบเทียบ:

  • การตรวจสอบประเภท : อินสแตนซ์ทั้งสองต้องเป็นประเภทเดียวกันหมายความว่าคุณต้องเปรียบเทียบคลาสจริงเพื่อความเท่าเทียมกัน นี้มักจะไม่ได้ดำเนินการอย่างถูกต้องเมื่อนักพัฒนาใช้instanceofสำหรับการเปรียบเทียบชนิด A extends B -> a instanceof b != b instanceof a)(ซึ่งจะทำงานเฉพาะตราบใดที่ไม่มีการย่อยและละเมิดกฎสมมาตรเมื่อ
  • การตรวจสอบความหมายของสถานะการระบุ : ตรวจสอบว่าคุณเข้าใจว่าอินสแตนซ์ระบุสถานะใด บุคคลอาจถูกระบุโดยหมายเลขประกันสังคม แต่ไม่ระบุด้วยสีผม (สามารถย้อมได้) ชื่อ (สามารถเปลี่ยนแปลงได้) หรืออายุ (เปลี่ยนแปลงตลอดเวลา) เฉพาะกับออบเจ็กต์ค่าคุณควรเปรียบเทียบสถานะเต็ม (ฟิลด์ที่ไม่ใช่ชั่วคราวทั้งหมด) มิฉะนั้นให้ตรวจสอบเฉพาะสิ่งที่ระบุอินสแตนซ์

สำหรับPersonชั้นเรียนของคุณ:

public boolean equals(Object obj) {

    // same instance
    if (obj == this) {
        return true;
    }
    // null
    if (obj == null) {
        return false;
    }
    // type
    if (!getClass().equals(obj.getClass())) {
        return false;
    }
    // cast and compare state
    Person other = (Person) obj;
    return Objects.equals(name, other.name) && Objects.equals(age, other.age);
}

คลาสยูทิลิตี้ทั่วไปที่ใช้ซ้ำได้:

public final class Equals {

    private Equals() {
        // private constructor, no instances allowed
    }

    /**
     * Convenience equals implementation, does the object equality, null and type checking, and comparison of the identifying state
     *
     * @param instance       object instance (where the equals() is implemented)
     * @param other          other instance to compare to
     * @param stateAccessors stateAccessors for state to compare, optional
     * @param <T>            instance type
     * @return true when equals, false otherwise
     */
    public static <T> boolean as(T instance, Object other, Function<? super T, Object>... stateAccessors) {
        if (instance == null) {
            return other == null;
        }
        if (instance == other) {
            return true;
        }
        if (other == null) {
            return false;
        }
        if (!instance.getClass().equals(other.getClass())) {
            return false;
        }
        if (stateAccessors == null) {
            return true;
        }
        return Stream.of(stateAccessors).allMatch(s -> Objects.equals(s.apply(instance), s.apply((T) other)));
    }
}

สำหรับPersonชั้นเรียนของคุณโดยใช้คลาสยูทิลิตี้นี้:

public boolean equals(Object obj) {
    return Equals.as(this, obj, t -> t.name, t -> t.age);
}

1

หากอายุเป็น int คุณควรใช้ == ถ้าเป็นวัตถุจำนวนเต็มคุณสามารถใช้ equals () คุณต้องใช้วิธีการแฮชโค้ดด้วยหากคุณลบล้างเท่ากับ รายละเอียดของสัญญามีอยู่ใน javadoc ของ Object และตามหน้าต่างๆในเว็บ


0

นี่คือวิธีแก้ปัญหาที่ฉันเพิ่งใช้:

public class Test {
    public String a;
    public long b;
    public Date c;
    public String d;
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Test)) {
            return false;
        }
        Test testOther = (Test) obj;
        return (a != null ? a.equals(testOther.a) : testOther.a == null)
                && (b == testOther.b)
                && (c != null ? c.equals(testOther.c) : testOther.c == null)
                && (d != null ? d.equals(testOther.d) : testOther.d == null);
    }

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