天天看点

集合框架 HashSet集合集合框架——HashSet

集合框架——HashSet

在介绍HashSet集合之前我们先了解下 equals == 和 toString

1,浅谈equals与 == 的区别

Java中测试两个变量相等有两种方式 “==” 和 equals方法。

对于基本类型变量  == 比较的是字面值(必须都是数值型变量)

对于引用类型  == 比较的是 引用的值(一个地址)

对于自己创建的类,继承的是Object 类的equals方法,如果该类不覆盖

equals方法,那么调用时,它还是会去比较对象引用的值(即指向的地址)

对于jar包中的类,哪些类覆盖了该方法,哪些没有,到具体的时候可以去查看

该类的API文档,我们只需要知道equasl 与 == 的区别即可。

现在有下列需求:

定义两个学生对象 

1,包含姓名 年龄 学号

2,不指定比较方式,比较两个学生对象是否是同一对象。

代码如下:

class Student
{
	private String name;
	private int age;
	private String idStr;
	public Student (String name,int age,String idStr){
		this.name = name;
		this.age = age;
		this.idStr = idStr;
	}
	public String getName(){
		return this.name;
	}
	public int getAge(){
		return this.age;
	}
	public String getIdStr(){
		return this.idStr;
	}
}
public class MyEquals
{
	public static void main(String args[]){
		Student stu1 = new Student("李磊",24,"0912");
		Student stu2 = new Student("韩梅梅",22,"0934");
		System.out.println(stu1 == stu2);
		System.out.println(stu1.equals(stu2));
	}
}
           

2,HashSet集合

HashSet集合底层数据结构是哈希表。

HashSet是如何保证元素的唯一性了?

是同过调用存入集合中元素自身的两个方法:hashCode 和 equals 方法

在这之前,我们先明确两个问题:

2.1 hashCode值 和 对象的地址 二者的联系和区别

 对于自定义类

 当新建一个自定义类,它会继承hashCode方法。当我们调用hashCode方法时,会返回一个int数据?这个数据是怎么得来的了?它与很多内容

 相关,与这个对象的内存地址,对象上的成员变量等都有关,这些都参与了哈希值的运算。所以可以明确的是:哈希值不是对象的地址。怎

样判断引用是否相同?其实是比较引用指向的地址是否相同。

 如果简单的认为hashCode返回的值是地址值,那么覆盖此方法,使其返回值都相同,那么所有新建的对象不是都相同吗?显然不是。

2.2equals方法到底在比什么

   很多地方都提到了 equals方法与 == 的区别 , == 这个操作符是java固有的操作符,它的比较方式是固定的:基本类型数值比较,

引用类型比较。都有自己的规定和计算方法。 但是对于equals方法,它是一个方法,所有类继承自Object类。由于重载的原因,使其

成为一个很灵活的比较方法,你可以按自己的想法去指定两个对象如何相等。

这时需要关心的是,查看谁的equals方法:谁调用查看谁,看它指定的比较方式。

看下面一段代码:

public class MyHashSet
{
public static void main(String args[]){
   E element = new E();
   E element2 = new E();
   String str = new String("这不合逻辑!");
       
   sop(element.hashCode());
   sop(element2.hashCode());
   sop(str.hashCode());//String 覆盖了hashCode方法,有自己计算哈希值的方法
   sop(element == element2);
   sop(element.equals(element));
   //sop(element == str);这句在编译时就无法通过。
   sop(element.equals(str));
}
public static void sop(Object obj){
System.out.println(obj);
}
}
class E
{
public boolean equals(Object obj){
//你可以很不负责的让两个对象“相等”
return true;
}
public int hashCode(){
return 60;
} 
}
           

---------- 运行java程序 ----------

60

60

-342913705

false

true

True

从运行结果来看:== 检查的是两个对象的地址值是否相同,对于不同类型在编译时就会出错。

而指定的equals方法,理论上可以使任何对象“相等”,不过这种相等并无实际意义。

2.3 HashSet集合的存储方式

有了以上内容做铺垫,我们在来看看HashSet是如何保证元素的唯一性,就比较清楚了。

你可以把HashSet集合看成是一个装东西的容器:玻璃瓶,罐子都行。只是在装入东西时,要按这个容器的规则进行。

当我们新建一个对象,以自定义对象为例。因为有hashCode方法,这个对象总会产生一个哈希值,不管你的自定义类覆盖hashCode方法与否。

当我们向HashSet容器放元素(或者对象,其实是对象的引用)时,元素放在哪是一个问题。HashSet这个集合的特点是,每个元素都有哈希值,依据元素的哈希值,决定存放位置。

在进行比较时,先调用的是hashCode方法。

当向HashSet集合中存储元素时,新添加的元素会与已存入的元素做一一比较:

   如果比较的两个元素通过自身hashCode方法算出的哈希值相同,进一步调用    自身的equals方法,如果equals方法返回值为真,那么这两个元素被判定为    同一元素,如果equals方法返回假,两个元素判定为不同。只是在集合中存    储位置上有一个关联。

   如果比较的两个元素通过自身的hashCode方法算出的哈希值不同,那么不调    用equals方法,也就判定这两个元素不同。

所以可以进行实验:

自定义一个类,不覆盖hashCode方法和equals 方法。那么它会沿用Object类中计算哈希值的方法和equals方法。由于hashCode方法的实现很复杂(目前仍未看懂),但是其计算方式可以最大程度防止出现相同哈希值相同。

至于equals方法就比较简单,我们可以看看:

public boolean equals (Object obj){

      return this == obj;

}也就是检查其实否为同一对象。

看代码如何实现:

import java.util.HashSet;
public class MyHashSet2
{
public static void main(String args[]) throws Exception{
HashSet con = new HashSet();
con.add(new Student(21,"Niki"));
con.add(new Student(24,"Caesar"));
con.add(new Student(22,"HanMeiMei"));
con.add(new Student(24,"Caesar"));
sop(con.size());
}
public static void sop(Object obj){
System.out.println(obj.toString());
}
}
class Student
{
private int age;
private String name;
public Student(int age,String name){
super();
this.age = age;
this.name = name;
}
}
           

---------- 运行java程序 ----------

4

通过结果可以看出,四个Student对象都存入到集合中了。

现在的需求变了,当学生的姓名和年龄相同时,就判定为相同对象(肯定不能用 == ,它是在比较地址值,当新建对象时,就会为其开辟存储空间,这种物理空间肯定不相同)。如何改写hashCode方法了??原则上hashCode的计算公式中要包含作为比较标准的那些参数:此处是年龄和姓名。

看看代码是如何实现的:

import java.util.HashSet;
public class MyHashSet2
{
public static void main(String args[]) throws Exception{
HashSet con = new HashSet();
con.add(new Student(21,"Niki"));
con.add(new Student(24,"Caesar"));
con.add(new Student(22,"HanMeiMei"));
con.add(new Student(24,"Caesar"));
sop(con.size());
}
public static void sop(Object obj){
System.out.println(obj.toString());
}
}
class Student
{
private int age;
private String name;
public Student(int age,String name){
super();
this.age = age;
this.name = name;
}
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
public void setAge(int age){
this.age = age;
}
public int getAge(){
return age;
}
public boolean equals(Object obj){ //指定比较方式
if(!(obj instanceof Student))
return false;
Student st = (Student)obj;
return this.name.equals(st.name) && this.age == st.age;
}
public int hashCode(){ //用年龄和姓名做参数计算哈希值
        return (name.hashCode()+age*39);
}
}
           

---------- 运行java程序 ----------

3

输出完成 (耗时 0 秒) - 正常终止

看到输出结果是3,证明按照指定的方式实现了元素的存储。