Apache Commons เท่ากับ / hashCode builder [ปิด]


155

ฉันอยากจะรู้ว่าสิ่งที่ผู้คนที่นี่คิดเกี่ยวกับการใช้ org.apache.commons.lang.builder EqualsBuilder/ HashCodeBuilder สำหรับการดำเนินการequals/ hashCode? มันจะเป็นการปฏิบัติที่ดีกว่าการเขียนของคุณเอง? เล่นกับไฮเบอร์เนตได้ดีหรือไม่? ความคิดเห็นของคุณคืออะไร


16
เพียงแค่ไม่ถูกล่อลวงโดยreflectionEqualsและreflectionHashcodeฟังก์ชั่น; ประสิทธิภาพเป็นนักฆ่าที่สมบูรณ์
skaffman

14
ฉันเห็นการสนทนาเกี่ยวกับที่นี่เมื่อวานนี้และมีเวลาว่างดังนั้นฉันจึงทำการทดสอบอย่างรวดเร็ว ฉันมีวัตถุ 4 ชิ้นที่มีการใช้งานที่แตกต่างกัน eclipse สร้างแล้ว, equalsbuilder.append, equalsbuilder.reflection และหมายเหตุประกอบแบบ pojomatic พื้นฐานคือคราส equalsbuilder.append เอา 3.7x pojomatic เอา 5x การสะท้อนใช้เวลา 25.8x มันค่อนข้างท้อใจเพราะฉันชอบความเรียบง่ายของการสะท้อนและฉันไม่สามารถยืนชื่อ "pojomatic" ได้
digitaljoel

5
ตัวเลือกอื่นคือโครงการลอมบอก; มันใช้การสร้าง bytecode มากกว่าการสะท้อนดังนั้นมันควรจะทำงานได้ดีเช่นเดียวกับที่สร้างโดย Eclipse projectlombok.org/features/EqualsAndHashCode.html
ไมล์

คำตอบ:


212

ผู้สร้างคอมมอนส์ / lang นั้นยอดเยี่ยมและฉันใช้มันมาหลายปีโดยไม่มีค่าใช้จ่ายด้านประสิทธิภาพที่เห็นได้ชัดเจน (ทั้งที่มีและไม่มีไฮเบอร์เนต) แต่ในขณะที่ Alain เขียนวิธีของ Guava นั้นดีกว่า:

นี่คือตัวอย่างถั่ว:

public class Bean{

    private String name;
    private int length;
    private List<Bean> children;

}

นี่คือสมการ () และ hashCode () ที่ใช้กับ Commons / Lang:

@Override
public int hashCode(){
    return new HashCodeBuilder()
        .append(name)
        .append(length)
        .append(children)
        .toHashCode();
}

@Override
public boolean equals(final Object obj){
    if(obj instanceof Bean){
        final Bean other = (Bean) obj;
        return new EqualsBuilder()
            .append(name, other.name)
            .append(length, other.length)
            .append(children, other.children)
            .isEquals();
    } else{
        return false;
    }
}

และที่นี่ด้วย Java 7 หรือสูงกว่า (ได้แรงบันดาลใจจาก Guava):

@Override
public int hashCode(){
    return Objects.hash(name, length, children);
}

@Override
public boolean equals(final Object obj){
    if(obj instanceof Bean){
        final Bean other = (Bean) obj;
        return Objects.equals(name, other.name)
            && length == other.length // special handling for primitives
            && Objects.equals(children, other.children);
    } else{
        return false;
    }
}

หมายเหตุ: รหัสนี้เดิมอ้างถึง Guava แต่เนื่องจากความคิดเห็นได้ชี้ให้เห็นว่าการทำงานนี้ได้รับการแนะนำใน JDK ดังนั้น Guava จึงไม่จำเป็นต้องใช้อีกต่อไป

อย่างที่คุณเห็นว่ารุ่น Guava / JDK นั้นสั้นกว่าและหลีกเลี่ยงวัตถุตัวช่วยที่ฟุ่มเฟือย ในกรณีที่เท่ากันมันยังช่วยให้การประเมินผลลัดวงจรถ้าการObject.equals()โทรก่อนหน้านี้กลับเท็จ (เป็นธรรม: สามัญ / lang มีObjectUtils.equals(obj1, obj2)วิธีการที่มีความหมายเหมือนกันซึ่งสามารถนำมาใช้แทนEqualsBuilderการอนุญาตให้ลัดวงจรดังกล่าวข้างต้น)

ดังนั้น: ใช่ผู้สร้างคอมมอนส์ lang จะเป็นที่นิยมมากกว่าการสร้างด้วยตนเองequals()และhashCode()วิธีการ (หรืออสุรกายที่น่ากลัว Eclipse จะสร้างให้คุณ) แต่เวอร์ชัน Java 7+ / Guava ดีกว่ามาก

และหมายเหตุเกี่ยวกับไฮเบอร์เนต:

ระวังเกี่ยวกับการใช้งานคอลเล็กชันสันหลังยาวในการใช้งานเท่ากับ (), hashCode () และ toString () นั่นจะล้มเหลวอย่างน่าสังเวชถ้าคุณไม่มีเซสชันที่เปิดอยู่


หมายเหตุ (ประมาณเท่ากับ ()):

ก) ในทั้งสองรุ่นมีค่าเท่ากับ () ด้านบนคุณอาจต้องการใช้ทางลัดหนึ่งหรือทั้งสองอย่างด้วยเช่นกัน:

@Override
public boolean equals(final Object obj){
    if(obj == this) return true;  // test for reference equality
    if(obj == null) return false; // test for null
    // continue as above

b) ทั้งนี้ขึ้นอยู่กับการตีความสัญญาของคุณเท่ากับคุณอาจเปลี่ยนบรรทัด

    if(obj instanceof Bean){

ถึง

    // make sure you run a null check before this
    if(obj.getClass() == getClass()){ 

หากคุณใช้รุ่นที่สองคุณอาจต้องการโทรsuper(equals())ภายในequals()วิธีการของคุณ ความคิดเห็นที่แตกต่างกันที่นี่หัวข้อจะกล่าวถึงในคำถามนี้:

วิธีที่ถูกต้องในการรวมซูเปอร์คลาสเข้ากับการใช้งาน Guava Objects.hashcode ()?

(แม้ว่าจะเป็นเรื่องhashCode()เดียวกันก็ใช้กับequals())


หมายเหตุ (ได้แรงบันดาลใจจากความคิดเห็นจากkayahr )

Objects.hashCode(..)(เช่นเดียวกับต้นแบบArrays.hashCode(...)) อาจทำงานได้ไม่ดีหากคุณมีฟิลด์ดั้งเดิมจำนวนมาก ในกรณีเช่นนี้EqualsBuilderอาจเป็นทางออกที่ดีกว่า


34
จะเป็นไปได้เช่นเดียวกันกับ Java 7 Objects.equals: download.oracle.com/javase/7/docs/api/java/util/ …
Thomas Jung

3
ถ้าฉันอ่านมันถูกต้อง Josh Bloch จะพูดในEffective Java , Item 8 ว่าคุณไม่ควรใช้ getClass () ใน method equals () ของคุณ คุณควรใช้ instanceof แทน
เจฟฟ์โอลสัน

6
@SeanPatrickFloyd The Guava-way ไม่เพียง แต่สร้างอ็อบเจกต์อาร์เรย์สำหรับ varargs เท่านั้น แต่ยังแปลงพารามิเตอร์ทั้งหมดเป็นออบเจ็กต์ ดังนั้นเมื่อคุณส่งค่า int 10 ค่าไปคุณจะได้วัตถุ 10 ตัวรวมกันและวัตถุอาร์เรย์ วิธีการแก้ปัญหาทั่วไปจะสร้างวัตถุเดียวเท่านั้นไม่ว่าคุณผนวกค่าแฮชโค้ดจำนวนเท่าใด equalsปัญหาเดียวกันกับ Guava แปลงค่าทั้งหมดเป็นวัตถุโดยทั่วไปจะสร้างวัตถุใหม่เพียงรายการเดียวเท่านั้น
kayahr

1
@ วอนฮีฉันไม่เห็นด้วยอย่างยิ่งว่านี่จะดีกว่า การใช้ Reflection เพื่อคำนวณรหัสแฮชไม่ใช่สิ่งที่ฉันจะทำ ค่าใช้จ่ายในการปฏิบัติงานอาจเล็กน้อย แต่ก็รู้สึกผิด
ฌอนแพทริคฟลอยด์

1
@kaushik การทำให้คลาสสุดท้ายเป็นจริงแก้ปัญหาที่อาจเกิดขึ้นของทั้งสองเวอร์ชัน (instanceof และ getClass ()) ตราบใดที่คุณใช้ equals ของคุณ () ในคลาสใบไม้เท่านั้น
Sean Patrick Floyd

18

ตื่นเถิด! ตั้งแต่ Java 7มีเมธอดตัวช่วยสำหรับเท่ากับและhashCodeในไลบรารีมาตรฐาน การใช้งานของพวกเขาเทียบเท่ากับการใช้วิธีการของ Guava อย่างเต็มที่


ก) ในเวลาที่คำถามนี้ถูกถาม Java 7 ยังไม่ได้ข) ในทางเทคนิคพวกเขาไม่เท่ากัน jdk มีวิธี Objects.equals เทียบกับวิธี Objects.equal ของ Guava ฉันสามารถใช้การนำเข้าแบบคงที่กับรุ่นของ Guava เท่านั้น นั่นเป็นเพียงเครื่องสำอางฉันรู้ แต่มันทำให้คนที่ไม่ใช่ฝรั่งมีความยุ่งเหยิงมากขึ้นอย่างเห็นได้ชัด
ฌอนแพทริคฟลอยด์

นี่ไม่ใช่วิธีที่ดีในการลบล้างออบเจ็กต์เท่ากับเมธอดเนื่องจาก Objects.equals จะเรียกเมธอด. equals ของอินสแตนซ์ หากคุณเรียก Objects.equals ภายในวิธีการ. equals ของอินสแตนซ์มันจะนำไปสู่การล้นสแต็ค
dardo

คุณยกตัวอย่างได้ไหมเมื่อมันตกอยู่ในวงวน
Mikhail Golubtsov

OP ขอให้แทนที่เมธอด equals () ภายใน Object ตามเอกสารของวิธีการคงที่ Objects.equals (): "ผลตอบแทนจริงถ้าข้อโต้แย้งมีค่าเท่ากันและเท็จอย่างอื่นดังนั้นถ้าอาร์กิวเมนต์ทั้งสองเป็นโมฆะจริงจะถูกส่งกลับและถ้าอาร์กิวเมนต์หนึ่งเป็นโมฆะเท็จเป็นเท็จ กลับมามิฉะนั้นความเท่าเทียมกันจะถูกกำหนดโดยใช้วิธีการเท่ากับของอาร์กิวเมนต์แรก "ดังนั้นหากคุณใช้ Objects.equals () ภายในอินสแตนซ์แทนที่ () มันจะเรียกว่าเป็นวิธีการของตัวเองแล้ว Objects.equals () จากนั้นตัวเองอีกครั้งให้กองล้น
dardo

@dardo เรากำลังพูดถึงการนำความเท่าเทียมกันของโครงสร้างมาใช้ดังนั้นมันจึงหมายความว่าวัตถุสองรายการนั้นมีค่าเท่ากันถ้าเขตข้อมูลของพวกมันทำ ดูตัวอย่างของ Guava ด้านบนวิธีการใช้งานเท่ากับ
มิคาอิล Golubtsov

8

หากคุณไม่ต้องการพึ่งพาห้องสมุดบุคคลที่สาม (บางทีคุณกำลังเรียกใช้อุปกรณ์ที่มีทรัพยากร จำกัด ) และคุณไม่ต้องการพิมพ์วิธีการของคุณเองคุณสามารถปล่อยให้ IDE ทำงานเช่นในการใช้ eclipse

Source -> Generate hashCode() and equals()...

คุณจะได้รับรหัส 'พื้นเมือง' ซึ่งคุณสามารถกำหนดค่าได้ตามที่คุณต้องการและสิ่งที่คุณต้องทำการสนับสนุนเกี่ยวกับการเปลี่ยนแปลง


ตัวอย่าง (eclipse Juno):

import java.util.Arrays;
import java.util.List;

public class FooBar {

    public String string;
    public List<String> stringList;
    public String[] stringArray;

    /* (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((string == null) ? 0 : string.hashCode());
        result = prime * result + Arrays.hashCode(stringArray);
        result = prime * result
                + ((stringList == null) ? 0 : stringList.hashCode());
        return result;
    }
    /* (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        FooBar other = (FooBar) obj;
        if (string == null) {
            if (other.string != null)
                return false;
        } else if (!string.equals(other.string))
            return false;
        if (!Arrays.equals(stringArray, other.stringArray))
            return false;
        if (stringList == null) {
            if (other.stringList != null)
                return false;
        } else if (!stringList.equals(other.stringList))
            return false;
        return true;
    }

}

14
เป็นจริง แต่รหัสที่สร้างโดย Eclipse นั้นไม่สามารถอ่านได้และไม่สามารถแก้ไขได้
ฌอนแพทริคฟลอยด์

6
equalsโปรดไม่เคยคิดเกี่ยวกับสิ่งที่เป็นที่น่ากลัวเป็นคราสสร้าง หากคุณไม่ต้องการพึ่งห้องสมุดบุคคลที่สามให้เขียนวิธีการหนึ่งบรรทัดเหมือนObjects.equalตัวคุณเอง แม้เมื่อใช้เพียงครั้งเดียวหรือสองครั้งก็ช่วยให้โค้ดดีขึ้น!
maaartinus

@maaartinus equals/ hashCodeวิธีการหนึ่งบรรทัด ???
FrVaBe

1
@maaartinus Guava เป็นห้องสมุดของบุคคลที่สาม ฉันชี้ให้เห็นว่าโซลูชันของฉันสามารถใช้ได้หากคุณต้องการหลีกเลี่ยงการใช้ห้องสมุดบุคคลที่สาม
FrVaBe

1
@FVVaBe: และฉันเขียนว่า "หากคุณไม่ต้องการพึ่งห้องสมุดบุคคลที่สามให้เขียนวิธีการหนึ่งบรรทัดเช่น Objects.equal ด้วยตัวคุณเอง" จากนั้นฉันก็เขียนวิธีการหนึ่งบรรทัดซึ่งคุณอาจใช้เพื่อหลีกเลี่ยงการใช้ Guava และยังตัดความยาวเท่ากับครึ่งหนึ่ง
maaartinus

6

EqualsBuilder และ HashCodeBuilder มีสองประเด็นหลักที่แตกต่างจากโค้ดที่เขียนด้วยตนเอง:

  • การจัดการ null
  • การสร้างอินสแตนซ์

EqualsBuilder และ HashCodeBuilder ทำให้ง่ายต่อการเปรียบเทียบเขตข้อมูลที่อาจเป็นค่าว่าง ด้วยรหัสเขียนด้วยตนเองนี้จะสร้างจำนวนมากสำเร็จรูป

EqualsBuilder จะสร้างอินสแตนซ์ต่อการเรียกเมธอดเท่ากับ หากวิธีการของคุณเท่ากับการโทรบ่อยครั้งนี้จะสร้างอินสแตนซ์จำนวนมาก

สำหรับการไฮเบอร์เนตการดำเนินการเท่ากับและ hashCode ไม่ได้สร้างความแตกต่าง พวกเขาเป็นเพียงรายละเอียดการใช้งาน สำหรับวัตถุโดเมนเกือบทั้งหมดที่โหลดด้วยไฮเบอร์เนตโอเวอร์เฮดรันไทม์ (แม้ไม่มีการวิเคราะห์การหลบหนี) ของตัวสร้างสามารถถูกละเว้นสามารถละเลย ฐานข้อมูลและค่าใช้จ่ายในการสื่อสารจะมีความสำคัญ

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


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

1
@digitaljoel ใช่คุณสามารถยกเว้นฟิลด์ได้ แต่คำจำกัดความเหล่านี้ไม่ได้ทำการบันทึกการบันทึกซ้ำ ดังนั้นฉันไม่ได้พูดถึงจุดมุ่งหมาย
Thomas Jung


0

หากคุณเพิ่งจัดการกับ entity bean โดยที่ id เป็นคีย์หลักคุณสามารถทำให้ง่ายขึ้น

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

      EntityBean castOther = (EntityBean) other;
      return new EqualsBuilder().append(this.getId(), castOther.getId()).isEquals();
   }

0

ในความคิดของฉันมันไม่ได้เล่นกับ Hibernate ได้ดีโดยเฉพาะตัวอย่างจากคำตอบเปรียบเทียบความยาวชื่อและลูกสำหรับบางเอนทิตี ไฮเบอร์เนตแนะนำให้ใช้รหัสธุรกิจเพื่อใช้ในเท่ากับ () และ hashCode () และพวกเขามีเหตุผล หากคุณใช้ตัวสร้างอัตโนมัติเท่ากับ () และ hashCode () บนคีย์ธุรกิจของคุณมันก็โอเคแค่ปัญหาเกี่ยวกับประสิทธิภาพจะต้องได้รับการพิจารณาตามที่กล่าวไว้ก่อนหน้านี้ แต่คนมักจะใช้คุณสมบัติทั้งหมดสิ่งที่ IMO ผิดมาก ตัวอย่างเช่นฉันกำลังทำงานในโครงการที่เอนทิตีเขียนโดยใช้ Pojomatic กับ @AutoProperty สิ่งที่ฉันพิจารณารูปแบบที่ไม่ดีจริงๆ

สถานการณ์หลักสองข้อที่ใช้ hashCode () และ equals () คือ:

  • เมื่อคุณใส่อินสแตนซ์ของคลาสถาวรในชุด (วิธีที่แนะนำเพื่อแสดงถึงการเชื่อมโยงที่มีค่ามากมาย) และ
  • เมื่อคุณใช้การติดตั้งใหม่ของอินสแตนซ์ที่แยกออก

ดังนั้นสมมติว่าเอนทิตีของเรามีลักษณะดังนี้:

class Entity {
  protected Long id;
  protected String someProp;
  public Entity(Long id, String someProp);
}

Entity entity1 = new Entity(1, "a");
Entity entity2 = new Entity(1, "b");

ทั้งสองเป็นเอนทิตีเดียวกันสำหรับ Hibernate ซึ่งถูกดึงมาจากบางเซสชั่นในบางจุด (id และคลาส / ตารางเท่ากัน) แต่เมื่อเราใช้ auto เท่ากับ () a hashCode () ในอุปกรณ์ประกอบฉากทั้งหมดเรามีอะไรบ้าง

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

ดังนั้นสำหรับโครงการ 99% ที่ฉันทำเราใช้การดำเนินการต่อไปนี้ของ equals () และ hashCode () เขียนครั้งเดียวในคลาสเอนทิตีฐานซึ่งสอดคล้องกับแนวคิดไฮเบอร์เนต:

@Override
public boolean equals(Object obj) {
    if (StringUtils.isEmpty(id))
        return super.equals(obj);

    return getClass().isInstance(obj) && id.equals(((IDomain) obj).getId());
}

@Override
public int hashCode() {
    return StringUtils.isEmpty(id)
        ? super.hashCode()
        : String.format("%s/%s", getClass().getSimpleName(), getId()).hashCode();
}

สำหรับเอนทิตีชั่วคราวฉันทำเช่นเดียวกันกับสิ่งที่ไฮเบอร์เนตจะทำในขั้นตอนการคงอยู่เช่น ฉันใช้การจับคู่อินสแตนซ์ สำหรับวัตถุถาวรฉันเปรียบเทียบคีย์เฉพาะซึ่งเป็นตาราง / id (ฉันไม่เคยใช้คีย์ผสม)


0

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

ตัวอย่างการใช้งาน:

public int hashCode() {
    return HashCode.hash(HashCode.hash(timestampMillis), name, dateOfBirth); // timestampMillis is long
}

public int hashCode() {
    return HashCode.hash(super.hashCode(), occupation, children);
}

ผู้ช่วย HashCode:

public class HashCode {

    public static int hash(Object o1, Object o2) {
        return add(Objects.hashCode(o1), o2);
    }

    public static int hash(Object o1, Object o2, Object o3) {
        return hash(Objects.hashCode(o1), o2, o3);
    }

    ...

    public static int hash(Object o1, Object o2, ..., Object o10) {
        return hash(Objects.hashCode(o1), o2, o3, ..., o10);
    }

    public static int hash(int initial, Object o1, Object o2) {
        return add(add(initial, o1), o2);
    }

    ...

    public static int hash(int initial, Object o1, Object o2, ... Object o10) {
        return add(... add(add(add(initial, o1), o2), o3) ..., o10);
    }

    public static int hash(long value) {
        return (int) (value ^ (value >>> 32));
    }

    public static int hash(int initial, long value) {
        return add(initial, hash(value));
    }

    private static int add(int accumulator, Object o) {
        return 31 * accumulator + Objects.hashCode(o);
    }
}

ฉันคิดว่า 10 คือจำนวนคุณสมบัติที่เหมาะสมที่สุดในรูปแบบโดเมนหากคุณมีมากกว่านั้นคุณควรคิดถึงการปรับโครงสร้างใหม่และแนะนำคลาสเพิ่มเติมแทนที่จะรักษากอง Strings และดั้งเดิมไว้

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

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