Java基础-HashSet中关于HashCode()和equals()的重写HashSet中元素的添加过程

HashSet
1.关于HashCode()和equals()的重写//原文链接:https://blog.csdn.net/javazejian/article/details/51348320
2.HashSet中元素的添加过程//来源尚硅谷教学

1.关于HashCode()和equals()的重写

equals()的所属以及内部原理(即Object中equals方法的实现原理)

equals方法是类Object中的一个基本方法,用于检测一个对象是否和另外一个对象相等。然而在Object类中这个方法是判断两个对象是否具有相同的引用,如果有他们就一定相等。

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

所有的对象都有内存地址和状态同时“==”比较两个对象的的内存地址,所以说 Object 的 equals() 方法是比较两个对象的内存地址是否相等,即若 object1.equals(object2) 为 true,则表示 equals1 和 equals2 实际上是引用同一个对象。

equals()与‘==’的区别

或许这是我们面试时更容易碰到的问题”equals方法与‘’运算符有什么区别?“,并且常常我们都会胸有成竹地回答:“equals比较的是对象的内容,而‘’比较的是对象的地址。”。但是从前面我们可以知道equals方法在Object中的实现也是间接使用了‘==’运算符进行比较的,所以从严格意义上来说,我们前面的回答并不完全正确。我们先来看一段代码并运行再来讨论这个问题。

package com.zejian.test;
public class Car {
	private int batch;
	public Car(int batch) {
		this.batch = batch;
	}
	public static void main(String[] args) {
		Car c1 = new Car(1);
		Car c2 = new Car(1);
		System.out.println(c1.equals(c2));
		System.out.println(c1 == c2);
	}
}

运行结果:false false
分析:对于等等 运算符比较两个Car对象,返回了false,这点我们很容易明白,毕竟它们比较的是内存地址,而c1与c2是两个不同的对象,所以c1与c2的内存地址自然也不一样。现在的问题是,我们希望生产的两辆的批次(batch)相同的情况下就认为这两辆车相等,但是运行的结果是尽管c1与c2的批次相同,但equals的结果却反回了false.
当然对于equals返回了false,我们也是心知肚明的,因为equal来自Object超类,访问修饰符为public,而我们并没有重写equal方法,故调用的必然是Object超类的原始方equals方法,根据前面分析我们也知道该原始equal方法内部实现使用的是 "=="运算符,所以返回了false。因此为了达到我们的期望值,我们必须重写Car的equal方法,让其比较的是对象的批次(即对象的内容),而不是比较内存地址,于是修改如下:

@Override
	public boolean equals(Object obj) {
		if (obj instanceof Car) {
			Car c = (Car) obj;
			return batch == c.batch;
		}
		return false;
	}

使用instanceof来判断引用obj所指向的对象的类型,如果obj是Car类对象,就可以将其强制转为Car对象,然后比较两辆Car的批次,相等返回true,否则返回false。当然如果obj不是 Car对象,自然也得返回false。我们再次运行:
true false
总结:默认情况下也就是从超类Object继承而来的equals方法与‘==’是完全等价的,比较的都是对象的内存地址,但我们可以重写equals方法,使其按照我们的需求的方式进行比较,如String类重写了equals方法,使其比较的是字符的序列,而不再是内存地址。
这也就是为什么hashset添加元素的时候需要重写.equals()方法。

equals()的重写规则(五条)

为什么重写equals()的同时还得重写hashCode()

在java中,我们可以使用hashCode()来获取对象的哈希码,其值就是对象的存储地址,这个方法在Object类中声明,因此所有的子类都含有该方法。那我们先来认识一下hashCode()这个方法吧。hashCode的意思就是散列码,也就是哈希码,是由对象导出的一个整型值,散列码是没有规律的,如果x与y是两个不同的对象,那么x.hashCode()与y.hashCode()基本是不会相同的,下面通过String类的hashCode()计算一组散列码

package com.zejian.test;
public class HashCodeTest {
	public static void main(String[] args) {
		int hash=0;
		String s="ok";
		StringBuilder sb =new StringBuilder(s);
		
		System.out.println(s.hashCode()+"  "+sb.hashCode());
		
		String t = new String("ok");
		StringBuilder tb =new StringBuilder(s);
		System.out.println(t.hashCode()+"  "+tb.hashCode());
	}
	
}

运行结果:
3548 1829164700
3548 2018699554
在Java API文档中关于hashCode方法有以下几点规定(原文来自java深入解析一书):

1.在java应用程序执行期间,如果在equals方法比较中所用的信息没有被修改,那么在同一个对象上多次调用hashCode方法时必须一致地返回相同的整数。如果多次执行同一个应用时,不要求该整数必须相同。

2.如果两个对象通过调用equals方法是相等的,那么这两个对象调用hashCode方法必须返回相同的整数

3.如果两个对象通过调用equals方法是不相等的,不要求这两个对象调用hashCode方法必须返回不同的整数。但是程序员应该意识到对不同的对象产生不同的hash值可以提供哈希表的性能。
通过前面的分析,我们知道在Object类中,hashCode方法是通过Object对象的地址计算出来的,因为Object对象只与自身相等,所以同一个对象的地址总是相等的,计算取得的哈希码也必然相等,对于不同的对象,由于地址不同,所获取的哈希码自然也不会相等。因此到这里我们就明白了,如果一个类重写了equals方法,但没有重写hashCode方法,将会直接违法了第2条规定,这样的话,如果我们通过映射表(Map接口)操作相关对象时,就无法达到我们预期想要的效果。如果大家不相信, 可以看看下面的例子:

package com.zejian.test;
import java.util.HashMap;
import java.util.Map;
public class MapTest {
	public static void main(String[] args) {
		Map<String,Value> map1 = new HashMap<String,Value>();
		String s1 = new String("key");
		String s2 = new String("key");	
		Value value = new Value(2);
		map1.put(s1, value);
		System.out.println("s1.equals(s2):"+s1.equals(s2));
		System.out.println("map1.get(s1):"+map1.get(s1));
		System.out.println("map1.get(s2):"+map1.get(s2));
		
		
		Map<Key,Value> map2 = new HashMap<Key,Value>();
		Key k1 = new Key("A");
		Key k2 = new Key("A");
		map2.put(k1, value);
		System.out.println("k1.equals(k2):"+s1.equals(s2));
		System.out.println("map2.get(k1):"+map2.get(k1));
		System.out.println("map2.get(k2):"+map2.get(k2));
	}
	
	/**
	 * 键
	 * @author zejian
	 *
	 */
	static class Key{
		private String k;
		public Key(String key){
			this.k=key;
		}
		
		@Override
		public boolean equals(Object obj) {
			if(obj instanceof Key){
				Key key=(Key)obj;
				return k.equals(key.k);
			}
			return false;
		}
	}
	
	/**
	 * 值
	 * @author zejian
	 *
	 */
	static class Value{
		private int v;
		
		public Value(int v){
			this.v=v;
		}
		
		@Override
		public String toString() {
			return "类Value的值-->"+v;
		}
	}
}
s1.equals(s2):true
map1.get(s1):类Value的值-->2
map1.get(s2):类Value的值-->2
k1.equals(k2):true
map2.get(k1):类Value的值-->2
map2.get(k2):null

对于s1和s2的结果,我们并不惊讶,因为相同的内容的s1和s2获取相同内的value这个很正常,因为String类重写了equals方法和hashCode方法,使其比较的是内容和获取的是内容的哈希码。但是对于k1和k2的结果就不太尽人意了,k1获取到的值是2, k2获取到的是null,这是为什么呢?想必大家已经发现了,Key只重写了equals方法并没有重写hashCode方法,这样的话,equals比较的确实是内容,而hashCode方法呢?没重写,那就肯定调用超类Object的hashCode方法,这样返回的不就是地址了吗?k1与k2属于两个不同的对象,返回的地址肯定不一样,所以现在我们知道调用map2.get(k2)为什么返回null了吧?那么该如何修改呢?很简单,我们要做也重写一下hashCode方法即可(如果参与equals方法比较的成员变量是引用类型的,则可以递归调用hashCode方法来实现):

@Override
public int hashCode() {
     return k.hashCode();
}

2.关于HashSet的添加过程

HashSet是set接口的典型实现,按照hash算法来存储集合中的元素,因此具有很好的存储,查找,删除的性能。

HashSet的性能以及特点

特点:HashSet不是线程安全的,集合元素可以是null。其底层还是数组,初始容量是16,如果使用率超过0.75,就会扩大容量为原来的2倍。无序性以及不可重复性
判断两个元素相等的标准:两个对象通过hashcode()方法比较相等,并且两个对象的equals()方法返回值也相等。(需要重写equals()方法以及hashcode方法)

HashSet元素的添加过程

我们向HashSet中添加元素a的过程:
首先调用元素a所在类的hashcode方法,计算元素的hash值——通过hash值计算元素a的存放位置,判断数组此位置上是否已经有其他元素——如果此位置上没有其他元素则元素a添加成功,如果此位置上有其他元素(或者以链表形式存在多个元素),则比较a和此位置元素的hash值如果hash值不同则元素添加成功,如果hash值相同则需要调用元素a的equals方法,如果返回ture则添加失败,如果返回false 则添加成功。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值