一、概述
在本教程中,我们将描述Java 中的两个基本相等检查——引用相等和值相等。我们将比较它们,展示示例,并突出它们之间的主要区别。
此外,我们将专注于null
检查,并理解为什么在处理对象时应该使用引用相等而不是值相等。
2. 引用相等
我们将从理解引用比较开始,它由相等运算符( ==
) 表示。当两个引用指向内存中的同一个对象时,就会发生引用相等。
2.1 基本类型的比较
我们知道Java 中的原始类型是简单的、非类的原始值。当我们对原始类型使用相等运算符时,我们只是比较它们的值:
int a = 10; int b = 15; assertFalse(a == b); int c = 10; assertTrue(a == c); int d = a; assertTrue(a == d);
如上所示,对于原语,相等性和引用检查的工作方式相同。当我们用相同的值初始化一个新的原语时,检查返回true.
此外,如果我们将原始值重新分配给新变量并进行比较,则运算符返回相同的结果。
现在让我们执行null
检查:
int e = null; // compilation error assertFalse(10 == null); // compilation error assertFalse(a == null); // compilation error
Java 禁止将null
分配给原语。通常,我们不能使用相等运算符对原始变量或值执行任何null
检查。
2.2.对像类型的相等比较
对于Java 中的对像类型,相等运算符只执行引用相等比较,而忽略对象值。在我们实现测试之前,让我们创建一个简单的自定义类:
public class Person { private String name; private int age; // constructor, getters, setters... }
现在,让我们初始化一些类对象并检查相等运算符的结果:
Person a = new Person("Bob", 20); Person b = new Person("Mike", 40); assertFalse(a == b); Person c = new Person("Bob", 20); assertFalse(a == c); Person d = a; assertTrue(a == d);
结果与以前大不相同。第二次检查返回false
,而我们为原语得到了true
。正如我们前面提到的,相等运算符在比较时忽略了对象的内部值。它只检查两个变量是否引用了相同的内存地址。
与原语不同,我们可以在处理对象时使用null
:
assertFalse(a == null); Person e = null; assertTrue(e == null);
通过使用相等运算符并比较null,
我们检查分配给变量的对像是否已经初始化。
3.相同的值比较
现在让我们关注价值平等测试。当两个独立的对象碰巧具有相同的值或状态时,就会发生值相等。
这会比较值,并且与Object's equals()
方法密切相关。和以前一样,让我们将其与原语和对像类型的使用进行比较,看看关键的区别。
3.1 具有原始类型的equals()
方法
众所周知,原语是具有单个值的基本类型,不实现任何方法。因此,不可能直接使用原语调用equals()
方法:
int a = 10; assertTrue(a.equals(10)); // compilation error
然而,由于每个原语都有自己的包装类,我们可以使用boxing mechanism
将其转换为它的对象表示。然后,我们可以像使用对像类型一样轻松调用equals()
方法:
int a = 10; Integer b = a; assertTrue(b.equals(10));
3.2 具有对像类型equals()
方法
让我们回到我们的Person
类。为了让equals()
方法正常工作,我们需要通过考虑类中包含的字段来覆盖自定义类中的方法:
public class Person { // other fields and methods omitted @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return age == person.age && Objects.equals(name, person.name); } }
首先,如果给定值具有相同的引用,equals()
方法返回true
,这由引用运算符检查。如果不是,我们开始平等测试。
此外,我们测试两个值的Class
对象的相等性。如果它们不同,我们返回false
。否则,我们继续检查是否相等。最后,我们返回分别比较每个属性的组合结果。
现在,让我们修改之前的测试并检查结果:
Person a = new Person("Bob", 20); Person b = new Person("Mike", 40); assertFalse(a.equals(b)); Person c = new Person("Bob", 20); assertTrue(a.equals(c)); Person d = a; assertTrue(a.equals(d));
正如我们所见,第二个检查返回true
而不是引用相等。我们重写的equals()
方法比较对象的内部值。
如果我们不重写equals()
方法,则使用父类Object
中的方法。由于Object.equals()
方法只进行引用相等性检查,因此在比较Person
对象时,行为可能不是我们所期望的。
虽然我们没有在上面展示hashCode()
方法,但我们应该注意,每当我们覆盖equals()
方法时,覆盖它是很重要的以确保这些方法之间的一致性。
4. 处理Null值
最后,让我们检查一下equals()
方法如何处理null
值:
Person a = new Person("Bob", 20); Person e = null; assertFalse(a.equals(e)); assertThrows(NullPointerException.class, () -> e.equals(a));
当我们使用equals()
方法对另一个对象进行检查时,我们会根据这些变量的顺序得到两个不同的结果。最后一条语句引发异常,因为我们在null
引用上调用了equals()
方法。要修复最后一条语句,我们应该首先调用相等运算符检查:
assertFalse(e != null && e.equals(a));
现在,条件的左侧返回false
,使整个语句等于false
,从而防止了NullPointerException
被抛出。因此,我们必须记住首先检查我们调用equals()
方法的值不是null
,否则会导致烦人的错误。
此外,从Java 7 开始,我们可以使用null 安全的方法来执行相等检查:Objects#equals()
static
assertFalse(Objects.equals(e, a)); assertTrue(Objects.equals(null, e));
此辅助方法执行额外的检查以防止抛出NullPointerException
,当两个参数都为null
时返回true
。
5. 结论
在本文中,我们讨论了引用相等和值相等检查原始值和对象值。
为了测试引用相等性,我们使用==
运算符。该运算符对原始值和对象的工作方式略有不同。当我们将相等运算符与基元一起使用时,它会比较值。另一方面,当我们将它与对像一起使用时,它会检查内存引用。通过将其与null
值进行比较,我们只需检查对像是否已在内存中初始化。
为了在Java 中执行值相等测试,我们使用从Object
继承的equals()
方法。Primitives 是简单的非类值,所以这个方法不能在没有包装的情况下被调用。
我们还需要记住只在实例化对像上调用equals()
方法。否则会抛出异常。为了防止这种情况发生,如果我们怀疑null
值,我们应该使用==
运算符检查该值。
0 评论