欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Effective Java读书笔记、感悟——2.1对所有对象都通用的方法之equals 博客分类: Java_SE effectivejava对象通用方法equals 

程序员文章站 2024-02-15 08:55:46
...

 

 

一:覆盖equals时请遵守通用约定

一直觉着equals挺实用也很简单,今天发现我大错特错了,包括以前的代码存在着很大的问题,以后使用equals必须要谨慎对待,因为此处带来的问题会。

1.       关于原则:

à不保证equals传入的是与自己相同的类的对象,一定要保证两个对象的equals返回相同的值,因此我们平时编程的时候才可以从不关注equals的参数是哪个。

à要保证传递性,比如子类的equals一定保证能正确的输出与父类对象比对的结果。且equal相互传递。可以考虑的实现比如抽象父类,复合优先于继承。

àequals比较不可依赖不可靠的资源。如IP地址,Androidsdcard中的配置文件,web services上的某些信息。

à通过每次操作都使用instanceof过滤保证不会equals(null)返回true

2.       关于经验:

à使用==操作符检查“参数是否为这个对象的引用”。

à使用instanceof操作符检查“参数是否为正确类型”。

à把参数转换成正确的类型。

à对于该类中的每个“关键(significant)”域,检查参数中的域是否与该对象中对应的域相匹配。

à当你编写完一个equals方法后,应该问自己三个问题:它是否是对称的、传递的、一致的?

3.       同时还有几个告诫:

à覆盖equals方法总要覆盖hashCode;

à不要企图让equals方法过于智能;

à不要将equals声明中的Object对象替换为其他的类型(最好加上@Override来确保覆盖)。

 

因为这个部分跟以前的认识出入很大,这里举个例子来修改一下实现。

csdn上找到一个例子,这里不给出链接了,并不是为了批判。博主的访问量很多,也只是为了说明equal的用法,这可能可以更好的说明现在很多认识与我一样略有小菜成分。这里如果只是简单的理解equals会遇到很多问题,这里先看代码:

 

public class Person{
	private String name;
	private int age;
	 
	public Person(String name,int age){
		this.name=name;
		this.age=age;
	}

	public boolean equals(Object obj){
		Person per1=this;
		Person per2=(Person) obj;
		boolean flag=false;
		  
		if(per1.name.equals(per2.name)&&per1.age==per2.age){
			flag=true;
		} 
		return flag;
	 } 

	 public String toString(){
		 return "姓名:"+name+"/t年龄:"+age;
	}
}

public class TestEquals{
	 public static void main(String[] args){
		 Person p1=new Person("c++",30);
		 Person p2=new Person("Java",15);
		 System.out.println(p1);
		 System.out.println(p2);
		 String out=p1.equals("sdff") ? "p1和p2是同一个人":"p1和p2不是同一个人";
		 System.out.println(out);
	 }
}

 

 不涉及代码效率和编码习惯的问题,通过刚才提到的很容易发现其中的问题,很明显传入不同类型的obj程序就会报错java.lang.ClassCastException;传入null报错NullPointer。如果我们要添加一个子类Worker,存在一个workid,首先直观的实现方法:

 

public boolean equals(Object obj){
		boolean flag=false;
		if(obj instanceof Worker){
			if(super.equals(obj)){
				Worker worker = (Worker)obj;
				if(worker.workid==workid){
					flag = true;
				}
			} 
		}
		return flag;
}

 现在这里很好的具备传递性,但存在一个基本问题,对于一个person.equals(worker)返回true时,worker.equals(person)返回false,这里就提到了刚才说过的反身性,我们在使用equals不关注参数是哪个。这里就需要解决这个问题。

 

依然使用最直观的思考,当传入Person时我们应该仅仅比较nameage

 

public boolean equals(Object obj){
	boolean flag=false;
	if(obj instanceof Worker){
		if(super.equals(obj)){
			Worker worker = (Worker)obj;
			if(worker.workid==workid)
				flag = true;
		} 
	}else if(obj instanceof Person)
		flag = super.equals(obj);
	}
	return flag;
}

 这样又无奈的引出了传递性问题,两个worker1worker2id12nameage都与一个person一样,那么worker1.equals(person),person.equals(worker2)都是返回true,但是wroker1.equals(worker2)返回false。你会发现这是一个很两难的问题。只针对这个例子的情景而言,在实现的时候equals其实就有了一个保证,person依照nameage可以完全确定,相当于数据库中的主键,但是这里就要保证同一个person如果他是worker就必须有一样的workerid,这样就具备了传递性,就这个场景而言这是最合理的处理方法,但如果没有意识到这个问题,不管使用了上面哪段代码实际都会遇到意想不到的问题,很有可能导致系统关键部分的逻辑错误,包括GUI或内核。针对普通的情况effective java中给了一些推荐,对一般的方式都可以考虑去使用,比如书中提到的Point类,就存在这种问题。

 

这里选择了一种推荐的方式来解决当前问题,即复合优先于继承。

这一步要修改Worker类,不让Worker继承自Person,而是采取复合的方式

 

public class Worker {

	private Person person;
	private int workid;
	
	public Worker(String name, int age, int workid) {
		person = new Person(name, age);
		this.workid=workid;
	}
	
	public Person asPerson(){
		return person;
	}
	
	public boolean equals(Object obj){
		boolean flag=false;
		if(obj instanceof Worker){
			Worker worker = (Worker)obj;
			if(worker.person.equals(this.person) && worker.workid==this.workid)
				flag = true;
		}
		return flag;
	 } 
}

 这样自然就没有子类父类的问题,Worker仅仅是Worker,Person不是同一种类型,Worker可能在工厂中使用,Person可能在社会中使用。如果要作为一种Person要使用Worker.asPerson来进入社会角色,这算是一种不错的折中方法。