Java 集合排序:比较方法违反其一般约定

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/28004269/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 12:47:46  来源:igfitidea点击:

Java Collections sort: Comparison method violates its general contract

javasortingcollectionscomparison

提问by math_law

I know it has been asked and answered millions of times but still I am unable to figure out why I am receiving with the violation during sort. Here is my code:

我知道它已经被询问和回答了数百万次,但我仍然无法弄清楚为什么我在排序过程中收到了违规行为。这是我的代码:

Collections.sort(sorted, new Comparator<MyObject>() {
    @Override
    public int compare(MyObject m1, MyObject m2) {
        // Actual energy comparison :-
        // THE higher the energy, the earlier in the list
        float delta = m1.getTotalEnergy() - m2.getTotalEnergy();

        if (delta > 0) {
            return 1;
        } else if (delta < 0) {
            return -1;
        } else {
            return 0;
        }
    }
});

and I receive this error

我收到这个错误

java.lang.IllegalArgumentException: Comparison method violates its general contract!  
        at java.util.TimSort.mergeHi(TimSort.java:895)  
        at java.util.TimSort.mergeAt(TimSort.java:512)  
        at java.util.TimSort.mergeForceCollapse(TimSort.java:453)  
        at java.util.TimSort.sort(TimSort.java:250)  
        at java.util.Arrays.sort(Arrays.java:1512)  
        at java.util.ArrayList.sort(ArrayList.java:1454)  
        at java.util.Collections.sort(Collections.java:175)

Any ideas ?

有任何想法吗 ?

回答by Elliott Frisch

Assuming getTotalEnergy()return(s) float, you could use

假设getTotalEnergy()return(s) float,你可以使用

return new Float(m1.getTotalEnergy()).compareTo(m2.getTotalEnergy());

Using Float.valueOf(float)is probably slightly more efficient, and hopefully this is easier to read.

使用Float.valueOf(float)可能稍微更有效,希望这更容易阅读。

Float f1 = Float.valueOf(m1.getTotalEnergy());
Float f2 = Float.valueOf(m2.getTotalEnergy());
return f1.compareTo(f2);

回答by Dunes

Without reference to MyObject. My guess is that the comparator is inconsistent with MyObject.equal.

不参考MyObject. 我的猜测是比较器与MyObject.equal.

That is, the contract you are violating is:

也就是说,你违反的合同是:

(comparator.compare(mo1, mo2) == 0) == mo1.equals(mo2)

Your comparator will compare objects with the same float value as being equal, where a more complex comparator would give an ordering, whilst the equals method would say the objects were not equal. Or you could have the reverse problem -- the equals method says the objects are equal and the compare method says they are different.

您的比较器会将具有相同浮点值的对象比较为相等,其中更复杂的比较器会给出排序,而 equals 方法会说对象不相等。或者您可能会遇到相反的问题——equals 方法表示对象相等,而 compare 方法表示它们不同。

The following should work.

以下应该工作。

public int compare(MyObject m1, MyObject m2) {
    if (m1 == m2) return 0;
    if (m1 == null) return -1;
    if (m2 == null) return 1;
    if (m1.equals(m2)) return 0;

    int value = Float.compare(m1.getTotalEnergy(), m2.getTotalEnergy());
    if (value != 0) return value;

    // Warning, this line is not fool proof as unequal objects can have identical hash 
    // codes.
    return m1.hashCode() - m2.hashCode();
}

回答by Mr. Polywhirl

The following code has been tested with float, NaN, and null values. All cases are handled correctly. I created a hashCode()and equals()method to the MyObjectclass.

以下代码已使用 float、NaN 和 null 值进行测试。所有案件都得到正确处理。我为类创建了一个hashCode()equals()方法MyObject

Result: [null, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, NaN]

结果: [null, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, NaN]

SortObjectFloatProperty

SortObjectFloat 属性

package q28004269;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class SortObjectFloatProperty {
    public static void main(String[] args) {
        List<MyObject> sorted;

        sorted = create(1f, 4f, 6f, null, 8f, 5f, Float.NaN, 3f, 2f, 7f, 0f, 9f);

        Collections.sort(sorted, new Comparator<MyObject>() {
            @Override
            public int compare(MyObject m1, MyObject m2) {
                if (m1 == m2) return 0;
                if (m1 == null) return -1;
                if (m2 == null) return 1;
                if (m1.equals(m2)) return 0;
                int value = Float.compare(m1.getTotalEnergy(), m2.getTotalEnergy());
                if (value != 0) return value;
                return m1.hashCode() - m2.hashCode();
            }
        });

        System.out.println(sorted);
    }

    public static MyObject create(float totalEnergy) {
        return new MyObject(totalEnergy);
    }

    public static List<MyObject> create(Object ...values) {
        List<MyObject> objs = new ArrayList<SortObjectFloatProperty.MyObject>();
        for (int i = 0; i < values.length; i++) {
            if (values[i] instanceof Float) {
                objs.add(create((float) values[i]));
            } else {
                objs.add(null);
            }
        }
        return objs;
    }
}

MyObject

我的对象

package q28004269;

public static class MyObject {
    private float totalEnergy;

    public float getTotalEnergy() {
        return totalEnergy;
    }

    public void setTotalEnergy(float totalEnergy) {
        this.totalEnergy = totalEnergy;
    }

    public MyObject() {
        this(0.0f);
    }

    public MyObject(float totalEnergy) {
        this.totalEnergy = totalEnergy;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + Float.floatToIntBits(totalEnergy);
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null) return false;
        if (getClass() != obj.getClass()) return false;
        MyObject other = (MyObject) obj;
        return !(Float.floatToIntBits(totalEnergy) != Float.floatToIntBits(other.totalEnergy));
    }

    @Override
    public String toString() {
        return Float.toString(totalEnergy);
    }
}

回答by Stuart Marks

There was some discussion of NaNvalues in the comments. This is important, because NaNviolates our typical expectations of comparisons of floating point numbers. For example, Double.NaN > 0.0and Double.NaN < 1.0are both false!

评论中有一些关于NaN价值观的讨论。这很重要,因为NaN违反了我们对浮点数比较的典型期望。例如,Double.NaN > 0.0Double.NaN < 1.0都是假的!

This may cause the sort to throw the exception as you encountered. Even if the exception isn't thrown, it may cause the list to end up sorted in the wrong order. Therefore, your sort comparator mustdeal with NaNvalues. Fortunately, the library's built-in comparisons such as Double.compare()do this for you. This has the same semantics as Double.compareTo(), except that boxed Doublevalues aren't necessary. See the documentationfor details. Briefly, NaNvalues are considered greater than all other values (including +Inf), and negative zero is less than positive zero.

这可能会导致排序在您遇到时抛出异常。即使没有抛出异常,也可能导致列表以错误的顺序排序。因此,您的排序比较器必须处理NaN值。幸运的是,库的内置比较Double.compare()可以为您执行此操作。这与 具有相同的语义Double.compareTo(),只是Double不需要装箱的值。有关详细信息,请参阅文档。简而言之,NaN值被认为大于所有其他值(包括+Inf),负零小于正零。

To use Double.compare()in a Comparatorfor sort, do this:

Double.compare()Comparatorfor 中使用sort,请执行以下操作:

Collections.sort(data, new Comparator<MyObject>() {
    public int compare(MyObject m1, MyObject m2) {
        return Double.compare(m1.getTotalEnergy(), m2.getTotalEnergy());
    }
});

If you're using Java 8, you could do this:

如果您使用的是 Java 8,则可以执行以下操作:

data.sort(Comparator.comparing(MyObject::getTotalEnergy));

To handle nulls, wrap the comparator with nullsFirstor nullsLast:

要处理空值,请使用nullsFirst或包装比较器nullsLast

data.sort(Comparator.nullsFirst(Comparator.comparing(MyObject::getTotalEnergy)));

回答by roeygol

Maybe this will make the change:

也许这会带来改变:

public int compare(Object m1, Object m2) {
    // Actual energy comparison :-
    // THE higher the energy, the earlier in the list
    float delta = ((MyObject)m1).getTotalEnergy() - ((MyObject)m2).getTotalEnergy();
....
}