在 Java 中使用继承平等有什么问题?
What's wrong with using Inheritance Equality in Java?
在我正在阅读的“Core Java Volume 1”一书中说,相等不应该与继承一起使用。所以,我有下面的例子,它似乎有问题:
public class Main {
public static void main(String[] args) {
C c = new C("Test", 10);
D d = new D("Test", 10);
if (c.equals(d))
System.out.println("Equal");
else
System.out.println("Unequal");
if (d.equals(c))
System.out.println("Equal");
else
System.out.println("Unequal");
}
}
class C
{
C(String cstr, int cnum) {
str = cstr;
num = cnum;
}
@Override
public boolean equals(Object otherObject) {
// A quick test to see if the objects are identical.
if (this == otherObject) {
return true;
}
// Must return false if the explicit parameter is null
if (otherObject == null)
{
return false;
}
if (!(otherObject instanceof C))
return false;
// Now we know otherObject is a non-null Employee
C other = (C) otherObject;
// Test whether the fields have identical values
return str.equals(other.str) && num == other.num;
}
private String str;
private int num;
}
class D extends C {
D(String cstr, int cnum) {
super(cstr, cnum);
}
}
它 returns "Equal" 用于两个对称比较,这大概是不应该的。它是否在派生的 class 'D' 中缺少另一个 equals
方法?如果是这样,那么如果我根本不使用另一个 equals
派生它,上面的示例代码会有什么问题?
如果您不希望实例 C
和 D
比较相等,请将 !(otherObject instanceof C)
替换为 otherObject.getClass() != this.getClass()
。这将确保只有 C
可以等于另一个 C
并且只有 D
可以等于另一个 D
.
这是否是您想要的取决于几个因素。我的一般经验法则是,"open-ended" 等式 class 可以添加 "external users"(无论对您意味着什么)都不是一个好主意。如果你想让不同类型的对象比较相等(这种情况很少见),那么应该严格控制层次结构(例如 final
classes)。
集合(和类似的定义良好的接口)是不同的:这里的接口定义了一个非常精确的契约,所有实现 classes 都需要遵循(即一个 ArrayList
可以等于一个LinkedList
如果它们都包含相同顺序的相同元素,因为 List
接口定义了两个 List
相等的含义)。
该代码有效,因为正如您所说,D
不会覆盖 equals
。我希望这本书的意思是 D
应该 覆盖 equals
(并且它们都应该覆盖 hashCode
),因为 D
实例不应该等于 C
实例,因为它们是不同的(尽管相关)类型。
请记住 the equals
contract 的关键方面之一是 对称性 。对于 c
和 d
的任何非 null
值,如果 c.equals(d)
为真,则 d.equals(c)
必须为真(反之亦然)。虽然在您当前的代码中确实如此,但可能不应该如此,因为 D
和 C
是不同的类型。如果 D
个实例确实 等同于 个 C
个实例,为什么要有 D
?
在我正在阅读的“Core Java Volume 1”一书中说,相等不应该与继承一起使用。所以,我有下面的例子,它似乎有问题:
public class Main {
public static void main(String[] args) {
C c = new C("Test", 10);
D d = new D("Test", 10);
if (c.equals(d))
System.out.println("Equal");
else
System.out.println("Unequal");
if (d.equals(c))
System.out.println("Equal");
else
System.out.println("Unequal");
}
}
class C
{
C(String cstr, int cnum) {
str = cstr;
num = cnum;
}
@Override
public boolean equals(Object otherObject) {
// A quick test to see if the objects are identical.
if (this == otherObject) {
return true;
}
// Must return false if the explicit parameter is null
if (otherObject == null)
{
return false;
}
if (!(otherObject instanceof C))
return false;
// Now we know otherObject is a non-null Employee
C other = (C) otherObject;
// Test whether the fields have identical values
return str.equals(other.str) && num == other.num;
}
private String str;
private int num;
}
class D extends C {
D(String cstr, int cnum) {
super(cstr, cnum);
}
}
它 returns "Equal" 用于两个对称比较,这大概是不应该的。它是否在派生的 class 'D' 中缺少另一个 equals
方法?如果是这样,那么如果我根本不使用另一个 equals
派生它,上面的示例代码会有什么问题?
如果您不希望实例 C
和 D
比较相等,请将 !(otherObject instanceof C)
替换为 otherObject.getClass() != this.getClass()
。这将确保只有 C
可以等于另一个 C
并且只有 D
可以等于另一个 D
.
这是否是您想要的取决于几个因素。我的一般经验法则是,"open-ended" 等式 class 可以添加 "external users"(无论对您意味着什么)都不是一个好主意。如果你想让不同类型的对象比较相等(这种情况很少见),那么应该严格控制层次结构(例如 final
classes)。
集合(和类似的定义良好的接口)是不同的:这里的接口定义了一个非常精确的契约,所有实现 classes 都需要遵循(即一个 ArrayList
可以等于一个LinkedList
如果它们都包含相同顺序的相同元素,因为 List
接口定义了两个 List
相等的含义)。
该代码有效,因为正如您所说,D
不会覆盖 equals
。我希望这本书的意思是 D
应该 覆盖 equals
(并且它们都应该覆盖 hashCode
),因为 D
实例不应该等于 C
实例,因为它们是不同的(尽管相关)类型。
请记住 the equals
contract 的关键方面之一是 对称性 。对于 c
和 d
的任何非 null
值,如果 c.equals(d)
为真,则 d.equals(c)
必须为真(反之亦然)。虽然在您当前的代码中确实如此,但可能不应该如此,因为 D
和 C
是不同的类型。如果 D
个实例确实 等同于 个 C
个实例,为什么要有 D
?