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

Java集合排序:比较方法违反其一般约定,java,sorting,collections,comparison,Java,Sorting,Collections,Comparison,我知道它已经被询问和回答了数百万次,但我仍然无法弄清楚为什么我在分拣过程中收到了违规行为。这是我的密码: Collections.sort(sorted, new Comparator<MyObject>() { @Override public int compare(MyObject m1, MyObject m2) { // Actual energy comparison :- // THE higher the energy,

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

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

有什么想法吗

也许这会带来变化:

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

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

return new Float(m1.getTotalEnergy()).compareTo(m2.getTotalEnergy());
使用可能会稍微高效一些,希望这更容易阅读

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

不参考
MyObject
。我猜想比较器与
MyObject.equal
不一致

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

(comparator.compare(mo1, mo2) == 0) == mo1.equals(mo2)
比较器将比较具有相同浮点值的对象作为相等对象,其中更复杂的比较器将给出排序,而equals方法将表示对象不相等。或者你可能会遇到相反的问题——equals方法表示对象相等,compare方法表示对象不同

以下几点应该行得通

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

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

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

Sortobject属性
评论中对
NaN
值进行了一些讨论。这很重要,因为
NaN
违反了我们对浮点数比较的典型期望。例如,
Double.NaN>0.0
Double.NaN<1.0
都是假的

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

要在
比较器中使用
Double.compare()
进行
排序
,请执行以下操作:

Collections.sort(data, new Comparator<MyObject>() {
    public int compare(MyObject m1, MyObject m2) {
        return Double.compare(m1.getTotalEnergy(), m2.getTotalEnergy());
    }
});
要处理空值,请使用
nullsFirst
nullsLast
包装比较器:

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


排序运行时是否有其他线程正在更改totalEnergy的值?在此异常期间,@DavidXu nope的可能重复单线程正在处理。您可以使用:return Float.compare(m1.getTotalEnergy(),m2.getTotalEnergy());我复制了这段代码,它没有任何问题。你能告诉我们“分类”收集的输入数据和类型吗?像一个最低限度的可复制样品吗?@Polywhill先生,我不是。它返回的是带有的结果。是的,但是代码中的
返回在哪里?希望不是在
compare()
中。也许代码可以写得更明显一些。这段代码看起来确实像是向我返回了一个浮点数。更有意义。首选
返回Float.valueOf(m1.getTotalEnergy()).compareTo(m2.getTotalEnergy())
允许它使用缓存中的
浮点值
,而不是总是创建一个新的。啊,没关系。我错过了比较
。在其他新闻中,不要在回答中使用“编辑”。每个堆栈溢出帖子都有一个全面的编辑历史记录,任何人都可以查看。已找到此答案的编辑历史记录。是的,这是一个很好的猜测,因为NaN不会导致Collections.sort失败。@Dunes谢谢,在尝试Atuos对Float的建议后。比较(希望它能处理NaN)我会尝试你的建议。BR。@math_law我查过了-NaN的不是问题所在。你能给我们看看MyObject.equals和MyObject.hashCode吗?@R4J我还没有重写这些方法。还有什么我可以提供的吗?@R4J可能是个NaN。如果一个物体的总能量为NaN,尽管它等于它自己,但它与它自身的比较是不同的。我刚刚使用了这里提出的compareTo方法,它成功了。不确定我的对象是否为Null,或者在totalEnergy方法中是否有一些NaN值。虽然我更喜欢单线解决方案(Stuart或Elliot的),但在这个线程中唯一有效的解决方案是这个。谢谢大家。@math_law还是一样吗?尝试检测比较器,即打印(或记录)每个调用的输入和结果,然后在获取异常之前查看最后一个条目。@math\u-law如果您的输入确实有空值,请尝试使用
comparator.nullFirst
comparator.nullsLast
(仅限Java 8)包装比较器。@math\u-law,
getTotalEnergy
是否总是为
MyObject
的任何给定实例返回相同的值?
Collections.sort(data, new Comparator<MyObject>() {
    public int compare(MyObject m1, MyObject m2) {
        return Double.compare(m1.getTotalEnergy(), m2.getTotalEnergy());
    }
});
data.sort(Comparator.comparing(MyObject::getTotalEnergy));
data.sort(Comparator.nullsFirst(Comparator.comparing(MyObject::getTotalEnergy)));