如何实现可比性,使其与身份平等一致

时间:2019-04-01 08:10:50

标签: java equals comparator comparable

我有一个类,必须根据对象标识(即equals())来定义(根据this == other

我想实现Comparable来对此类对象进行排序(例如通过某些getName()属性)。为了与equals()保持一致,即使两个对象具有相同的名称,compareTo()也不得返回0

是否有一种方法可以比较compareTo上的对象身份?我可以比较System.identityHashCode(o),但是在发生哈希冲突的情况下仍会返回0

7 个答案:

答案 0 :(得分:32)

我认为真正的答案是:那时不要实现Comparable。实施此接口暗示表示您的对象具有自然顺序。当您遵循该思想时,“相等”的事物应该放在同一位置。

如果有的话,应该使用自定义比较器...但这即使没有多大意义。如果不允许定义a 比较的整个方法为您的用例而损坏。

换句话说:仅仅因为您可以将代码放入“以某种方式”产生所需结果的类中,所以这样做并不是的主意。

答案 1 :(得分:6)

通过定义,通过为每个对象分配一个Universally unique identifier(UUID)(或全局唯一标识符(GUID))作为其身份属性,UUID是可比较的,并且与equals一致。 Java已经有一个UUID类,并且一旦生成,就可以使用字符串表示形式来保持持久性。专用属性还将确保身份在版本/线程/机器之间是稳定的。如果您有一种确保所有内容都获得唯一ID的方法,则也可以只使用递增ID,但是使用标准UUID实现可以保护您免受集合合并和并行系统同时生成数据的困扰。

如果对可比对象使用其他任何内容,则意味着可比对象与身份/值分开的方式是可比较的。因此,您将需要定义该对象的可比方法并进行记录。例如,按名称,DOB,身高或按优先级组合,人们是可比的;最自然地是按惯例命名(为了方便人们查找),这与两个人是同一个人是分开的。您还必须接受compareto和equals是不相交的,因为它们基于不同的事物。

答案 2 :(得分:5)

您可以添加第二个属性(例如int idlong id),该属性对于您的类的每个实例都是唯一的(您可以拥有一个static计数器变量并将其用于初始化构造函数中的id

然后,您的compareTo方法可以首先比较名称,如果名称相等,则比较id

由于每个实例具有不同的id,所以compareTo将永远不会返回0

答案 3 :(得分:1)

虽然我坚持最初的回答,即应该使用UUID属性进行稳定且一致的比较/相等设置,但我认为我会回答一个问题:“如果您真的偏执狂,您能走多远?并希望有可比的独特身份”。

简而言之,简而言之,如果您不信任UUID唯一性或身份唯一性,请使用尽可能多的UUID来证明上帝正在积极阴谋与您作对。 (请注意,虽然从技术上讲不能保证不会引发异常,但是在任何理智的宇宙中,都需要2个UUID会显得过高。)

import java.time.Instant;
import java.util.ArrayList;
import java.util.UUID;

public class Test implements Comparable<Test>{

    private final UUID antiCollisionProp = UUID.randomUUID();
    private final ArrayList<UUID> antiuniverseProp = new ArrayList<UUID>();

    private UUID getParanoiaLevelId(int i) {
        while(antiuniverseProp.size() < i) {
            antiuniverseProp.add(UUID.randomUUID());
        }

        return antiuniverseProp.get(i);
    }

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

        int temp = System.identityHashCode(this) - System.identityHashCode(o);
        if(temp != 0)
            return temp;

        //If the universe hates you
        temp = this.antiCollisionProp.compareTo(o.antiCollisionProp);
        if(temp != 0)
            return temp;

        //If the universe is activly out to get you
        temp = System.identityHashCode(this.antiCollisionProp) - System.identityHashCode(o.antiCollisionProp);;
        if(temp != 0)
            return temp;

        for(int i = 0; i < Integer.MAX_VALUE; i++) {
            UUID id1 = this.getParanoiaLevelId(i);
            UUID id2 = o.getParanoiaLevelId(i);
            temp = id1.compareTo(id2);
            if(temp != 0)
                return temp;

            temp = System.identityHashCode(id1) - System.identityHashCode(id2);;
            if(temp != 0)
                return temp;
        }

        // If you reach this point, I have no idea what you did to deserve this
        throw new IllegalStateException("RAGNAROK HAS COME! THE MIDGARD SERPENT AWAKENS!");
    }

}

答案 4 :(得分:0)

假设对于两个具有相同名称的对象,如果equals()返回false,则compareTo()不应返回0。如果您要这样做,则可以执行以下操作:

  • 覆盖hashcode(),并确保它不完全依赖name
  • 实施compareTo()如下:
public void compareTo(MyObject object) {
    this.equals(object) ? this.hashcode() - object.hashcode() : this.getName().compareTo(object.getName());
}

答案 5 :(得分:0)

您拥有唯一的对象,但是正如Eran所说,您可能需要一个额外的计数器/哈希码来处理任何冲突。

private static Set<Pair<C, C> collisions = ...;

@Override
public boolean equals(C other) {
    return this == other;
}

@Override
public int compareTo(C other) {
    ...
    if (this == other) {
        return 0
    }
    if (super.equals(other)) {
        // Some stable order would be fine:
        // return either -1 or 1
        if (collisions.contains(new Pair(other, this)) {
            return 1;
        } else if (!collisions.contains(new Pair(this, other)) {
            collisions.add(new Par(this, other));
        }
        return 1;
    }
    ...
}

因此,请回答伊兰(Eran)的问题,或者将要求原样放在问题中。

  • 一个人可能会认为不完全相同的0比较的开销可以忽略不计。
  • 如果在某个时间点不再创建实例,则可能会研究理想的哈希函数。这意味着您具有所有实例的集合。

答案 6 :(得分:0)

有时(尽管很少)需要实现基于身份的 compareTo 覆盖。就我而言,我正在实施 java.util.concurrent.Delayed

由于JDK也实现了这个类,所以我想我会分享JDK的解决方案,它使用原子递增的序列号。这是 ScheduledThreadPoolExecutor 的一个片段(为了清晰起见略有修改):


    /**
     * Sequence number to break scheduling ties, and in turn to
     * guarantee FIFO order among tied entries.
     */
    private static final AtomicLong sequencer = new AtomicLong();

    private class ScheduledFutureTask<V>
            extends FutureTask<V> implements RunnableScheduledFuture<V> {

        /** Sequence number to break ties FIFO */
        private final long sequenceNumber = sequencer.getAndIncrement();
        
    }

如果 compareTo 中使用的其他字段已用完,则使用此 sequenceNumber 值打破平局。一个 64 位整数(长)的范围足以指望这一点。