如何实现Comparable,以便与identity-equality保持一致

问题描述 投票:18回答:6

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

我想实现Comparable来订购这样的对象(比如一些getName()属性)。为了与equals()保持一致,compareTo()不得返回0,即使两个对象具有相同的名称。

有没有办法比较compareTo意义上的对象身份?我可以比较System.identityHashCode(o),但在哈希碰撞的情况下仍会返回0

java equals comparator comparable
6个回答
32
投票

我认为这里真正的答案是:不要实现Comparable。实现此接口意味着您的对象具有自然顺序。当你跟进那个想法时,那些“平等”的东西应该在同一个地方。

如果有的话,你应该使用自定义比较器...但即使这样也没有多大意义。如果定义<b ...的东西不允许给你一个== b(当a和b根据你的<关系“相等”时),那么整个比较方法就会因你的用例而被破坏。

换句话说:仅仅因为你可以将代码放入一个“以某种方式”产生你想要的东西的类......这样做并不是一个好主意。


6
投票

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

如果您使用其他任何可比较的东西,则意味着它在与其身份/值分开的方式上具有可比性。因此,您需要为此对象定义可比较的方法,并记录该对象。例如,人们可以按名称,DOB,身高或优先顺序组合进行比较;最自然地通过名称作为约定(为了更容易被人类查找),如果两个人是同一个人则是分开的。你还必须接受compareto和equals是不相交的,因为它们基于不同的东西。


5
投票

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

然后你的compareTo方法可以首先比较名称,如果名称相等,比较ids。

由于每个实例都有不同的idcompareTo永远不会返回0


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

}

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

0
投票

你有独特的对象,但正如Eran所说,你可能需要一个额外的计数器/ rehash代码来处理任何碰撞。

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

因此,请选择伊兰的答案,或将要求作为问题。

  • 有人可能认为不相同的0比较的开销可以忽略不计。
  • 如果在某个时间点不再创建实例,则可以研究理想的散列函数。这意味着您拥有所有实例的集合。
© www.soinside.com 2019 - 2024. All rights reserved.