“ วิธีการเปรียบเทียบละเมิดสัญญาทั่วไป!”


187

มีใครช่วยอธิบายฉันด้วยคำศัพท์ง่ายๆทำไมรหัสนี้ถึงเป็นข้อยกเว้น "วิธีการเปรียบเทียบละเมิดสัญญาทั่วไป!" และฉันจะแก้ไขได้อย่างไร

private int compareParents(Foo s1, Foo s2) {
    if (s1.getParent() == s2) return -1;
    if (s2.getParent() == s1) return 1;
    return 0;
}

1
ชื่อและคลาสของข้อยกเว้นคืออะไร มันเป็น IllegalArgumentException หรือไม่ ถ้าผมจะคิดว่าผมจะคิดว่าคุณควรจะทำแทนs1.getParent().equals(s2) s1.getParent() == s2
Freiheit

และข้อยกเว้นที่ถูกโยนเช่นกัน
Matthew Farwell

2
ฉันไม่รู้เกี่ยวกับ Java หรือ API เปรียบเทียบ Java มากนัก แต่วิธีเปรียบเทียบนี้ดูเหมือนผิดไป สมมติว่าs1เป็นแม่ของs2และไม่ได้เป็นแม่ของs2 s1จากนั้นก็compareParents(s1, s2)เป็น0แต่เป็นcompareParents(s2, s1) 1ไม่สมเหตุสมผลเลย (นอกจากนี้ยังไม่ได้ transitive เช่น Aix กล่าวถึงด้านล่าง.)
MQP

4
ข้อผิดพลาดนี้ดูเหมือนว่าจะมีการผลิตโดยเฉพาะไลบรารีcr.openjdk.java.net/~martin/webrevs/openjdk7/timsort/src/share/…
Peter Lawrey

ใน java คุณสามารถใช้เท่ากับ (return a บูลีน) หรือ compareTo (return -1, 0 หรือ +1) แทนที่ฟังก์ชั่นนี้ในคลาส Foo ของคุณและหลังจากนั้นคุณสามารถตรวจสอบ s1.getParent (). เท่ากับ (s2) ...
Mualig

คำตอบ:


261

เครื่องมือเปรียบเทียบของคุณไม่ใช่สกรรมกริยา

อนุญาตAเป็นแม่ของBและเป็นแม่ของB Cตั้งแต่A > Bและจากนั้นมันจะต้องเป็นกรณีที่B > C A > Cแต่ถ้าเปรียบเทียบของคุณจะถูกเรียกในAและก็จะกลับเป็นศูนย์ที่มีความหมายC A == Cสิ่งนี้ละเมิดสัญญาและด้วยเหตุนี้จึงทำให้เกิดข้อยกเว้น

มันค่อนข้างดีที่ห้องสมุดจะตรวจจับสิ่งนี้และแจ้งให้คุณทราบมากกว่าจะประพฤติผิดปกติ

วิธีหนึ่งที่จะสนองความต้องการด้านการเปลี่ยนแปลงได้compareParents()คือการสำรวจgetParent()ห่วงโซ่แทนที่จะมองดูบรรพบุรุษทันที


3
แนะนำในjava.util.Arrays.sort stackoverflow.com/questions/7849539/…
leonbloy

46
ความจริงที่ว่าห้องสมุดกำลังตรวจจับสิ่งนี้มันยอดเยี่ยม ใครบางคนที่ดวงอาทิตย์สมควรที่จะโยนออกยักษ์คุณกำลังต้อนรับ
Qix - MONICA ถูกยกเลิก

คุณช่วยให้คำตอบทั่วไปนี้เล็กน้อยเพื่อทำให้คำถามนี้มีประโยชน์มากขึ้นในการโพสต์อ้างอิงได้หรือไม่?
Bernhard Barker

1
@Qix - มากเท่าที่ฉันรัก Sun สิ่งนี้ถูกเพิ่มเข้ามาใน Java 7 ภายใต้แบนเนอร์ของ Oracle
isapir

1
@isapir ประณาม! จับดี.
Qix - MONICA ถูกยกเลิก

38

เพียงเพราะนี่คือสิ่งที่ฉันได้รับเมื่อฉัน Googled ข้อผิดพลาดนี้ปัญหาของฉันคือฉันมี

if (value < other.value)
  return -1;
else if (value >= other.value)
  return 1;
else
  return 0;

value >= other.valueควร (ชัด) เป็นจริงvalue > other.valueเพื่อให้คุณจริงสามารถกลับ 0 กับวัตถุที่เท่าเทียมกัน


7
ฉันต้องเพิ่มว่าหากใด ๆ ของคุณvalueเป็นน่าน (ถ้าvalueเป็นdoubleหรือfloat) ก็จะล้มเหลวเช่นกัน
Matthieu

22

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

if ( one.length() == 0 ) {
    return 1;                   // empty string sorts last
}
if ( two.length() == 0 ) {
    return -1;                  // empty string sorts last                  
}
return one.compareToIgnoreCase( two );

แต่สิ่งนี้มองเห็นกรณีที่ทั้งหนึ่งและสองว่างเปล่า - และในกรณีนั้นค่าผิดจะถูกส่งกลับ (1 แทน 0 เพื่อแสดงการแข่งขัน) และผู้เปรียบเทียบรายงานว่าเป็นการละเมิด ควรเขียนเป็น:

if ( one.length() == 0 ) {
    if ( two.length() == 0 ) {
        return 0;               // BOth empty - so indicate
    }
    return 1;                   // empty string sorts last
}
if ( two.length() == 0 ) {
    return -1;                  // empty string sorts last                  
}
return one.compareToIgnoreCase( two );

13

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

public int compareTo(tfidfContainer compareTfidf) {
    //descending order
    if (this.tfidf > compareTfidf.tfidf)
        return -1;
    else if (this.tfidf < compareTfidf.tfidf)
        return 1;
    else
        return 0;

}   

คุณสมบัติสกรรมกริยานั้นชัดเจน แต่ด้วยเหตุผลบางอย่างที่ฉันได้รับ IllegalArgumentException และปรากฎว่าเนื่องจากข้อผิดพลาดเล็ก ๆ ในการคำนวณจุดลอยตัวข้อผิดพลาดปัดเศษที่ทำให้คุณสมบัติสกรรมกริยาหยุดพักที่ไม่ควร! ดังนั้นฉันจึงเขียนรหัสใหม่เพื่อพิจารณาความแตกต่างเล็ก ๆ น้อย ๆ 0 และมันทำงานได้:

public int compareTo(tfidfContainer compareTfidf) {
    //descending order
    if ((this.tfidf - compareTfidf.tfidf) < .000000001)
        return 0;
    if (this.tfidf > compareTfidf.tfidf)
        return -1;
    else if (this.tfidf < compareTfidf.tfidf)
        return 1;
    return 0;
}   

2
สิ่งนี้มีประโยชน์! รหัสของฉันใช้ได้ในเชิงตรรกะ แต่มีข้อผิดพลาดเนื่องจากปัญหาความแม่นยำ
JSong

6

ในกรณีของเราได้รับข้อผิดพลาดนี้เนื่องจากเราได้พลิกลำดับการเปรียบเทียบของ s1 และ s2 โดยไม่ได้ตั้งใจ ดังนั้นระวังสิ่งนั้น เห็นได้ชัดว่ามันซับซ้อนกว่าวิธีดังต่อไปนี้ แต่นี่เป็นภาพประกอบ:

s1 == s2   
    return 0;
s2 > s1 
    return 1;
s1 < s2 
    return -1;

3

Java ไม่ได้ตรวจสอบความสอดคล้องในความเข้มงวดเท่านั้นจะแจ้งให้คุณทราบหากพบปัญหาร้ายแรง ยังไม่ให้ข้อมูลมากจากข้อผิดพลาด

ฉันงุนงงกับสิ่งที่เกิดขึ้นในเครื่องคัดเเยกของฉันและสร้างตัวตรวจสอบความมั่นคงอย่างเข้มงวดบางทีนี่อาจช่วยคุณได้:

/**
 * @param dailyReports
 * @param comparator
 */
public static <T> void checkConsitency(final List<T> dailyReports, final Comparator<T> comparator) {
  final Map<T, List<T>> objectMapSmallerOnes = new HashMap<T, List<T>>();

  iterateDistinctPairs(dailyReports.iterator(), new IPairIteratorCallback<T>() {
    /**
     * @param o1
     * @param o2
     */
    @Override
    public void pair(T o1, T o2) {
      final int diff = comparator.compare(o1, o2);
      if (diff < Compare.EQUAL) {
        checkConsistency(objectMapSmallerOnes, o1, o2);
        getListSafely(objectMapSmallerOnes, o2).add(o1);
      } else if (Compare.EQUAL < diff) {
        checkConsistency(objectMapSmallerOnes, o2, o1);
        getListSafely(objectMapSmallerOnes, o1).add(o2);
      } else {
        throw new IllegalStateException("Equals not expected?");
      }
    }
  });
}

/**
 * @param objectMapSmallerOnes
 * @param o1
 * @param o2
 */
static <T> void checkConsistency(final Map<T, List<T>> objectMapSmallerOnes, T o1, T o2) {
  final List<T> smallerThan = objectMapSmallerOnes.get(o1);

  if (smallerThan != null) {
    for (final T o : smallerThan) {
      if (o == o2) {
        throw new IllegalStateException(o2 + "  cannot be smaller than " + o1 + " if it's supposed to be vice versa.");
      }
      checkConsistency(objectMapSmallerOnes, o, o2);
    }
  }
}

/**
 * @param keyMapValues 
 * @param key 
 * @param <Key> 
 * @param <Value> 
 * @return List<Value>
 */ 
public static <Key, Value> List<Value> getListSafely(Map<Key, List<Value>> keyMapValues, Key key) {
  List<Value> values = keyMapValues.get(key);

  if (values == null) {
    keyMapValues.put(key, values = new LinkedList<Value>());
  }

  return values;
}

/**
 * @author Oku
 *
 * @param <T>
 */
public interface IPairIteratorCallback<T> {
  /**
   * @param o1
   * @param o2
   */
  void pair(T o1, T o2);
}

/**
 * 
 * Iterates through each distinct unordered pair formed by the elements of a given iterator
 *
 * @param it
 * @param callback
 */
public static <T> void iterateDistinctPairs(final Iterator<T> it, IPairIteratorCallback<T> callback) {
  List<T> list = Convert.toMinimumArrayList(new Iterable<T>() {

    @Override
    public Iterator<T> iterator() {
      return it;
    }

  });

  for (int outerIndex = 0; outerIndex < list.size() - 1; outerIndex++) {
    for (int innerIndex = outerIndex + 1; innerIndex < list.size(); innerIndex++) {
      callback.pair(list.get(outerIndex), list.get(innerIndex));
    }
  }
}

เพียงแค่เรียกใช้ checkConsitency metohod พร้อมรายการพารามิเตอร์และตัวเปรียบเทียบ
Martin

รหัสของคุณไม่ได้รวบรวม การเรียนการสอนCompare, Convert(และอาจอื่น ๆ ) ไม่ได้กำหนดไว้ โปรดอัปเดตโค้ด sniplet ด้วยตัวอย่างที่มีในตัวเอง
Gili

คุณควรแก้ไขการพิมพ์ผิดcheckConsi(s)tencyและลบการ@paramประกาศซ้ำซ้อนทั้งหมดเพื่อให้โค้ดอ่านง่ายขึ้น
Roland Illig

3

ในกรณีของฉันฉันกำลังทำสิ่งต่อไปนี้:

if (a.someField == null) {
    return 1;
}

if (b.someField == null) {
    return -1;
}

if (a.someField.equals(b.someField)) {
    return a.someOtherField.compareTo(b.someOtherField);
}

return a.someField.compareTo(b.someField);

สิ่งที่ฉันลืมตรวจสอบคือเมื่อทั้ง a.someField และ b.someField เป็นโมฆะ


3

ฉันเคยเห็นสิ่งนี้เกิดขึ้นในโค้ดที่มีการตรวจสอบค่าโมฆะซ้ำบ่อยครั้ง:

if(( A==null ) && ( B==null )
  return +1;//WRONG: two null values should return 0!!!

2

ถ้าเป็นเช่นcompareParents(s1, s2) == -1นั้นcompareParents(s2, s1) == 1คาดว่า ด้วยรหัสของคุณมันไม่เป็นความจริงเสมอไป

s1.getParent() == s2 && s2.getParent() == s1โดยเฉพาะถ้า มันเป็นเพียงหนึ่งในปัญหาที่เป็นไปได้


1

การแก้ไขการกำหนดค่า VM ใช้งานได้สำหรับฉัน

-Djava.util.Arrays.useLegacyMergeSort=true

โปรดตรวจสอบอีกครั้งว่าฉันพยายามช่วยคุณในการจัดรูปแบบไม่ทำลายอะไรเลย ฉันไม่แน่ใจเกี่ยวกับ-จุดเริ่มต้นของการแก้ปัญหาที่เสนอ บางทีคุณอาจต้องการบางอย่างเช่นรายการสัญลักษณ์แสดงหัวข้อย่อยแทน
Yunnosch

2
นอกจากนี้โปรดอธิบายว่าสิ่งนี้ช่วยได้อย่างไรกับปัญหาที่อธิบายไว้ ขณะนี้มันเป็นคำตอบที่ใช้รหัสเท่านั้น
Yunnosch

0

คุณไม่สามารถเปรียบเทียบข้อมูลวัตถุเช่นนี้: s1.getParent() == s2- สิ่งนี้จะเปรียบเทียบการอ้างอิงวัตถุ คุณควรจะแทนที่equals functionคลาส Foo แล้วเปรียบเทียบพวกมันแบบนี้s1.getParent().equals(s2)


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