Phương pháp so sánh vi phạm hợp đồng chung của nó!


187

Ai đó có thể giải thích cho tôi bằng những thuật ngữ đơn giản, tại sao mã này lại đưa ra một ngoại lệ, "Phương pháp so sánh vi phạm hợp đồng chung của nó!" Và làm cách nào để khắc phục nó?

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

1
Tên và lớp của Ngoại lệ là gì? Đây có phải là một ngoại lệ IllegalArgument? Nếu tôi phải đoán tôi sẽ nghĩ rằng bạn nên làm s1.getParent().equals(s2)thay vì s1.getParent() == s2.
Freiheit

Và ngoại lệ được ném là tốt.
Matthew Farwell

2
Tôi không biết nhiều về Java hoặc về các API so sánh Java, nhưng phương pháp so sánh này có vẻ đã sai. Giả sử s1là cha mẹ của s2, và s2không phải là cha mẹ của s1. Sau đó compareParents(s1, s2)0, nhưng compareParents(s2, s1)1. Điều đó không có ý nghĩa. (Ngoài ra, nó không
mang tính bắc cầu

4
Lỗi này dường như chỉ được tạo ra bởi một thư viện cụ thể cr.openjdk.java.net/~martin/webrevs/openjdk7/timsort/src/share/iêu
Peter Lawrey

trong java, bạn có thể sử dụng bằng (trả về boolean) hoặc so sánh (trả về -1, 0 hoặc +1). Ghi đè các hàm này trong lớp Foo của bạn và sau đó, bạn có thể kiểm tra s1.getParent (). Bằng (s2) ...
Mualig

Câu trả lời:


260

So sánh của bạn không phải là bắc cầu.

Hãy Alà cha mẹ của B, và Blà cha mẹ của C. Kể từ A > BB > C, sau đó nó phải là trường hợp đó A > C. Tuy nhiên, nếu bộ so sánh của bạn được gọi AC, nó sẽ trả về 0, nghĩa là A == C. Điều này vi phạm hợp đồng và do đó ném ngoại lệ.

Thư viện phát hiện ra điều này khá hay và cho bạn biết, thay vì cư xử thất thường.

Một cách để đáp ứng các yêu cầu transitivity trong compareParents()là để đi qua các getParent()chuỗi thay vì chỉ nhìn vào tổ tiên ngay lập tức.


3
Được giới thiệu trong java.util.Arrays.sort stackoverflow.com/questions/7849539/
leonbloy

46
Thực tế thư viện đang phát hiện điều này là tuyệt vời. Ai đó ở mặt trời xứng đáng để ném ra một người khổng lồ Bạn được chào đón .
Qix - MONICA ĐƯỢC PHÂN PHỐI

Bạn có thể khái quát câu trả lời này một chút để làm cho câu hỏi này hữu ích hơn như một bài viết tham khảo không?
Bernhard Barker

1
@Qix - nhiều như tôi yêu Sun, điều này đã được thêm vào Java 7 dưới biểu ngữ Oracle
isapir

1
@isapir Chết tiệt! Nắm bắt tốt.
Qix - MONICA ĐƯỢC PHÂN PHỐI

38

Chỉ vì đây là những gì tôi nhận được khi tôi xử lý lỗi này, vấn đề của tôi là tôi đã có

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

các value >= other.valuenên (rõ ràng) thực sự là value > other.valueđể bạn có thể thực sự trở về 0 với các đối tượng bằng nhau.


7
Tôi phải nói thêm rằng nếu bất kỳ ai trong số bạn valuelà NaN (nếu valuedoublehoặc float), thì nó cũng sẽ thất bại.
Matthieu

22

Việc vi phạm hợp đồng thường có nghĩa là bộ so sánh không cung cấp giá trị chính xác hoặc nhất quán khi so sánh các đối tượng. Ví dụ: bạn có thể muốn thực hiện so sánh chuỗi và buộc các chuỗi trống sắp xếp đến cuối bằng:

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

Nhưng điều này bỏ qua trường hợp CẢ MỘT và hai trống - và trong trường hợp đó, giá trị sai được trả về (1 thay vì 0 để hiển thị khớp) và bộ so sánh báo cáo đó là vi phạm. Nó nên được viết là:

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

Ngay cả khi so sánh của bạn giữ tính siêu việt trong lý thuyết, đôi khi các lỗi tinh vi làm rối tung mọi thứ ... chẳng hạn như lỗi số học dấu phẩy động. Nó đã xảy ra với tôi. đây là mã của tôi:

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;

}   

Tài sản bắc cầu rõ ràng nắm giữ, nhưng vì một số lý do tôi đã nhận được IllegalArgumentException. Và hóa ra là do các lỗi nhỏ trong số học dấu phẩy động, các lỗi làm tròn khiến cho thuộc tính bắc cầu bị phá vỡ ở những nơi không nên! Vì vậy, tôi đã viết lại mã để xem xét sự khác biệt rất nhỏ 0 và nó đã hoạt động:

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
Điều này rất hữu ích! Mã của tôi đã ổn về mặt logic nhưng đã xảy ra lỗi do vấn đề chính xác.
JSong

6

Trong trường hợp của chúng tôi đã nhận được lỗi này vì chúng tôi đã vô tình lật thứ tự so sánh của s1 và s2. Vì vậy, xem ra cho điều đó. Rõ ràng là nó phức tạp hơn những điều sau đây nhưng đây là một minh họa:

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

3

Java không kiểm tra tính nhất quán theo nghĩa chặt chẽ, chỉ thông báo cho bạn nếu nó gặp rắc rối nghiêm trọng. Ngoài ra, nó không cung cấp cho bạn nhiều thông tin từ lỗi.

Tôi đã bối rối với những gì xảy ra trong trình sắp xếp của mình và thực hiện một sự thống nhất nghiêm ngặt, có thể điều này sẽ giúp bạn:

/**
 * @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));
    }
  }
}

Đơn giản chỉ cần gọi metohod checkConsitency với danh sách tham số và bộ so sánh.
Martin

Mã của bạn không biên dịch. Các lớp Compare, Convert(và có khả năng khác) không được xác định. Vui lòng cập nhật sniplet mã với một ví dụ độc lập.
Gili

Bạn nên sửa lỗi đánh máy checkConsi(s)tencyvà xóa tất cả các @paramkhai báo thừa để làm cho mã dễ đọc hơn.
Roland Illig

3

Trong trường hợp của tôi, tôi đã làm một cái gì đó như sau:

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);

Điều tôi quên kiểm tra là khi cả a.someField và b.someField đều null.


3

Tôi đã thấy điều này xảy ra trong một đoạn mã nơi kiểm tra định kỳ cho các giá trị null được thực hiện:

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

2

Nếu compareParents(s1, s2) == -1sau đó compareParents(s2, s1) == 1được dự kiến. Với mã của bạn, điều đó không phải lúc nào cũng đúng.

Cụ thể nếu s1.getParent() == s2 && s2.getParent() == s1. Đó chỉ là một trong những vấn đề có thể xảy ra.


1

Chỉnh sửa cấu hình VM làm việc cho tôi.

-Djava.util.Arrays.useLegacyMergeSort=true

Vui lòng kiểm tra lại xem nỗ lực của tôi để giúp bạn định dạng không phá vỡ bất cứ điều gì. Tôi không chắc chắn về -sự khởi đầu của giải pháp được đề xuất. Có thể bạn dự định một cái gì đó giống như một danh sách điểm đạn một mục thay thế.
Yunnosch

2
Ngoài ra xin vui lòng giải thích làm thế nào điều này giúp với vấn đề được mô tả. Nó hiện đang là một câu trả lời chỉ có mã.
Yunnosch

0

Bạn không thể so sánh dữ liệu đối tượng như thế này: s1.getParent() == s2- điều này sẽ so sánh các tham chiếu đối tượng. Bạn nên ghi đè equals functioncho lớp Foo và sau đó so sánh chúng như thế nàys1.getParent().equals(s2)


Không, thực sự tôi nghĩ OP đang cố gắng sắp xếp một danh sách sắp xếp nào đó và muốn thực sự so sánh các tài liệu tham khảo.
Edward Falk
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.