【JAVA学习】EffectiveJava的学习笔记--第二章 Object类的通用方法

Object类就是为了扩展用,其中非final方法(equals hashCode,toString,Clone,finalize)耳熟能详的方法在改写的时候必须要有很多通用约定,否则其他依赖于这些约定的类就无法与这些类正常工作。


1.equals方法

 1.1什么时候该改写equals方法?

       当一个类有自己的逻辑相等(而非对象身份)的概念,超类也没有改写equals以实现自己需要的行为时候,一般都是都是比较两个指向值对象的引用的时候,希望知道逻辑是否相等,而非指向同一个对象。

1.2 实现改写的通用约定

  •    自反性:   x.equals(x) 为true
  •    对称性:   x.equals(y) 为true,则   y.equals(x)也为true

下面这段代码coment部分的如果存在的话就违反了对称性,即equals方法中CaseInsensitiveString认识String类因此进行了判断,而反过来String类却人不认识CaseInsensitiveString类。这种不自觉的错误经常不经意就发生。

public class CaseInsensitiveString {
	
	private String s;
	public CaseInsensitiveString(String s) {
		if(s == null) {
			throw new NullPointerException();			
		}
		this.s = s;
	}
	
	public boolean equals(Object o) {
		if(o instanceof CaseInsensitiveString) {
			return  s.equalsIgnoreCase(((CaseInsensitiveString)o).s);
		}
		// 这段代码违反了对称性
//		if(o instanceof  String) {
//			return s.equalsIgnoreCase((String)o);
//		}
		return false;
	}
	
	public static void main(String[] args) {
		String s = "abc";
		CaseInsensitiveString cis = new CaseInsensitiveString("Abc");
		CaseInsensitiveString cis2 = new CaseInsensitiveString("ABC");
		System.out.println("cis.equals(s)= " +cis.equals(s));
		System.out.println("s.equals(cis) = " + s.equals(cis));
		System.out.println("cis2.equals(cis) = " + cis2.equals(cis));
	}
	
}

comment之前的结果是:

cis.equals(s)= true
s.equals(cis) = false
cis2.equals(cis) = true

comment段后的结果是:

cis.equals(s)= false
s.equals(cis) = false
cis2.equals(cis) = true

  •    传递性:    x.equals(y) 为true    y.equals(z)为true, 则  x.equals(z)也为true

         这个问题经常发生在继承扩展一个值的类的时候,如果继承扩展的类没有override equals则,调用equals的时候就会出现以下问题。

import java.awt.Color;
import java.awt.Point;

public class ColorPoint  extends Point{
	private static final long serialVersionUID = 1L;
	private Color  color;
	public ColorPoint(int x, int y, Color color) {
		super(x,y);
		this.color = color;
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Point p = new Point(1,2);
		ColorPoint cp1 = new ColorPoint(1, 2, Color.red);
		ColorPoint cp2 = new ColorPoint(1, 2, Color.black);		
		System.out.println("p.equals(cp1) =" + p.equals(cp1));
		System.out.println("p.equals(cp2) =" + p.equals(cp2));
		System.out.println("cp1.equals(cp2) =" + cp1.equals(cp2));
	}

}

得到的结果是

p.equals(cp1) =true
p.equals(cp2) =true
cp1.equals(cp2) =true 

但实际上cp1 和cp2是两个颜色不同的点。override equals方法后,虽然可以保证证对称性,但没有保证传递性,即p == p1  p==p2  得到的结果是p1 != p2.

	@Override
	public boolean equals(Object obj) {
		// TODO Auto-generated method stub
		if(!(obj instanceof Point)) {
			return false;
		}
		
		// if obj is normal point  do a color-blind comparasion
		if(!(obj instanceof ColorPoint)) {
			return this.equals(obj);
		}
		
		ColorPoint cp = (ColorPoint)obj;
		return super.equals(obj) && (cp.color == this.color);
	}
得到的结果是:

p.equals(cp1) =true
p.equals(cp2) =true
cp1.equals(cp2) =false

为了解决这个传递性的问题,一般要扩展可实例化的类的同时,既要增加新的特征,又要保留equals的约定,没有办法做到。

另外一种解决方案是 采用【复合】的方式。

即ColorPoint不是继承与Point,而是该类包含了一个Point的成员变量。

  •   非空性:   非空引用值 x  x.equals(null) 则返回false,非空不用多说了,就是上面override里面的加上null 的判断。
  • 对于类中每个关键域,检查实参中与当前对象中对应的域是否匹配。 

1.3检查equals方法的一些小结:

  •           如果该类的类型是接口,则通过接口访问实参中的关键域
  •           如果该类的类型是类,则访问实参中的关键域。
  •           对于不是float 也不是double则可以直接用==比较,对于对象引用域则可以用递归的equals方法,
  •           对于float则使用Float.floatToIntBits转换成int类型的值,再==比较
  •           对于double则使用Double.doubleToLongBits转换成long型的值,再==比较
  •           对于数组域,则以应用到元素上,有些对象引用域包含null是合法的,所以为了避免null异常,可以通过判断该field是不是null然后再进行常规的equals比较。

 1.4 修改equals的时候一定要修改hashcode方法,否则违反了相等的对象必须有相等的散列码。

     这个下一篇再记述。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值