关于equals与hashcode的重写

1. 引言

        在学习ADT的等价性这一章节中,我们学习到了不同的等价性的判定原理,包括不可变类型的等价性,可变类型的等价性,观察等价性,行为等价性等等。我们知道,对于可变类型的ADT,我们最好保留默认的equals与hashcode函数(继承自Object类),这是因为可变的equals与hashcode会导致包含此类元素集合的RI被破坏。而对于不可变类型的ADT,我们要求必须重写equals与hashcode,这是因为"=="仅能判断其引用等价性,而无法比较基于AF的对象等价性。

2. 原理 / 分析

        那么,我们重写equals与hashcode,仅仅是为了我们编写程序的时候使用equals来判断两个对象是否“相等”吗?当然不是。在Java的集合框架(JCF)中,这两个函数被大量使用到。首先,我们可以看看JCF中的一些内容。JCF包括Collection体系集合与Map体系集合,前者又包含List,Set以及它们的具体实现,后者则是Map的一些具体实现。它们的共同点就在于“集合”这个概念,它们都包含了一些数据结构对象。那么,基本的增删改查是少不了的,而增删改查中又必然会使用到“Compare"(注意这里我说的compare并非指实现Java里的比较接口,而单指字面意义上的是否相等的比较),也就是说,这里的元素必须都是可比较的。比如下面一段代码:

package Test;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashSet;
import java.util.List;
import java.util.Scanner;
import java.util.Set;

public class Testscaaner {
	public static void main(String[] args) {
		Set<Person> aList = new HashSet<Person>();
		Person aPerson = new Person("Tom");
		Person bPerson = new Person("Tom");
		aList.add(aPerson);
		aList.add(bPerson);
		System.out.println(aPerson.equals(bPerson));
		System.out.println(aList.size());
	}
}


class Person{
	private String name;
	
	public Person(String name) {
		// TODO Auto-generated constructor stub
		this.name = name;
	}	

	@Override
	public boolean equals(Object obj) {
		// TODO Auto-generated method stub
		if(!(obj instanceof Person)) {
			return false;
		}
		if(((Person)obj).name.equals(name)) {
			return true;
		}
		return false;
	}
}

这段代码表面上没有使用equals进行比较,但实际上仍然进行了比较操作。为什么呢?我们知道HashSet类会确保集合中没有一样的元素,再添加元素的时候,就会使用equals进行比较来决定是否添加。看到这里我们可能就会觉得,既然Person类重写了equals方法,改成了如果姓名相同即对象等价,那么aPerson就与bPerson等价,那这里的aList中就只应该有一个元素吧?

然而输出却是两个:

true
2

我们再来看下面的代码:

package Test;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashSet;
import java.util.List;
import java.util.Scanner;
import java.util.Set;

public class Testscaaner {
	public static void main(String[] args) {
		Set<Person> aList = new HashSet<Person>();
		Person aPerson = new Person("Tom");
		Person bPerson = new Person("Tom");
		aList.add(aPerson);
		aList.add(bPerson);
		System.out.println(aPerson.equals(bPerson));
		System.out.println(aList.size());
	}
}


class Person{
	private String name;
	
	public Person(String name) {
		// TODO Auto-generated constructor stub
		this.name = name;
	}	

	@Override
	public boolean equals(Object obj) {
		// TODO Auto-generated method stub
		if(!(obj instanceof Person)) {
			return false;
		}
		if(((Person)obj).name.equals(name)) {
			return true;
		}
		return false;
	}

	@Override
	public int hashCode() {
		// TODO Auto-generated method stub
		return name.hashCode();
	}
	
}

现在输出确实是1了:

true
1

这就是我们要注意的点,也是为什么equals要与hashcode配套修改使用。当仅仅修改其中的一个函数时,是不会起作用的。事实上 ,由于HashSet是由哈希表实现的,我们可以理解为它会维护一个数组,记录每个储存在里面元素的hashcode,当试图向其中插入时,会先检查这个数组,如果hashcode存在,就不会插入。如果不存在,就会比较hashcode所指向的位置的元素与待插入元素,决定是否插入。在上述代码中由于hashcode不同,放置的位置不同,故不会产生比较,使得单个equals起不了作用。也就是存储过程(访问过程(包括remove)):先计算hashCode,再equals,想改变存储依据,既要改变hashCode也要重写equals(因为如果只改变equals,第一步计算hashCode就不一样,根本轮不到equals)。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值