集合框架——HashSet
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,证明按照指定的方式实现了元素的存储。