Lỗi Java: Phương pháp so sánh vi phạm hợp đồng chung của nó


84

Tôi thấy nhiều câu hỏi về vấn đề này và đã cố gắng giải quyết vấn đề, nhưng sau một giờ sử dụng googling và rất nhiều lần thử & lỗi, tôi vẫn không thể khắc phục được sự cố. Tôi hy vọng một số bạn nắm bắt được vấn đề.

Đây là những gì tôi nhận được:

java.lang.IllegalArgumentException: Comparison method violates its general contract!
    at java.util.ComparableTimSort.mergeHi(ComparableTimSort.java:835)
    at java.util.ComparableTimSort.mergeAt(ComparableTimSort.java:453)
    at java.util.ComparableTimSort.mergeForceCollapse(ComparableTimSort.java:392)
    at java.util.ComparableTimSort.sort(ComparableTimSort.java:191)
    at java.util.ComparableTimSort.sort(ComparableTimSort.java:146)
    at java.util.Arrays.sort(Arrays.java:472)
    at java.util.Collections.sort(Collections.java:155)
    ...

Và đây là bộ so sánh của tôi:

@Override
public int compareTo(Object o) {
    if(this == o){
        return 0;
    }

    CollectionItem item = (CollectionItem) o;

    Card card1 = CardCache.getInstance().getCard(cardId);
    Card card2 = CardCache.getInstance().getCard(item.getCardId());

    if (card1.getSet() < card2.getSet()) {
        return -1;
    } else {
        if (card1.getSet() == card2.getSet()) {
            if (card1.getRarity() < card2.getRarity()) {
                return 1;
            } else {
                if (card1.getId() == card2.getId()) {
                    if (cardType > item.getCardType()) {
                        return 1;
                    } else {
                        if (cardType == item.getCardType()) {
                            return 0;
                        }
                        return -1;
                    }
                }
                return -1;
            }
        }
        return 1;
    }
}

Bất kỳ ý tưởng?


Dòng mã nào khiến Exception này được ném ra? Có gì trên dòng 835 và 453 của CompishedTimSort.java?
Hovercraft Full Of Lươn

Đối với tôi, dường như bạn nên ủy nhiệm phương thức này cho Cardlớp: return card1.compareTo(card2)và thực hiện logic này ở đó.
Hunter McMillen,

2
@HovercraftFullOfEels Đó là một lớp của Oracle, không phải do tôi quét. Nó có một ngoại lệ trên dòng đó. Phương pháp này rất dài và có vẻ khó hiểu.
Lakatos Gyula,

@HunterMcMillen Tôi không thể làm điều đó. Thẻ chỉ chứa dữ liệu tĩnh của thẻ (tên, văn bản, v.v.) trong khi lớp này chứa một số biến thay đổi như kiểu và số lượng.
Lakatos Gyula,

1
Sau khi đọc cuốn sách Clean Code, tôi cũng không biết nữa.
Lakatos Gyula

Câu trả lời:


97

Thông báo ngoại lệ thực sự khá mô tả. Hợp đồng nó đề cập đến là transitivity : nếu A > BB > Csau đó cho bất kỳ A, BC: A > C. Tôi đã kiểm tra nó bằng giấy và bút chì và mã của bạn dường như có vài lỗ hổng:

if (card1.getRarity() < card2.getRarity()) {
  return 1;

bạn không trở lại -1nếu card1.getRarity() > card2.getRarity().


if (card1.getId() == card2.getId()) {
  //...
}
return -1;

Bạn trả lại -1nếu id không bằng nhau. Bạn nên trả lại -1hoặc 1tùy thuộc vào id nào lớn hơn.


Hãy xem này. Ngoài việc dễ đọc hơn, tôi nghĩ nó thực sự nên hoạt động:

if (card1.getSet() > card2.getSet()) {
    return 1;
}
if (card1.getSet() < card2.getSet()) {
    return -1;
};
if (card1.getRarity() < card2.getRarity()) {
    return 1;
}
if (card1.getRarity() > card2.getRarity()) {
    return -1;
}
if (card1.getId() > card2.getId()) {
    return 1;
}
if (card1.getId() < card2.getId()) {
    return -1;
}
return cardType - item.getCardType();  //watch out for overflow!

15
Trên thực tế, ví dụ bạn đưa ra không vi phạm độ nhạy nhưng quy tắc sgn (so sánh (x, y)) == -sgn (so sánh (y, x)) như đã nêu trong docs.oracle.com/javase/7/docs/ api / java / use /… - đó là sự phản đối xứng về mặt toán học.
Hans-Peter Störr

1
Tôi đề nghị sử dụng if (card1.getSet() != card2.getSet()) return card1.getSet() > card2.getSet() ? 1 : -1;để tăng tốc độ, nếu ai đó quan tâm.
maaartinus

Tôi gặp phải điều này và đó là một vấn đề đối xứng trong Bộ so sánh của tôi. Phần khó để chẩn đoán là điểm yếu của tôi là trường đầu tiên (kiểm tra trường boolean, không phải là so sánh thích hợp) không kiểm tra xem cả hai đều đúng. Điều này chỉ được tiết lộ, tuy nhiên, sau khi tôi thêm một so sánh cấp dưới tiếp theo, điều này hẳn đã làm xáo trộn thứ tự sắp xếp.
Thomas W

1
@maaartinus cảm ơn bạn đã gợi ý! Nó là hoàn hảo cho tôi :)
Sonhja

45

Bạn có thể sử dụng lớp sau để xác định lỗi độ nhạy trong Bộ so sánh của bạn:

/**
 * @author Gili Tzabari
 */
public final class Comparators
{
    /**
     * Verify that a comparator is transitive.
     *
     * @param <T>        the type being compared
     * @param comparator the comparator to test
     * @param elements   the elements to test against
     * @throws AssertionError if the comparator is not transitive
     */
    public static <T> void verifyTransitivity(Comparator<T> comparator, Collection<T> elements)
    {
        for (T first: elements)
        {
            for (T second: elements)
            {
                int result1 = comparator.compare(first, second);
                int result2 = comparator.compare(second, first);
                if (result1 != -result2)
                {
                    // Uncomment the following line to step through the failed case
                    //comparator.compare(first, second);
                    throw new AssertionError("compare(" + first + ", " + second + ") == " + result1 +
                        " but swapping the parameters returns " + result2);
                }
            }
        }
        for (T first: elements)
        {
            for (T second: elements)
            {
                int firstGreaterThanSecond = comparator.compare(first, second);
                if (firstGreaterThanSecond <= 0)
                    continue;
                for (T third: elements)
                {
                    int secondGreaterThanThird = comparator.compare(second, third);
                    if (secondGreaterThanThird <= 0)
                        continue;
                    int firstGreaterThanThird = comparator.compare(first, third);
                    if (firstGreaterThanThird <= 0)
                    {
                        // Uncomment the following line to step through the failed case
                        //comparator.compare(first, third);
                        throw new AssertionError("compare(" + first + ", " + second + ") > 0, " +
                            "compare(" + second + ", " + third + ") > 0, but compare(" + first + ", " + third + ") == " +
                            firstGreaterThanThird);
                    }
                }
            }
        }
    }

    /**
     * Prevent construction.
     */
    private Comparators()
    {
    }
}

Chỉ cần gọi Comparators.verifyTransitivity(myComparator, myCollection)trước mã không thành công.


3
Đoạn mã này xác thực so sánh (a, b) = - so sánh (b, c). Nó không kiểm tra xem nếu so sánh (a, b)> 0 && so sánh (b, c)> 0 sau đó so sánh (a, c)> 0
Olaf Achthoven

3
Tôi thực sự thấy lớp học của bạn rất, rất hữu ích. Cảm ơn.
crigore,

Cảm ơn bạn, tôi đã phải quay lại và ủng hộ câu trả lời vì lớp này rất hữu ích khi gỡ lỗi trình so sánh, không chỉ cho mục đích được đề cập ở đây vì nó cũng có thể được thay đổi để kiểm tra các trường hợp khác!
Jaco-Ben Vosloo

36

Nó cũng có liên quan đến phiên bản JDK. Nếu nó hoạt động tốt trong JDK6, có thể nó sẽ gặp sự cố trong JDK 7 do bạn mô tả, vì phương pháp triển khai trong jdk 7 đã được thay đổi.

Nhìn vào cái này:

Mô tả: Thuật toán sắp xếp được sử dụng bởi java.util.Arrays.sortvà (gián tiếp) bởi java.util.Collections.sortđã được thay thế. Việc triển khai sắp xếp mới có thể ném ra IllegalArgumentExceptionnếu nó phát hiện ra một Comparablevi phạm Comparablehợp đồng. Việc thực hiện trước đó đã âm thầm bỏ qua một tình huống như vậy. Nếu hành vi trước đó được mong muốn, bạn có thể sử dụng thuộc tính hệ thống mới java.util.Arrays.useLegacyMergeSort, để khôi phục hành vi hợp nhất trước đó.

Tôi không biết lý do chính xác. Tuy nhiên, nếu bạn thêm mã trước khi sử dụng sắp xếp. Sẽ ổn thôi.

System.setProperty("java.util.Arrays.useLegacyMergeSort", "true");

1
Vấn đề nằm ở logic mà tôi sử dụng để so sánh mọi thứ. Tôi sẽ ủng hộ cái này, hy vọng nó có thể giúp ích cho ai đó đang tìm kiếm loại vấn đề này.
Lakatos Gyula

10
Điều đó chỉ nên được sử dụng như một giải pháp nhanh chóng và xấu xí để làm cho mọi thứ hoạt động trở lại cho đến khi bạn sửa chữa bộ so sánh. Nếu trường hợp ngoại lệ xảy ra, điều đó có nghĩa là trình so sánh của bạn bị lỗi và thứ tự sắp xếp sẽ khác lạ. Bạn phải xem xét tất cả các quy tắc được đưa ra trong docs.oracle.com/javase/7/docs/api/java/util/… .
Hans-Peter Störr

Điều gì sẽ xảy ra nếu "kỳ lạ" là những gì tôi sẽ làm? (a,b) -> { return (new int[]{-1,1})[(int)((Math.random()*2)%2)]}Tôi biết Collections.shufflecó tồn tại, nhưng tôi không thích được bảo vệ quá mức.
Hang động Jefferey Cave

Tôi có một ứng dụng cũ và với JDK8, tôi gặp lỗi này. Sử dụng JDK6 nó hoạt động chính xác, vì vậy tôi chỉ cần hạ cấp xuống 6, cảm ơn.
Stefano Scarpanti

6

Hãy xem xét trường hợp sau:

Đầu tiên, o1.compareTo(o2)được gọi là. card1.getSet() == card2.getSet()xảy ra đúng và như vậy là đúng card1.getRarity() < card2.getRarity(), vì vậy bạn trả về 1.

Sau đó, o2.compareTo(o1)được gọi là. Một lần nữa, card1.getSet() == card2.getSet()là sự thật. Sau đó, bạn bỏ qua phần sau else, thì điều đó card1.getId() == card2.getId()xảy ra đúng, và cũng vậy cardType > item.getCardType(). Bạn quay lại 1 lần nữa.

Từ đó o1 > o2, và o2 > o1. Bạn đã phá vỡ hợp đồng.


2
        if (card1.getRarity() < card2.getRarity()) {
            return 1;

Tuy nhiên, nếu card2.getRarity()nhỏ hơn card1.getRarity()bạn có thể không trả về -1 .

Tương tự bạn cũng bỏ lỡ những trường hợp khác. Tôi sẽ làm điều này, bạn có thể thay đổi tùy thuộc vào ý định của mình:

public int compareTo(Object o) {    
    if(this == o){
        return 0;
    }

    CollectionItem item = (CollectionItem) o;

    Card card1 = CardCache.getInstance().getCard(cardId);
    Card card2 = CardCache.getInstance().getCard(item.getCardId());
    int comp=card1.getSet() - card2.getSet();
    if (comp!=0){
        return comp;
    }
    comp=card1.getRarity() - card2.getRarity();
    if (comp!=0){
        return comp;
    }
    comp=card1.getSet() - card2.getSet();
    if (comp!=0){
        return comp;
    }   
    comp=card1.getId() - card2.getId();
    if (comp!=0){
        return comp;
    }   
    comp=card1.getCardType() - card2.getCardType();

    return comp;

    }
}

1

Nó cũng có thể là một lỗi OpenJDK ... (không phải trong trường hợp này nhưng nó là cùng một lỗi)

Nếu ai đó như tôi tình cờ gặp câu trả lời này liên quan đến

java.lang.IllegalArgumentException: Comparison method violates its general contract!

thì nó cũng có thể là một lỗi trong Phiên bản Java. Tôi có một so sánh Để chạy từ vài năm nay trong một số ứng dụng. Nhưng đột nhiên nó ngừng hoạt động và ném lỗi sau khi tất cả các so sánh đã được thực hiện (tôi so sánh 6 thuộc tính trước khi trả về "0").

Bây giờ tôi vừa tìm thấy Báo cáo lỗi này của OpenJDK:


1

Tôi đã có cùng một triệu chứng. Đối với tôi, hóa ra là một luồng khác đang sửa đổi các đối tượng được so sánh trong khi việc phân loại đang diễn ra trong Luồng. Để giải quyết vấn đề, tôi đã ánh xạ các đối tượng thành các đối tượng tạm thời bất biến, thu thập Luồng vào Bộ sưu tập tạm thời và thực hiện sắp xếp trên đó.


0

Tôi đã phải sắp xếp theo một số tiêu chí (ngày tháng, và nếu giống ngày tháng; những thứ khác ...). Những gì đang hoạt động trên Eclipse với phiên bản Java cũ hơn, không hoạt động nữa trên Android: phương pháp so sánh vi phạm hợp đồng ...

Sau khi đọc trên StackOverflow, tôi đã viết một hàm riêng biệt mà tôi đã gọi từ so sánh () nếu ngày tháng giống nhau. Hàm này tính toán mức độ ưu tiên, theo tiêu chí và trả về -1, 0 hoặc 1 để so sánh (). Nó dường như hoạt động ngay bây giờ.


0

Tôi gặp lỗi tương tự với một lớp như sau StockPickBean. Được gọi từ mã này:

List<StockPickBean> beansListcatMap.getValue();
beansList.sort(StockPickBean.Comparators.VALUE);

public class StockPickBean implements Comparable<StockPickBean> {
    private double value;
    public double getValue() { return value; }
    public void setValue(double value) { this.value = value; }

    @Override
    public int compareTo(StockPickBean view) {
        return Comparators.VALUE.compare(this,view); //return 
        Comparators.SYMBOL.compare(this,view);
    }

    public static class Comparators {
        public static Comparator<StockPickBean> VALUE = (val1, val2) -> 
(int) 
         (val1.value - val2.value);
    }
}

Sau khi gặp lỗi tương tự:

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

Tôi đã thay đổi dòng này:

public static Comparator<StockPickBean> VALUE = (val1, val2) -> (int) 
         (val1.value - val2.value);

đến:

public static Comparator<StockPickBean> VALUE = (StockPickBean spb1, 
StockPickBean spb2) -> Double.compare(spb2.value,spb1.value);

Điều đó sửa chữa lỗi.


0

Tôi đã gặp phải một vấn đề tương tự trong đó tôi đang cố gắng sắp xếp một cái n x 2 2D arrayđược đặt tên contestslà một mảng 2D gồm các số nguyên đơn giản. Điều này đã hoạt động trong hầu hết các lần nhưng đã gây ra lỗi thời gian chạy cho một đầu vào: -

Arrays.sort(contests, (row1, row2) -> {
            if (row1[0] < row2[0]) {
                return 1;
            } else return -1;
        });

Lỗi:-

Exception in thread "main" java.lang.IllegalArgumentException: Comparison method violates its general contract!
    at java.base/java.util.TimSort.mergeHi(TimSort.java:903)
    at java.base/java.util.TimSort.mergeAt(TimSort.java:520)
    at java.base/java.util.TimSort.mergeForceCollapse(TimSort.java:461)
    at java.base/java.util.TimSort.sort(TimSort.java:254)
    at java.base/java.util.Arrays.sort(Arrays.java:1441)
    at com.hackerrank.Solution.luckBalance(Solution.java:15)
    at com.hackerrank.Solution.main(Solution.java:49)

Nhìn vào các câu trả lời ở trên, tôi đã thử thêm một điều kiện equalsvà tôi không biết tại sao nhưng nó hoạt động. Hy vọng rằng chúng ta phải chỉ định rõ ràng những gì sẽ được trả về cho tất cả các trường hợp (lớn hơn, bằng và nhỏ hơn):

        Arrays.sort(contests, (row1, row2) -> {
            if (row1[0] < row2[0]) {
                return 1;
            }
            if(row1[0] == row2[0]) return 0;
            return -1;
        });
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.