มียูทิลิตี้การสะท้อนของ Java เพื่อทำการเปรียบเทียบอย่างลึกซึ้งของสองวัตถุหรือไม่ [ปิด]


99

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


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

ตามหลักการแล้วจะสามารถกำหนดค่าได้เพียงพอ :) ฉันกำลังมองหาบางสิ่งบางอย่างโดยอัตโนมัติเพื่อที่ว่าหากมีการเพิ่มฟิลด์ใหม่ (และไม่ได้โคลน) การทดสอบจะสามารถระบุได้
Uri

3
สิ่งที่ฉันพยายามจะบอกคือคุณจะต้องกำหนดค่า (เช่นใช้งาน) การเปรียบเทียบต่อไป แล้วทำไมไม่แทนที่วิธีการเท่ากับในชั้นเรียนของคุณและใช้สิ่งนั้น?
Zed

3
ถ้าเท่ากับส่งคืนเท็จสำหรับอ็อบเจ็กต์เชิงซ้อนขนาดใหญ่คุณจะเริ่มต้นที่ไหน? คุณจะดีกว่ามากในการเปลี่ยนวัตถุให้เป็นสตริงหลายบรรทัดและทำการเปรียบเทียบสตริง จากนั้นคุณจะเห็นว่าวัตถุสองชิ้นแตกต่างกันตรงไหน IntelliJ จะปรากฏหน้าต่างเปรียบเทียบ "การเปลี่ยนแปลง" ซึ่งช่วยค้นหาการเปลี่ยนแปลงหลายรายการระหว่างผลลัพธ์สองรายการกล่าวคือเข้าใจผลลัพธ์ของ assertEquals (string1, string2) และให้หน้าต่างเปรียบเทียบแก่คุณ
Peter Lawrey

มีคำตอบที่ดีจริงๆที่นี่นอกเหนือจากคำตอบที่ได้รับการยอมรับซึ่งดูเหมือนว่าจะถูกฝังอยู่
user1445967

คำตอบ:


63

Unitilsมีฟังก์ชันนี้:

การยืนยันความเท่าเทียมกันผ่านการสะท้อนด้วยตัวเลือกต่างๆเช่นการละเว้นค่าเริ่มต้น / ค่าว่างของ Java และละเว้นลำดับของคอลเลกชัน


10
ฉันได้ทำการทดสอบบางอย่างเกี่ยวกับฟังก์ชันนี้แล้วและดูเหมือนว่าจะทำการเปรียบเทียบอย่างลึกซึ้งโดยที่ EqualsBuilder ไม่ทำ
Howard พฤษภาคม

มีวิธีที่จะทำให้ไม่เพิกเฉยต่อเขตข้อมูลชั่วคราวหรือไม่?
หยิก

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

@ Wolfgang มีโค้ดตัวอย่างที่จะแนะนำเราหรือไม่? คุณดึงคำพูดนั้นมาจากไหน?
anon58192932

30

ฉันชอบคำถามนี้! ส่วนใหญ่เป็นเพราะแทบไม่เคยตอบหรือตอบไม่ดี มันเหมือนกับว่ายังไม่มีใครคิดออก ดินแดนเวอร์จิน :)

ปิดแรกไม่ได้คิดequalsเกี่ยวกับการใช้ สัญญาของequalsตามที่กำหนดไว้ใน javadoc คือความสัมพันธ์ที่เท่าเทียมกัน (รีเฟล็กซีฟสมมาตรและสกรรมกริยา) ไม่ใช่ความสัมพันธ์แบบเท่าเทียมกัน สำหรับสิ่งนั้นก็จะต้องมีการป้องกันเสียงรบกวนด้วย การดำเนินการเฉพาะของequalsที่มีอยู่ (หรือที่เคยอาจจะ) java.lang.Objectความสัมพันธ์ที่เท่าเทียมกันจริงเป็นหนึ่งใน แม้ว่าคุณจะใช้equalsเพื่อเปรียบเทียบทุกอย่างในกราฟ แต่ความเสี่ยงในการผิดสัญญาก็ค่อนข้างสูง ดังที่ Josh Bloch ชี้ให้เห็นในภาษา Java ที่มีประสิทธิภาพการทำสัญญาเท่ากับนั้นง่ายมากที่จะทำลาย:

"ไม่มีวิธีใดเลยที่จะขยายคลาสแบบทันทีและเพิ่มแง่มุมในขณะที่รักษาสัญญาที่เท่าเทียมกัน"

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

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

ปัญหาบางอย่างที่คุณจะพบ:

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

XStream ค่อนข้างเร็วและเมื่อรวมกับ XMLUnit จะทำงานได้ในโค้ดเพียงไม่กี่บรรทัด XMLUnit เป็นสิ่งที่ดีเพราะสามารถรายงานความแตกต่างทั้งหมดหรือหยุดที่ความแตกต่างแรกที่พบ และเอาต์พุตของมันรวม xpath ไปยังโหนดที่แตกต่างกันซึ่งเป็นสิ่งที่ดี โดยค่าเริ่มต้นจะไม่อนุญาตให้มีคอลเล็กชันที่ไม่ได้เรียงลำดับ แต่สามารถกำหนดค่าให้ทำได้ การใส่ตัวจัดการความแตกต่างพิเศษ (เรียกว่า a DifferenceListener) ช่วยให้คุณสามารถระบุวิธีที่คุณต้องการจัดการกับความแตกต่างรวมถึงการละเว้นลำดับ อย่างไรก็ตามทันทีที่คุณต้องการทำอะไรที่นอกเหนือจากการปรับแต่งที่ง่ายที่สุดการเขียนจะกลายเป็นเรื่องยากและรายละเอียดมักจะผูกติดกับวัตถุโดเมนเฉพาะ

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


@StatePolicy(unordered=true, ignore=false, exactTypesOnly=true)
private List<StringyThing> _mylist;

ฉันคิดว่านี่เป็นปัญหาที่หนักมาก แต่ก็แก้ไขได้ทั้งหมด! และเมื่อคุณมีสิ่งที่เหมาะกับคุณแล้วมันก็มีประโยชน์จริงๆ :)

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


15

ดู DeepEquals และ DeepHashCode () ภายใน java-util: https://github.com/jdereg/java-util

คลาสนี้ทำตามที่ผู้เขียนดั้งเดิมร้องขอทุกประการ


4
คำเตือน: DeepEquals ใช้เมธอด .equals () ของอ็อบเจ็กต์หากมีอยู่ นี่อาจไม่ใช่สิ่งที่คุณต้องการ
Adam

4
จะใช้เฉพาะ .equals () ในคลาสหากมีการเพิ่มเมธอด equals () อย่างชัดเจนมิฉะนั้นจะทำการเปรียบเทียบแบบสมาชิกต่อสมาชิก ตรรกะที่นี่คือถ้ามีคนพยายามเขียนวิธีการเท่ากับ () ที่กำหนดเองควรใช้ การปรับปรุงในอนาคต: อนุญาตให้แฟล็กมีการละเว้นวิธีการเท่ากับ () แม้ว่าจะมีอยู่ก็ตาม มียูทิลิตี้ที่มีประโยชน์ใน java-util เช่น CaseInsensitiveMap / Set
John DeRegnaucourt

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

เพื่อตอบ @beluchin ข้างต้น DeepEquals.deepEquals () ไม่ได้ทำการเปรียบเทียบแบบฟิลด์ต่อฟิลด์เสมอไป ขั้นแรกมีตัวเลือกให้ใช้ .equals () บนเมธอดหากมีอยู่ (ซึ่งไม่ใช่ตัวเลือกบน Object) หรือสามารถละเว้น ประการที่สองเมื่อเปรียบเทียบแผนที่ / คอลเล็กชันจะไม่ดูที่ประเภทคอลเล็กชันหรือแผนที่หรือฟิลด์บนคอลเล็กชัน / แผนที่ แต่จะเปรียบเทียบอย่างมีเหตุผล LinkedHashMap สามารถเท่ากับ TreeMap ได้หากมีเนื้อหาและองค์ประกอบเดียวกันในลำดับเดียวกัน สำหรับคอลเล็กชันและแผนที่ที่ไม่ได้สั่งซื้อจำเป็นต้องมีเฉพาะขนาดและรายการที่มีความลึกเท่านั้น
John DeRegnaucourt

เมื่อเปรียบเทียบแผนที่ / คอลเล็กชันจะไม่ดูที่ประเภทคอลเล็กชันหรือแผนที่หรือฟิลด์บนคอลเล็กชัน / แผนที่ แต่มันเปรียบเทียบพวกเขาในเชิงเหตุผล @JohnDeRegnaucourt ฉันจะโต้แย้งการเปรียบเทียบเชิงตรรกะนี้คือการเปรียบเทียบเฉพาะสิ่งที่publicควรใช้กับทุกประเภทแทนที่จะใช้กับคอลเล็กชัน / แผนที่เท่านั้น
beluchin

10

แทนที่วิธีการเท่ากับ ()

คุณสามารถลบล้างเมธอดequals ()ของคลาสโดยใช้EqualsBuilder.reflectionEquals ()ตามที่อธิบายไว้ที่นี่ :

 public boolean equals(Object obj) {
   return EqualsBuilder.reflectionEquals(this, obj);
 }

8

ในAssertJคุณสามารถทำได้:

Assertions.assertThat(expectedObject).isEqualToComparingFieldByFieldRecursively(actualObject);

อาจจะใช้ไม่ได้ในทุกกรณี แต่จะใช้ได้ผลในหลาย ๆ กรณีที่คุณคิด

นี่คือสิ่งที่เอกสารระบุ:

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

การเปรียบเทียบแบบวนซ้ำจะจัดการกับวัฏจักร โดยค่าเริ่มต้นการลอยจะถูกเปรียบเทียบด้วยความแม่นยำ 1.0E-6 และเพิ่มเป็นสองเท่าด้วย 1.0E-15

คุณสามารถระบุตัวเปรียบเทียบแบบกำหนดเองต่อฟิลด์ (ซ้อนกัน) หรือพิมพ์ตามลำดับโดยใช้ ComparatorForFields (ตัวเปรียบเทียบสตริง ... ) และการใช้ComparatorForType (ตัวเปรียบเทียบคลาส)

ออบเจ็กต์ที่จะเปรียบเทียบอาจเป็นประเภทต่างๆ แต่ต้องมีคุณสมบัติ / ฟิลด์เดียวกัน ตัวอย่างเช่นถ้าออบเจ็กต์จริงมีฟิลด์ชื่อสตริงก็คาดว่าอีกอ็อบเจกต์จะมีด้วย หากออบเจ็กต์มีฟิลด์และคุณสมบัติที่มีชื่อเดียวกันค่าคุณสมบัติจะถูกใช้เหนือฟิลด์


2
isEqualToComparingFieldByFieldRecursivelyเลิกใช้งานแล้ว ใช้assertThat(expectedObject).usingRecursiveComparison().isEqualTo(actualObject);แทน :)
dargmuesli

7

ต้องใช้การเปรียบเทียบอินสแตนซ์เอนทิตีสองรายการที่แก้ไขโดย Hibernate Envers ฉันเริ่มเขียนความแตกต่างของตัวเอง แต่พบกรอบต่อไปนี้

https://github.com/SQiShER/java-object-diff

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

ประสิทธิภาพของมันอยู่ในเกณฑ์ดีเมื่อเปรียบเทียบเอนทิตี JPA อย่าลืมถอดออกจากตัวจัดการเอนทิตีก่อน


6

ฉันใช้ XStream:

/**
 * @see java.lang.Object#equals(java.lang.Object)
 */
@Override
public boolean equals(Object o) {
    XStream xstream = new XStream();
    String oxml = xstream.toXML(o);
    String myxml = xstream.toXML(this);

    return myxml.equals(oxml);
}

/**
 * @see java.lang.Object#hashCode()
 */
@Override
public int hashCode() {
    XStream xstream = new XStream();
    String myxml = xstream.toXML(this);
    return myxml.hashCode();
}

5
คอลเล็กชันอื่นที่ไม่ใช่รายการอาจส่งคืนองค์ประกอบในลำดับที่ต่างกันดังนั้นการเปรียบเทียบสตริงจะล้มเหลว
Alexey Berezkin

นอกจากนี้คลาสที่ไม่สามารถต่ออนุกรมได้ก็จะล้มเหลว
Zwelch

5

http://www.unitils.org/tutorial-reflectionassert.html

public class User {

    private long id;
    private String first;
    private String last;

    public User(long id, String first, String last) {
        this.id = id;
        this.first = first;
        this.last = last;
    }
}
User user1 = new User(1, "John", "Doe");
User user2 = new User(1, "John", "Doe");
assertReflectionEquals(user1, user2);

2
มีประโยชน์โดยเฉพาะอย่างยิ่งหากคุณต้องจัดการคลาสที่สร้างขึ้นโดยที่คุณไม่มีอิทธิพลใด ๆ เกี่ยวกับการเท่ากับ!
Matthias B

1
stackoverflow.com/a/1449051/829755 ได้กล่าวไว้แล้ว คุณควรแก้ไขโพสต์นั้น
user829755

1
@ user829755 ด้วยวิธีนี้ฉันเสียคะแนน ดังนั้นทุกอย่างเกี่ยวกับเกมจุด)) คนชอบได้รับเครดิตสำหรับงานที่ทำฉันก็เช่นกัน
gavenkoa

3

Hamcrest มี Matcher samePropertyValuesAs แต่ต้องอาศัยอนุสัญญา JavaBeans (ใช้ getters และ setters) หากออบเจ็กต์ที่จะเปรียบเทียบไม่มีตัวรับและตัวเซ็ตสำหรับแอตทริบิวต์สิ่งนี้จะไม่ทำงาน

import static org.hamcrest.beans.SamePropertyValuesAs.samePropertyValuesAs;
import static org.junit.Assert.assertThat;

import org.junit.Test;

public class UserTest {

    @Test
    public void asfd() {
        User user1 = new User(1, "John", "Doe");
        User user2 = new User(1, "John", "Doe");
        assertThat(user1, samePropertyValuesAs(user2)); // all good

        user2 = new User(1, "John", "Do");
        assertThat(user1, samePropertyValuesAs(user2)); // will fail
    }
}

ถั่วผู้ใช้ - พร้อมตัวรับและตัวตั้งค่า

public class User {

    private long id;
    private String first;
    private String last;

    public User(long id, String first, String last) {
        this.id = id;
        this.first = first;
        this.last = last;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getFirst() {
        return first;
    }

    public void setFirst(String first) {
        this.first = first;
    }

    public String getLast() {
        return last;
    }

    public void setLast(String last) {
        this.last = last;
    }

}

วิธีนี้ใช้ได้ผลดีจนกว่าคุณจะมี POJO ที่ใช้isFooวิธีการอ่านสำหรับBooleanคุณสมบัติ มี PR ที่เปิดให้แก้ไขตั้งแต่ปี 2559 github.com/hamcrest/JavaHamcrest/pull/136
Snekse

2

หากวัตถุของคุณใช้ Serializable คุณสามารถใช้สิ่งนี้:

public static boolean deepCompare(Object o1, Object o2) {
    try {
        ByteArrayOutputStream baos1 = new ByteArrayOutputStream();
        ObjectOutputStream oos1 = new ObjectOutputStream(baos1);
        oos1.writeObject(o1);
        oos1.close();

        ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
        ObjectOutputStream oos2 = new ObjectOutputStream(baos2);
        oos2.writeObject(o2);
        oos2.close();

        return Arrays.equals(baos1.toByteArray(), baos2.toByteArray());
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

1

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

ฉันเห็นด้วยกับบุคคลข้างต้นที่บอกว่าใช้ LinkedList (เหมือน Stack แต่ไม่มีวิธีการซิงโครไนซ์จึงเร็วกว่า) การข้ามกราฟวัตถุโดยใช้ Stack ในขณะที่ใช้การสะท้อนเพื่อให้ได้แต่ละฟิลด์เป็นทางออกที่ดี เขียนครั้งเดียว "ภายนอก" นี้เท่ากับ () และ "ภายนอก" hashCode () คือสิ่งที่ควรเรียกใช้เมธอด equals () และ hashCode () ทั้งหมด คุณไม่ต้องการวิธีการเท่ากับลูกค้า () อีกต่อไป

ฉันเขียนโค้ดเล็กน้อยที่ลากผ่านกราฟออบเจ็กต์ที่สมบูรณ์ซึ่งระบุไว้ใน Google Code ดู json-io (http://code.google.com/p/json-io/) มันทำให้กราฟออบเจ็กต์ Java เป็นอนุกรมเป็น JSON และแยกส่วนออกจากมัน มันจัดการกับวัตถุ Java ทั้งหมดโดยมีหรือไม่มีตัวสร้างสาธารณะ Serializeable หรือไม่ต่ออนุกรมได้ ฯลฯ รหัสการส่งผ่านเดียวกันนี้จะเป็นพื้นฐานสำหรับการใช้งานภายนอก "equals ()" และ "hashcode ()" ภายนอก Btw, JsonReader / JsonWriter (json-io) มักจะเร็วกว่า ObjectInputStream / ObjectOutputStream ในตัว

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

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

สำหรับช่องชั่วคราว - นั่นจะเป็นตัวเลือกที่เลือกได้ บางครั้งคุณอาจต้องการชั่วคราวเพื่อนับเวลาอื่น ๆ "บางครั้งคุณรู้สึกเหมือนถั่ว

กลับไปที่โปรเจ็กต์ json-io (สำหรับโปรเจ็กต์อื่น ๆ ของฉัน) และคุณจะพบโปรเจ็กต์ equals () / hashcode () ภายนอก ฉันยังไม่มีชื่อ แต่จะเห็นได้ชัด


0

ฉันเดาว่าคุณรู้เรื่องนี้ แต่ในทางทฤษฎีคุณควรจะลบล้างเสมอเท่ากับยืนยันว่าวัตถุสองชิ้นมีค่าเท่ากันอย่างแท้จริง นี่หมายความว่าพวกเขาตรวจสอบเมธอด .equals ที่ถูกแทนที่บนสมาชิกของพวกเขา

สิ่งนี้คือสาเหตุที่ .equals ถูกกำหนดไว้ใน Object

หากสิ่งนี้ทำได้อย่างสม่ำเสมอคุณก็จะไม่มีปัญหา


2
ปัญหาคือฉันต้องการทดสอบสิ่งนี้โดยอัตโนมัติสำหรับโค้ดเบสที่มีอยู่ขนาดใหญ่ที่ฉันไม่ได้เขียน ... :)
Uri

0

การหยุดการรับประกันสำหรับการเปรียบเทียบที่ลึกซึ้งเช่นนี้อาจเป็นปัญหาได้ ต่อไปนี้ควรทำอย่างไร? (หากคุณใช้ตัวเปรียบเทียบดังกล่าวจะเป็นการทดสอบหน่วยที่ดี)

LinkedListNode a = new LinkedListNode();
a.next = a;
LinkedListNode b = new LinkedListNode();
b.next = b;

System.out.println(DeepCompare(a, b));

นี่คืออีก:

LinkedListNode c = new LinkedListNode();
LinkedListNode d = new LinkedListNode();
c.next = d;
d.next = c;

System.out.println(DeepCompare(c, d));

หากคุณมีคำถามใหม่โปรดขอได้โดยคลิกที่ถามคำถามปุ่ม ใส่ลิงก์ไปยังคำถามนี้หากช่วยให้บริบท
YoungHobbit

@younghobbit: ไม่นี่ไม่ใช่คำถามใหม่ เครื่องหมายคำถามในคำตอบไม่ทำให้ธงนั้นเหมาะสม กรุณาให้ความสนใจมากขึ้น
Ben Voigt

จากนี้: Using an answer instead of a comment to get a longer limit and better formatting.ถ้าเป็นความคิดเห็นแล้วทำไมต้องใช้ส่วนคำตอบ? นั่นคือเหตุผลที่ฉันตั้งค่าสถานะไว้ ไม่ใช่เพราะไฟล์?. คำตอบนี้ถูกตั้งค่าสถานะโดยบุคคลอื่นซึ่งไม่ได้แสดงความคิดเห็นไว้ข้างหลัง ฉันเพิ่งได้รับสิ่งนี้ในคิวการตรวจสอบ อาจจะเป็นเรื่องเลวร้ายของฉันที่ฉันควรระวังให้มากขึ้น
YoungHobbit

0

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

การทำให้เป็นอนุกรมอาจเป็น byte, json, xml หรือ simple toString เป็นต้น ToString ดูเหมือนจะถูกกว่า ลอมบอกสร้าง ToSTring ที่ปรับแต่งได้ง่ายฟรีสำหรับเรา ดูตัวอย่างด้านล่าง

@ToString @Getter @Setter
class foo{
    boolean foo1;
    String  foo2;        
    public boolean deepCompare(Object other) { //for cohesiveness
        return other != null && this.toString().equals(other.toString());
    }
}   

0

Apache ให้บางสิ่งแก่คุณแปลงวัตถุทั้งสองเป็นสตริงและเปรียบเทียบสตริง แต่คุณต้อง Override toString ()

obj1.toString().equals(obj2.toString())

แทนที่ toString ()

หากฟิลด์ทั้งหมดเป็นประเภทดั้งเดิม:

import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
@Override
public String toString() {return 
ReflectionToStringBuilder.toString(this);}

หากคุณมีฟิลด์และ / หรือคอลเล็กชันและ / หรือแผนที่ที่ไม่ใช่แบบดั้งเดิม:

// Within class
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
@Override
public String toString() {return 
ReflectionToStringBuilder.toString(this,new 
MultipleRecursiveToStringStyle());}

// New class extended from Apache ToStringStyle
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import java.util.*;

public class MultipleRecursiveToStringStyle extends ToStringStyle {
private static final int    INFINITE_DEPTH  = -1;

private int                 maxDepth;

private int                 depth;

public MultipleRecursiveToStringStyle() {
    this(INFINITE_DEPTH);
}

public MultipleRecursiveToStringStyle(int maxDepth) {
    setUseShortClassName(true);
    setUseIdentityHashCode(false);

    this.maxDepth = maxDepth;
}

@Override
protected void appendDetail(StringBuffer buffer, String fieldName, Object value) {
    if (value.getClass().getName().startsWith("java.lang.")
            || (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) {
        buffer.append(value);
    } else {
        depth++;
        buffer.append(ReflectionToStringBuilder.toString(value, this));
        depth--;
    }
}

@Override
protected void appendDetail(StringBuffer buffer, String fieldName, 
Collection<?> coll) {
    for(Object value: coll){
        if (value.getClass().getName().startsWith("java.lang.")
                || (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) {
            buffer.append(value);
        } else {
            depth++;
            buffer.append(ReflectionToStringBuilder.toString(value, this));
            depth--;
        }
    }
}

@Override
protected void appendDetail(StringBuffer buffer, String fieldName, Map<?, ?> map) {
    for(Map.Entry<?,?> kvEntry: map.entrySet()){
        Object value = kvEntry.getKey();
        if (value.getClass().getName().startsWith("java.lang.")
                || (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) {
            buffer.append(value);
        } else {
            depth++;
            buffer.append(ReflectionToStringBuilder.toString(value, this));
            depth--;
        }
        value = kvEntry.getValue();
        if (value.getClass().getName().startsWith("java.lang.")
                || (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) {
            buffer.append(value);
        } else {
            depth++;
            buffer.append(ReflectionToStringBuilder.toString(value, this));
            depth--;
        }
    }
}}

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