Java问答:终极父类(第一部分 下)

Equality

问:euqals()函数是用来作什么的? java

答:equals()函数能够用来检查一个对象与调用这个equals()的这个对象是否相等。 数组

问:为何不用“==”运算符来判断两个对象是否相等呢? ide

答:虽然“==”运算符能够比较两个数据是否相等,可是要来比较对象的话,恐怕达不到预期的结果。就是说,“==”经过是否引用了同一个对象来判断两个对象是否相等,这被称为“引用相等”。这个运算符不能经过比较两个对象的内容来判断它们是否是逻辑上的相等。 函数

问:使用Object类的equals()方法能够用来作什么样的对比? flex

答:Object类默认的eqauls()函数进行比较的依据是:调用它的对象和传入的对象的引用是否相等。也就是说,默认的equals()进行的是引用比较。若是两个引用是相同的,equals()函数返回true;不然,返回false. ui

问:覆盖equals()函数的时候要遵照那些规则? this

答:覆盖equals()函数的时候须要遵照的规则在Oracle官方的文档中都有申明: spa

  • 自反性:对于任意非空的引用值x,x.equals(x)返回值为真。
  • 对称性:对于任意非空的引用值x和y,x.equals(y)必须和y.equals(x)返回相同的结果。
  • 传递性:对于任意的非空引用值x,y和z,若是x.equals(y)返回真,y.equals(z)返回真,那么x.equals(z)也必须返回真。
  • 一致性:对于任意非空的引用值x和y,不管调用x.equals(y)多少次,都要返回相同的结果。在比较的过程当中,对象中的数据不能被修改。
  • 对于任意的非空引用值x,x.equals(null)必须返回假。

问:能提供一个正确覆盖equals()的示例吗? 对象

答:固然,请看代码清单8。 继承

代码清单8:对两个对象进行逻辑比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
classEmployee
{
privateString name;
privateintage;
Employee(String name,intage)
{
this.name = name;
this.age = age;
}
@Override
publicbooleanequals(Object o)
{
if(!(oinstanceofEmployee))
returnfalse;
Employee e = (Employee) o;
returne.getName().equals(name) && e.getAge() == age;
}
String getName()
{
returnname;
}
intgetAge()
{
returnage;
}
}
publicclassEqualityDemo
{
publicstaticvoidmain(String[] args)
{
Employee e1 =newEmployee("John Doe",29);
Employee e2 =newEmployee("Jane Doe",33);
Employee e3 =newEmployee("John Doe",29);
Employee e4 =newEmployee("John Doe",27+2);
// 验证自反性。
System.out.printf("Demonstrating reflexivity...%n%n");
System.out.printf("e1.equals(e1): %b%n", e1.equals(e1));
// 验证对称性。
System.out.printf("%nDemonstrating symmetry...%n%n");
System.out.printf("e1.equals(e2): %b%n", e1.equals(e2));
System.out.printf("e2.equals(e1): %b%n", e2.equals(e1));
System.out.printf("e1.equals(e3): %b%n", e1.equals(e3));
System.out.printf("e3.equals(e1): %b%n", e3.equals(e1));
System.out.printf("e2.equals(e3): %b%n", e2.equals(e3));
System.out.printf("e3.equals(e2): %b%n", e3.equals(e2));
// 验证传递性。
System.out.printf("%nDemonstrating transitivity...%n%n");
System.out.printf("e1.equals(e3): %b%n", e1.equals(e3));
System.out.printf("e3.equals(e4): %b%n", e3.equals(e4));
System.out.printf("e1.equals(e4): %b%n", e1.equals(e4));
// 验证一致性。
System.out.printf("%nDemonstrating consistency...%n%n");
for(inti =0; i <5; i++)
{
System.out.printf("e1.equals(e2): %b%n", e1.equals(e2));
System.out.printf("e1.equals(e3): %b%n", e1.equals(e3));
}
// 验证传入非空集合时,返回值为false。
System.out.printf("%nDemonstrating null check...%n%n");
System.out.printf("e1.equals(null): %b%n", e1.equals(null));
}
}

代码清单8声明了一个包含名字、年龄成员变量的Employee对象。这个对象覆盖了equals()函数来对Employee对象进行适当的对比。

ps:覆盖hashCode()函数
当覆盖equals()函数的时候,就至关于覆盖了hashCode()函数,我将在下篇文章讨论hashCode()的时候详细说明。

equals()函数首先要检查传入的确实是一个Employee对象。若是不是,返回false。这个检查是靠instanceof运算来判断的,当传入null值的时候,一样也返回false。所以,遵照了“对于任意的非空引用值x,x.equals(null)必须返回假”这个规则。

这样,咱们就保证了传入的对象是Employee类型。由于以前的instanceof判断保证了传入值必须是Employee类型的对象,因此在这里咱们就没必要担忧抛出ClassCastException异常了。接下来,equals()方法对两个对象的name和age的值进行了比较。

编译(javac EqualityDemo.java)并运行(java EqualityDemo)代码清单8,你能够看到如下输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Demonstrating reflexivity...
e1.equals(e1):true
Demonstrating symmetry...
e1.equals(e2):false
e2.equals(e1):false
e1.equals(e3):true
e3.equals(e1):true
e2.equals(e3):false
e3.equals(e2):false
Demonstrating transitivity...
e1.equals(e3):true
e3.equals(e4):true
e1.equals(e4):true
Demonstrating consistency...
e1.equals(e2):false
e1.equals(e3):true
e1.equals(e2):false
e1.equals(e3):true
e1.equals(e2):false
e1.equals(e3):true
e1.equals(e2):false
e1.equals(e3):true
e1.equals(e2):false
e1.equals(e3):true
Demonstrating null check...
e1.equals(null):false

equals()和继承

当Employee类被继承的时候,代码清单8就存在一些问题。例如,SaleRep类继承了Employee类,这个类中也有基于字符串类型的变量,equals()能够对其进行比较。假设你建立的Employee对象和SaleRep对象都有相同的“名字”和“年龄”。可是,SaleRep中仍是添加了一些内容。

假设你在Employee对象中调用equals()方法而且传入了一个SaleRep对象。因为SaleRep对象继承了Employee,也是一种Employee的对象,instanceof判断会经过,而且执行equals()方法来判断名字和年龄。由于这两个对象有彻底相同的名字和年龄,因此equals()方法返回true。若是拿SaleRep对象中Employee的部分来和Employee比较的话,返回true值是正确的,可是,若是拿整个SaleRep对象来和Employee对象比较,返回true值就不妥了。

如今假设在SaleRep对象中调用equals()方法并将Employee传入。由于Employee不是SaleRep类型的对象(不然的话,你能够访问Employee对象中不存在的Region域,这会致使虚拟机崩溃),没法经过instanceof判断,equals()方法返回false。综上,equals()在一种判断中为true却在另外一判断中为false,违背了“对称性原则”。

Joshua Bloch在《Effective Java Programming Language Guide》第七版中指出:咱们没法扩展可被实例化的类(例如Employee)并向其中增长一个域(如Region域),而同时维持equals()方法的对称性。尽管也有办法来维持对称性,但代价是破坏传递性。Bloch指出解决这个问题须要在继承上支持组合:不是让SaleRep来扩展Employee,SaleRep应该引用一个私有的Employee值。得到更多信息能够参考Bloch的书。

问:可使用equals()函数来判断两个数组是否相等吗?

答:能够调用equals()函数来比较数组的引用是否相等。可是,因为在数组对象中没法覆盖equals(),因此只能对数组的引用进行比较,由于不是很经常使用。参见代码清单9。

代码清单9:尝试经过equals()函数来比较两个数组

1
2
3
4
5
6
7
8
9
10
11
publicclassEqualityDemo
{
publicstaticvoidmain(String[] args)
{
intx[] = {1,2,3};
inty[] = {1,2,3};
System.out.printf("x.equals(x): %b%n", x.equals(x));
System.out.printf("x.equals(y): %b%n", x.equals(y));
}
}

代码清单9的main()函数中声明了一对类型与内容彻底相等的数组。而后尝试对第一个数组和它本身、第一个数组和第二个数组分别进行比较。因为equals()对数组来讲比较的仅仅是引用,而不比较内容,因此x.equals(x)返回true(由于自反性——一个对象与它本身相等),可是x.equals(y)返回false。

编译(javac EqualityDemo.java) 并运行(java EqualityDemo)代码清单9,你将会看到如下输出结果:

1
2
x.equals(x):true
x.equals(y):false

若是你想要比较的是两个数组的内容,也不要绝望。 可使用java.util.Arrays 类中声明的 static boolean deepEquals(Object[] a1, Object[] a2) 方法来实现。代码清单10演示了这个方法。

代码清单10:经过deepEquals()函数来比较两个数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
importjava.util.Arrays;
publicclassEqualityDemo
{
publicstaticvoidmain(String[] args)
{
Integer x[] = {1,2,3};
Integer y[] = {1,2,3};
Integer z[] = {3,2,1};
System.out.printf("x.equals(x): %b%n", Arrays.deepEquals(x, x));
System.out.printf("x.equals(y): %b%n", Arrays.deepEquals(x, y));
System.out.printf("x.equals(z): %b%n", Arrays.deepEquals(x, z));
}
}

因为deepEquals()方法要求传入的数组元素必须是对象,因此以前在代码清单9中的元素类型要从int[]改成Integer[]。Java语言的自动封装特性会把integer常量转换成Integer对象存放在数组中。接下来要将数组传入到deepEquals()就是小事一桩了。

编译(javac EqualityDemo.java)并运行(java EqualityDemo)代码清单10,你将看到如下输出结果。

1
2
3
x.equals(x):true
x.equals(y):true
x.equals(z):false

用deepEquals()方法比较的相等是“深度”的相等:这要求每一个元素对象所包含的的成员、对象相等。成员对象若是还包含了对象,也要相等,以此类推,才算是“相等”(另外,两个空的数组引用也是“深度”的相等,所以Arrays.deepEquals(null, null)返回true)。

相关文章
相关标签/搜索