我有一个类,必须根据对象标识(即equals()
)来定义(根据this == other
) 。
我想实现Comparable
来对此类对象进行排序(例如通过某些getName()
属性)。为了与equals()
保持一致,即使两个对象具有相同的名称,compareTo()
也不得返回0
。
是否有一种方法可以比较compareTo
上的对象身份?我可以比较System.identityHashCode(o)
,但是在发生哈希冲突的情况下仍会返回0
。
答案 0 :(得分:32)
我认为真正的答案是:那时不要实现Comparable。实施此接口暗示表示您的对象具有自然顺序。当您遵循该思想时,“相等”的事物应该放在同一位置。
如果有的话,应该使用自定义比较器...但这即使没有多大意义。如果不允许定义a 比较的整个方法为您的用例而损坏。
换句话说:仅仅因为您可以将代码放入“以某种方式”产生所需结果的类中,所以这样做并不是好的主意。
答案 1 :(得分:6)
通过定义,通过为每个对象分配一个Universally unique identifier(UUID)(或全局唯一标识符(GUID))作为其身份属性,UUID是可比较的,并且与equals一致。 Java已经有一个UUID类,并且一旦生成,就可以使用字符串表示形式来保持持久性。专用属性还将确保身份在版本/线程/机器之间是稳定的。如果您有一种确保所有内容都获得唯一ID的方法,则也可以只使用递增ID,但是使用标准UUID实现可以保护您免受集合合并和并行系统同时生成数据的困扰。
如果对可比对象使用其他任何内容,则意味着可比对象与身份/值分开的方式是可比较的。因此,您将需要定义该对象的可比方法并进行记录。例如,按名称,DOB,身高或按优先级组合,人们是可比的;最自然地是按惯例命名(为了方便人们查找),这与两个人是同一个人是分开的。您还必须接受compareto和equals是不相交的,因为它们基于不同的事物。
答案 2 :(得分:5)
您可以添加第二个属性(例如int id
或long 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)的问题,或者将要求原样放在问题中。
答案 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 位整数(长)的范围足以指望这一点。