जावा त्रुटि: तुलना विधि अपने सामान्य अनुबंध का उल्लंघन करती है


86

मैंने इस बारे में कई सवाल देखे, और समस्या को हल करने की कोशिश की, लेकिन एक घंटे के गोग्लिंग और बहुत सारे परीक्षण और त्रुटि के बाद, मैं अभी भी इसे ठीक नहीं कर सकता। मुझे आशा है कि आप में से कुछ समस्या को पकड़ेंगे।

यह वही है जो मुझे मिलता है:

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

और यह मेरा तुलनित्र है:

@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;
    }
}

कोई उपाय?


किस कोड की वजह से यह अपवाद फेंका जा सकता है? तुलनीय 8 की 453 और 453 लाइनों पर क्या है। जावा?
होल्सक्राफ्ट

यह मुझे लगता है कि आपके पास Cardकक्षा के लिए यह विधि होनी चाहिए : return card1.compareTo(card2)और इस तर्क को वहां लागू करें।
हंटर मैकमिलन

2
@HovercraftFullOfEels यह ओरेकल का एक वर्ग है, मेरे द्वारा नहीं मिटाया गया। यह उस लाइन पर एक अपवाद है। विधि बहुत लंबी है और समझने में कठिन लगती है।
लाकटोस गयुला

@HterterMcMillen मैं ऐसा नहीं कर सकता। कार्ड में केवल कार्ड (नाम, पाठ आदि ...) के स्थिर डेटा होते हैं, जबकि इस वर्ग में कुछ बदलते प्रकार और राशि जैसे चर होते हैं।
लाकटोस गयुला

1
क्लीन कोड बुक पढ़ने के बाद मुझे पता नहीं है।
लाकटोस ग्युल

जवाबों:


99

अपवाद संदेश वास्तव में बहुत वर्णनात्मक है। अनुबंध यह उल्लेख है संक्रामिता : यदि A > Bऔर B > Cउसके बाद के लिए किसी भी A, Bऔर C: A > C। मैंने इसे कागज और पेंसिल से जांचा और आपके कोड में कुछ छेद हैं:

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

आप नहीं लौटते -1, तो card1.getRarity() > card2.getRarity()


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

-1यदि आईडी बराबर नहीं हैं तो आप वापस लौटते हैं। आपको वापस लौटना चाहिए -1या 1इस पर निर्भर करना चाहिए कि कौन सा आईडी बड़ा था


इस पर एक नजर डालिए। अधिक पठनीय होने के अलावा, मुझे लगता है कि यह वास्तव में काम करना चाहिए:

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
वास्तव में, आपके द्वारा दिया गया उदाहरण ट्रांज़िटिविटी का उल्लंघन नहीं करता है, लेकिन नियम sgn (तुलना (x, y)) == -sgn (तुलना (y, x)) docs.oracle.com/javase/7/docs/ में कहा गया है एपीआई / जावा / उपयोग /… - यह गणितीय शब्दों में एंटीसिममेट्री है।
हंस-पीटर स्टॉर

1
if (card1.getSet() != card2.getSet()) return card1.getSet() > card2.getSet() ? 1 : -1;अगर कोई परवाह करता है, तो मैं और अधिक गति के लिए उपयोग करने का सुझाव दूंगा।
Maaartinus

मुझे इसका सामना करना पड़ा और यह मेरे कम्प्लेटर में एक समरूपता का मुद्दा था । निदान करने के लिए कठिन हिस्सा यह था कि मेरी कमजोरी पहला क्षेत्र था (एक बूलियन क्षेत्र की जांच, उचित तुलना नहीं) जो दोनों सही सही होने के लिए जांच नहीं करते थे। यह केवल तब उजागर हुआ, जब मैंने बाद में एक अधीनस्थ तुलना जोड़ी, जो क्रमबद्ध क्रम को बनाए रखना चाहिए।
थॉमस डब्ल्यू

1
@maaartinus आपके सुझाव के लिए धन्यवाद! यह मेरे लिए बिल्कुल सही था :)
सोनाजा

46

आप अपने कंप्रेशर्स में ट्रांज़िटिविटी बग्स को इंगित करने के लिए निम्न वर्ग का उपयोग कर सकते हैं:

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

बस Comparators.verifyTransitivity(myComparator, myCollection)कोड के सामने विफल रहता है।


3
यह कोड तुलना करता है (ए, बी) = - तुलना (बी, सी)। यह जाँच नहीं करता है कि यदि तुलना करें (a, b)> 0 && तुलना करें (b, c)> 0 तो तुलना करें (a, c)> 0
Olaf Achthoven

3
मैंने वास्तव में आपकी कक्षा को बहुत उपयोगी पाया है। धन्यवाद।
crigore

धन्यवाद, मुझे वापस आना पड़ा और उत्तर को उखाड़ फेंकना पड़ा क्योंकि यह वर्ग एक तुलनित्र को डिबग करते समय बहुत उपयोगी है, न कि केवल यहाँ बताए गए उद्देश्य के लिए क्योंकि इसे अन्य परिस्थितियों को परखने के लिए बदला जा सकता है!
जैको-बेन वोस्लोवो

37

इसका जेडीके के संस्करण के साथ भी कुछ लेना देना है। यदि यह JDK6 में अच्छा करता है, तो शायद आपके द्वारा वर्णित JDK 7 में यह समस्या होगी, क्योंकि jdk 7 में कार्यान्वयन विधि बदल दी गई है।

इसे देखो:

विवरण: java.util.Arrays.sortऔर (अप्रत्यक्ष रूप से) द्वारा उपयोग किए जाने वाले छँटाई एल्गोरिथ्म java.util.Collections.sortको बदल दिया गया है। IllegalArgumentExceptionयदि यह अनुबंध Comparableका उल्लंघन करता है तो नए प्रकार का कार्यान्वयन फेंक सकता है Comparable। पिछले कार्यान्वयन ने चुपचाप ऐसी स्थिति को अनदेखा कर दिया। यदि पिछला व्यवहार वांछित है, तो आप नए सिस्टम गुण का उपयोग कर सकते हैं, java.util.Arrays.useLegacyMergeSortपिछले विलय के व्यवहार को पुनर्स्थापित करने के लिए।

मुझे इसका सही कारण नहीं पता। हालाँकि, यदि आप सॉर्ट का उपयोग करने से पहले कोड जोड़ते हैं। यह ठीक हो जाएगा।

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

1
समस्या तर्क में थी जो मैं चीजों की तुलना करता था। मैं इसे उखाड़ फेंकूंगा, उम्मीद है कि यह किसी ऐसे व्यक्ति की मदद कर सकता है जो इस प्रकार की समस्या की तलाश कर रहा है।
लाकटोस ग्युला

10
जब तक आप तुलनित्र को ठीक नहीं करते हैं, तब तक केवल चीजों को फिर से काम करने के लिए एक त्वरित और बदसूरत वर्कअराउंड के रूप में उपयोग किया जाना चाहिए। यदि अपवाद होता है, तो इसका मतलब है कि आप तुलनित्र छोटी गाड़ी है और सॉर्ट क्रम अजीब होगा। आपको docs.oracle.com/javase/7/docs/api/java/utav// में दिए गए सभी नियमों पर विचार करना होगा
हंस-पीटर स्टॉर

क्या होगा अगर "अजीब" वह है जो मैं जा रहा था? (a,b) -> { return (new int[]{-1,1})[(int)((Math.random()*2)%2)]}मुझे पता Collections.shuffleहै कि अस्तित्व में है, लेकिन मैं अधिक संरक्षित होना पसंद नहीं करता।
जेफरी गुफा

मेरे पास एक पुराना एप्लिकेशन है और JDK8 के साथ मुझे यह त्रुटि मिली है। JDK6 का उपयोग करना यह सही ढंग से काम करता है, इसलिए मैं केवल 6 को डाउनग्रेड करता हूं, धन्यवाद।
स्टेफानो स्कार्पेंटी

6

निम्नलिखित मामले पर विचार करें:

सबसे पहले, o1.compareTo(o2)कहा जाता है। card1.getSet() == card2.getSet()सच होता है और ऐसा ही होता है card1.getRarity() < card2.getRarity(), इसलिए आप 1 पर लौटते हैं।

फिर, o2.compareTo(o1)कहा जाता है। फिर से, card1.getSet() == card2.getSet()सच है। फिर, आप निम्न पर जाएं else, तब card1.getId() == card2.getId()सत्य होता है, और ऐसा ही है cardType > item.getCardType()। आप 1 फिर से लौटते हैं।

उससे o1 > o2, और o2 > o1। आपने अनुबंध तोड़ दिया।


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

हालांकि, अगर आप card2.getRarity()से कम है तो card1.getRarity()आप -1 नहीं लौट सकते ।

आप इसी तरह अन्य मामलों को याद करते हैं। मैं यह करूँगा, आप अपने इरादे के आधार पर चारों ओर बदल सकते हैं:

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

यह एक OpenJDK बग भी हो सकता है ... (इस मामले में नहीं, लेकिन यह वही त्रुटि है)

अगर मेरे जैसा कोई इस बारे में जवाब पर ठोकर खाता है

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

तब यह जावा-संस्करण में एक बग भी हो सकता है। मैं कुछ अनुप्रयोगों में कई वर्षों से एक तुलना चल रहा है। लेकिन अचानक यह काम करना बंद कर देता है और सभी तुलनाओं के बाद त्रुटि को फेंक देता है (मैं "0" लौटने से पहले 6 गुण की तुलना करता हूं)।

अब मुझे बस OpenJDK का यह Bugreport मिला:


1

मेरा भी यही लक्षण था। मेरे लिए यह पता चला कि एक अन्य धागा तुलनात्मक वस्तुओं को संशोधित कर रहा था, जबकि एक स्ट्रीम में छंटनी हो रही थी। समस्या को हल करने के लिए, मैंने वस्तुओं को अपरिवर्तनीय अस्थायी वस्तुओं के लिए मैप किया, स्ट्रीम को एक अस्थायी संग्रह में एकत्र किया और उस पर छंटनी की।


0

मुझे कई कसौटी पर खरा उतरना था (तारीख, और, अगर एक ही तारीख; अन्य चीजें ...)। जावा के पुराने संस्करण के साथ ग्रहण पर क्या काम कर रहा था, एंड्रॉइड पर कोई अधिक काम नहीं किया: तुलना विधि अनुबंध का उल्लंघन करती है ...

स्टैकऑवरफ्लो पर पढ़ने के बाद, मैंने एक अलग फ़ंक्शन लिखा था जिसे मैंने तुलना () से कहा था यदि तिथियां समान हैं। यह फ़ंक्शन मानदंड के अनुसार प्राथमिकता की गणना करता है, और तुलना करने के लिए -1, 0, या 1 देता है ()। यह अब काम करने लगता है।


0

मुझे निम्न की तरह एक वर्ग के साथ एक ही त्रुटि मिली StockPickBean। इस कोड से कॉल किया गया:

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

एक ही त्रुटि मिलने के बाद:

java.lang.IllegalArgumentException: तुलना विधि इसके सामान्य अनुबंध का उल्लंघन करती है!

मैंने इस लाइन को बदल दिया:

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

सेवा:

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

जो त्रुटि को ठीक करता है।


0

मैं एक ऐसी ही समस्या में भाग गया, जहाँ मैं एक n x 2 2D arrayनाम को क्रमबद्ध करने की कोशिश कर रहा था contestsजो सरल पूर्णांक का 2 डी सरणी है। यह ज्यादातर समय के लिए काम कर रहा था, लेकिन एक इनपुट के लिए रनटाइम त्रुटि को फेंक दिया: -

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

त्रुटि: -

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)

ऊपर दिए गए जवाबों को देखते हुए मैंने इसके लिए एक शर्त जोड़ने की कोशिश की equalsऔर मुझे नहीं पता कि क्यों, लेकिन यह काम किया। उम्मीद है कि हमें स्पष्ट रूप से निर्दिष्ट करना चाहिए कि सभी मामलों के लिए क्या लौटाया जाना चाहिए (अधिक से अधिक, बराबर और कम से कम)

        Arrays.sort(contests, (row1, row2) -> {
            if (row1[0] < row2[0]) {
                return 1;
            }
            if(row1[0] == row2[0]) return 0;
            return -1;
        });
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.