在哈希表和可比表中使用的Java空对象
我有一个表示在哈希表和可比表中使用的Java空对象,java,equals,hashcode,equality,comparable,Java,Equals,Hashcode,Equality,Comparable,我有一个表示未知值的对象,或“Null对象” 与SQL中一样,此对象不应等于任何对象,包括另一个未知,因此(未知==未知)->false 但是,该对象在哈希表中使用,并且类型为Comparable,因此我创建了一个类,如下所示: public class Tag implements Comparable { final static UNKNOWN = new Tag("UNKNOWN"); String id; public Tag(String id) {
未知值的对象,或“Null对象”
与SQL中一样,此对象不应等于任何对象,包括另一个未知
,因此(未知==未知)->false
但是,该对象在哈希表中使用,并且类型为Comparable,因此我创建了一个类,如下所示:
public class Tag implements Comparable {
final static UNKNOWN = new Tag("UNKNOWN");
String id;
public Tag(String id) {
this.id = id;
}
public int hashCode(){
return id.hashCode();
}
public String toString(){
return id;
}
public boolean equals(Object o){
if (this == UNKNOWN || o == UNKNOWN || o == null || !(o instanceof Tag))
return false;
return this.id.equals(((Tag)o).id);
}
public int compareTo(Tag o){
if (this == UNKNOWN)
return -1;
if (o == UNKNOWN || o == null)
return 1;
return this.id.compareTo(o.id);
}
}
但是现在compareTo()
似乎“不一致”
有没有更好的方法来实现compareTo()
?的文档中提到了这种情况:
强烈建议,但并非严格要求:
(x.compareTo(y)==0) == (x.equals(y))
一般来说,任何实现Compariable
接口并违反此条件的类都应该清楚地指出这一事实。建议使用的语言是“注意:此类具有与equals不一致的自然顺序。”
因此,如果您希望对象具有可比性,但仍然不允许两个未知对象通过equals
方法相等,则必须使您的compareTo
与equals不一致
适当的实施办法是:
public int compareTo(Tag t) {
return this.id.compareTo(t.id);
}
否则,您可以明确指出,未知
值尤其是不可比较的值
:
public static boolean isUnknown(Tag t) {
return t == UNKNOWN || (t != null && "UNKNOWN".equals(t.id));
}
public int compareTo(Tag t) {
if (isUnknown(this) || isUnknown(t)) {
throw new IllegalStateException("UNKNOWN is not Comparable");
}
return this.id.compareTo(t.id);
}
答案很简单:你不应该 你在这里有矛盾的要求。您的标记对象有一个隐式的顺序(这是Compariable表示的),或者您可以有这样的“特殊”值,这些值不等于任何东西,甚至不等于它们本身 正如另一个极好的答案和评论所指出的:是的,你可以以某种方式到达那里;例如,通过简单地同时允许a.compare(b)<0和b.compare(a)<0;或者抛出一个异常 但是我会非常小心。您正在违反一份已确立的合同。事实上,一些javadoc说:“打破合同是可以的”并不是关键——打破合同意味着所有从事这个项目的人都必须理解这个细节 从这里开始:如果a或b未知,您可以继续并在
compareTo()中抛出异常;通过这样做,您至少可以清楚地知道,例如,不应该尝试对列表进行排序()。但是,嘿,等等,你怎么会发现你的名单上有未知的东西呢?因为,你知道,UNKNOWN.equals(UNKNOWN)返回false;而contains()
使用的是equals
本质上:虽然技术上可行,但无论你走到哪里,这种方法都会导致故障。意思:SQL支持这个概念并不意味着您应该在java代码中强制执行类似的内容。如前所述:这种想法非常“不合标准”;而且很容易让任何人看到它都感到惊讶。又名“意外行为”又名bug。几秒钟的批判性思考:
Java中已经有一个null
,出于某种原因,您不能将其用作键
如果您尝试使用的密钥不等于其他任何密钥,包括
本身,您可以从不检索与该键关联的值
您的compareTo()
方法现在不一致,这是正确的。它违反了此方法的几个要求。compareTo()
方法必须提供域中值的总顺序。特别是,如评论中所述,a.compareTo(b)<0
必须暗示b.compareTo(a)>0
。此外,a.compareTo(a)==0
对于每个值都必须为真
如果您的compareTo()
方法不能满足这些要求,那么API的各个部分都将崩溃。例如,如果对包含未知值的列表进行排序,则可能会出现可怕的“比较方法违反其一般约定!”异常
这与SQL中的null
值不相等的要求有什么关系
对于SQL,答案是它在某种程度上扭曲了自己的规则。在你引用的维基百科文章中,有一个章节涵盖了类似的行为。虽然null
值不被视为彼此相等,但它们也被视为彼此“不不同”,这允许groupby
将它们分组在一起。(我在这里检测到一些规范性的黄鼠狼措辞。)对于排序,SQL要求ORDER BY
子句首先有额外的null
或null LAST
,以便使用null进行排序
那么Java如何处理具有相似属性的IEEE 754NaN
?应用于NaN
的任何比较运算符的结果均为false。特别是,NaN==NaN
为false。这似乎不可能对浮点值进行排序,也不可能将它们用作映射中的键。事实证明,Java有自己的一组特殊情况。如果您查看和的规范,它们有专门的案例,正好涵盖这些情况。具体来说,
Double.NaN == Double.NaN // false
Double.valueOf(Double.NaN).equals(Double.NaN) // true!
另外,指定了Double.compareTo()
,以便它认为NaN
等于它本身(它与equals一致),并且NaN
被认为比其他所有Double
值都大,包括正数
还有一种实用方法,它使用相同的语义比较两个原语double
值
这些特殊情况使Java排序、映射等能够很好地处理Double
值,即使这违反了IEEE 754。(但请注意,基本double
值确实符合IEEE 754。)
这应该如何应用于您的标记
类及其未知
值?我认为这里不需要遵循SQL的null
规则。如果在中使用标记
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
return obj instanceof Tag && id.equals(((Tag)obj).id);
}
public int compareTo(Tag o) {
if (this.equals(o)) {
return 0;
}
if (this.equals(UNKNOWN)) {
return -1;
}
if (o.equals(UNKNOWN)) {
return 1;
}
return id.compareTo(o.id);
}