前段时间一直在工作中使用Java,由于有一些C++功底,于是简单看了一下Java相关的语法便开始编写代码,结果在创建一个自定义类,并将自定义类放入ArrayList中,之后查找ArrayList是否有此元素的时候,发现怎么也查询不到对应的元素。在网上搜了一下资料,发现原因是没有重写对象的equals()方法,导致无法查找到对应的对象。之后由查了与之联系的相关资料,便有了以下的总结。

这篇总结的形式是提出个问题,然后给出问题的答案。这是目前学习知识的一种尝试,可以让学习更有目的。

Q1.什么时候应当重写对象的equals方法?

答:一般在我们需要进行值比较的时候,是需要重写对象的equals方法的。而例外情况在《effective java》的第7条“在改写equals的时候请遵守通用约定”中清楚描述了。

我们知道,在Java中,每个对象都继承于Object.如果不重写,则默认的equals代码如下所示:

public boolean euqals(Object obj){
return this == obj;
}

由上面的代码可以看出,equal默认是使用“”来判断两个对象是否相等。两个对象使用“”比较的是对象的地址,只有两个引用指向的对象相同的时候,“==”才返回true。所以,在开头的例子中,就需要重写equals方法,让两个对象有equals的时候。

Q2.如何重写equals?

答:首先,当改写equals方法时,需要保证满足它的通用约定。这些约定如下所示:

  • 自反性,对于任意的引用值x,x.equals(x)一定为true。
  • 对称性,对于任意的引用值x和y,当且仅当y.equals(x)时,x.equals(y)也一定返回true.
  • 传递性,对于任意的引用值x,y,z。如果x.equals(y)返回true,y.euqals(z)返回true,则x.equals(z)也返回true。
  • 一致性,对于任意的引用值x和y,如果用于equals比较的对象信息没有修改,那么,多次调用x.equals(y)要么一致返回true,要么一致返回false.
  • 非空性,所有的对象都必须不等于null。

其实我觉的一个简单的方法是参照String的equals方法即可,官方出版,满足各种要求。其代码如下所示

public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = count;
if (n == anotherString.count) {
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n– != 0) {
if (v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}

函数的解释如下所示:

  1. 使用==检查“实参是否是指向对象的一个引用”。
  2. 使用instanceof检查实参是否和本对象同类,如果不同类,就不相等。
  3. 将实参转换为正确的类型。
  4. 根据类的定义,检查实现此对象值相等的各个条件。

更详细的信息,还是请看《effective java》的第7条“在改写equals的时候请遵守通用约定”。

Q3.修改equals时需要注意什么?

答:大致需要注意以下几点:

若修改equals方法,也请修改hashCode方法

首先这个是语言的一个约定,这么做的一个原因是当此对象作为哈希容器的元素时,需要依赖hashCode,对象默认的hashCode是返回一个此对象特有的hashCode,不同的对象的hashCode返回值是不一样的,而哈希容器处理元素时,是按照对象的哈希值将对象分配到不同的桶中,若我们不重写对象的hashCode,那么值相等的对象产生的哈希值也会不同,这样当在哈希容器中查找时,会找不到对应的元素。

更详细的信息请看《effective Java》的第8条“改写equals时总是要改写hashCode”。

重写时保证函数声明的正确

请注意equals的声明是

public boolean equals(Object obj)

参数类型是Object,如果参数类型是此对象类型的话,如下:

class Point{
final int x;
final int y;
public void Point(int x, int y)
this.x = x;
this.y = y;
}
public boolean euqals(Point obj){
return (this.x == obj.x && this.y == obj.y);
}
}

下面代码执行是按照我们的预期执行的。

Point a(1, 2);
Poinr b(1, 2);
System.out.println(a.equals(b));// 输出true

但是如果将类A放入容器中,则会出问题

import java.util.HashSet;

HashSet<Point> coll = new HashSet<Point>();
coll.add(a);
System.out.println(coll.contains(b));// 输出false

这是由于HashSet中的contains方法中调用的是equals(Object obj),而Point中的equals(Object obj)仍是Object的equals,这个方法在前面已经说过了,比较的是对象的地址,所以在coll中调用contains(b)时,当然得不到true。

当有继承关系时注意equals的正确

当一个类重写equals方法后,另一个类继承此类,此时,可能会违反前面说到的对称性,代码如下所示:

public class ColoredPoint extends Point {
private final Color color;
public ColoredPoint(int x, int y, Color color) {
super(x, y);
this.color = color;
} @Override
public boolean equals(Object other) {
boolean result = false;
if (other instanceof ColoredPoint) {
ColoredPoint that = (ColoredPoint) other;
result = (this.color.equals(that.color) && super.equals(that));
}
return result;
}
}

当我们作比较时

Point p = new Point(1, 2);
ColoredPoint cp = new ColoredPoint(1, 2, Color.RED);
System.out.println(p.equals(cp)); //输出ture
System.out.println(cp.equals(p)); //输出false

原因是当调用Point.equals的时候,只比较了Point的x和y坐标,同时ColoredPoint也是Point类型,所以上面第三行代码相等,而调用ColoredPoint的时候,Point不是ColoredPoint类型,这样就导致第四行代码输出false。

若我们忽略Color的信息来比较呢,例如将ColoredPoint的equals方法改为:

@overwrite
public boolean equals(Object obj){
if((obj instanceof Point)){
return false;
} if(!(obj instanceof ColoredPoint)){
return obj.equals(this);
} return super.equals(obj) && ((ColoredPoint)obj).color == color;
}

这样就保证了对称性,但是却违反了传递性,即下面的情况:

ColoredPoint cp1 = new ColoredPoint(1, 2, Color.RED);
Point p = new Point(1, 2);
ColoredPoint cp2 = new ColoredPoint(1, 2, Color.BLUE);
System.out.println(cp1.equals(p)); //true
System.out.println(p.equals(cp2)); //true
System.out.println(cp1.equals(cp2)); //false

面对这种情况,大致有两种解决方案,一种酷壳的文章--如何在Java中避免equals方法的隐藏陷阱的最后一条,断绝了Point和ColoredPoint相等的可能,这是一种处理方法,认为Point和ColoredPoint是不同的。另一种方法是effective Java上提出的,使用聚合而不是继承,将Point作为ColoredPoint的一个成员变量。目前我倾向于这种方法,因为聚合比继承更灵活,耦合更低。这种方法的代码如下所示:

class ColoredPoint{
private final Point point;
private final Color color; public Point asPoint(){
return point;
} public boolean equals(Object obj){
boolean ret = false;
if(obj instanceof ColoredPoint){
ColoredPoint that = (ColoredPoint)obj;
ret = that.point.equals(point) && color.equals(that.color);
}
return ret;
}
}

当ColoredPoint需要比较坐标时,可以调用asPoint方法来转化为坐标进行比较。其他情况比较坐标和颜色,这样就可以解决上面关于对称性和传递性的问题了。

以上就是全文的内容,由于水平有限,文章中难免会有错误,希望大家指正。谢谢

[1/30]

参考资料:

  1. effective Java
  2. 如何在Java中避免equals方法的隐藏陷阱
  3. Java中equals()与hashCode()方法详解

Java里的equals总结的更多相关文章

  1. java里面的equals和hashcode的总结

    问题1: java比较两个对象,除了equals,为什么还要重写hashcode方法? 基本类型比较,用==就可以了. 对象比较,equals比较是对象的内存地址,hashcode比较的也是对象的内存 ...

  2. Java里 equals 和 == 以及 hashcode

    本文探讨的是老掉牙的基础问题,先建个实体类 package com.demo.tools; public class User { private String name; public User(S ...

  3. Java中的equals和hashCode方法

    本文转载自:Java中的equals和hashCode方法详解 Java中的equals方法和hashCode方法是Object中的,所以每个对象都是有这两个方法的,有时候我们需要实现特定需求,可能要 ...

  4. Java中==、equals、hashcode的区别与重写equals以及hashcode方法实例(转)

    Java中==.equals.hashcode的区别与重写equals以及hashcode方法实例  原文地址:http://www.cnblogs.com/luankun0214/p/4421770 ...

  5. java里的基本数据类型

    java里一共有八大数据类型 boolean(未定) char(2字节) byte(1字节) short(2字节) int(4字节) long(8字节) float(4字节) double(8字节), ...

  6. java String的equals,intern方法(转载)

    JAVA中的equals和==的区别 ==比较的是2个对象的地址,而equals比较的是2个对象的内容. 显然,当equals为true时,==不一定为true: 基础知识的重要性,希望引起大家的重视 ...

  7. java集合(3)- Java中的equals和hashCode方法详解

    参考:http://blog.csdn.net/jiangwei0910410003/article/details/22739953 Java中的equals方法和hashCode方法是Object ...

  8. Java中 == 和 equals()详解

    java中的数据类型分为两种: 一 .基本数据类型: byte.short.int.long.float.double.char.boolean 比较它们需要用  ==  ,比较的是它们的值是否相等 ...

  9. Java中==与equals的区别及理解

    区别: "==" 比较的是两个引用在内存中指向的是不是同一对象(即同一内存空间),也就是说在内存空间中的存储位置是否一致. 如果两个对象的引用相同时(指向同一对象时)," ...

随机推荐

  1. HTML的表格玩法

    HTML的表格玩法 HTML也是可已展示表格的,大体结构如下 <!DOCTYPE html> <html lang="en"> <head> & ...

  2. Android下如何理解onMeasure,onLayout的过程

    在Android中view如何完成绘制这个过程介绍了很多,但是很多理论化的东西,最近重新整理一下,通俗的讲解一下. View绘制过程就好比你向银行贷款, 在执行onMeasure的时候,好比银行告诉你 ...

  3. 从不同层面看cocos2d-x

    一  框架层面 二  Lua层面 三  工具层面 四  android打包 一 框架层     总体来说,cocos2dX提供的一个简便的框架,包括了渲染,动画,事件分发,网络还有UI,物理引擎等几大 ...

  4. WebMagic的设计参考了业界最优秀的爬虫Scrapy

    http://webmagic.io/docs/zh/posts/ch1-overview/thinking.html https://github.com/psvehla/liferay-sprin ...

  5. MVC三和,你能辨别它?

    上次我们聊的时间MVC,而之前我们学习过三层.那么我们不禁就要问,他们说的是一回事吗.他们有什么联系吗? 三层架构(3-tier application)通常意义上的三层架构就是将整个业务应用划分为: ...

  6. MySQL数据库的环境及简单操作

    ***********************************************声明*************************************************** ...

  7. [编辑中] 免费的Internet流量发生器 | Free Internet Traffic Generators

    流量发生器 (Traffic Generator) 是用来检测网络性能,进行网络相关研究的一个很重要的工具.大家可能用过Iperf或者IxChariot,前者是类UNIX环境下的一个免费.开源的网络性 ...

  8. DevExpress GridView.CustomSummaryCalculate 实现自定义Group Summary

    --首发于博客园, 转载请保留链接  博客原文 DevExpress Documentation官方地址:GridView.CustomSummaryCalculate Event 1. 概要 界面上 ...

  9. javaScript模块化一

    1. Module模式的基本特性 A) 模块化 可重用   B) 封装了变量和function 和全局的namespace不接触 松耦合.   C) 只暴露可用public的方法 其他私有方法全部隐藏 ...

  10. ios中模拟延时的几种方法

    - (void)delay1 {     // 延迟执行不要用sleep,坏处:卡住当前线程     [NSThread sleepForTimeInterval:3];     NSLog(@&qu ...