equals和==及hashcode!!!(重要!!!)

发布于 2021-03-08  2.16k 次阅读


==和equals的区别和作用

==:它的作用是判断两个对象的地址是否相等;即判断两个对象是不是同一个对象;基本数据类型==比较的是值;引用数据类型==比较的是内存地址;

因为java只有值传递,所以,对于==来说,不管是比较基本数据类型,还是引用数据类型的变量,本质比较的都是值,只是引用类型变量存的值是对象的地址;

equals():判断两个对象是否相等,不能用于基本数据类型的变量比较;

equals()方法存放在Object类中,而Object类是所有类的直接或间接父类,也就是说,java所有的类都有equals()方法;

默认:通过判断两个对象的地址是否相等,即判断两个对象是不是同一个对象;此时equals()方法等价于==;

覆盖(重写equals()):若两个对象的内容相等,则equals()方法返回true,否则返回false;

 

举例说明:

1.没有重写(覆盖equals()方法)
public class Demo {
    public static void main(String[] args) {
     //新建两个相同内容的Person对象:
     Person p1=new Person("梨花",15);
     Person p2=new Person("梨花",15);
         System.out.println(p1==p2);//肯定输出false;
         System.out.println(p1.equals(p2));
    }
    /**
     * Person类;
     */
    public  static class Person{
        String name;
        int age;
        public Person(String name,int age){
            this.name=name;
            this.age=age;
        }
        public String toString(){
            return name+"-"+age;
        }
    }
}

运行结果:false;false;(未重写则equals()方法和==相同);

2.重写(覆盖)equals()方法
public class Demo {
    public static void main(String[] args) {
     //新建两个相同内容的Person对象:
     Person p1=new Person("梨花",15);
     Person p2=new Person("梨花",15);
         System.out.println(p1==p2);//肯定输出false;
         System.out.println(p1.equals(p2));//此时输出true;
    }
    /**
     * Person类;
     */
    public  static class Person {
        String name;
        int age;

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

        public String toString() {
            return name + "-" + age;
        }

        @Override
        public boolean equals(Object obj) {
            if(obj==null)return false;
            //如果是同一个对象返回true;
            if(this==obj)return true;
            //判断类型是否相同;
            if(this.getClass()!=obj.getClass()){
                //类型不同;
                return false;
            }
            //到此时,两个判断的对象不是同一个对象,且类型相同,继续判断;
            Person person=(Person)obj;
            return name.equals(person.name)&&age==person.age;
        }
    }
}

运行结果:false;true;(equals()方法已重写,此时equals()用于判断两个对象的内容是否相等);

结果分析

我们在EqualsTest2.java 中重写了Person的equals()函数:当两个Person对象的 name 和 age 都相等,则返回true。
因此,运行结果返回true。

讲到这里,顺便说一下java对equals()的要求。有以下几点:

1. 对称性:如果x.equals(y)返回是"true",那么y.equals(x)也应该返回是"true"。
2. 反射性:x.equals(x)必须返回是"true"。
3. 类推性:如果x.equals(y)返回是"true",而且y.equals(z)返回是"true",那么z.equals(x)也应该返回是"true"。
4. 一致性:如果x.equals(y)返回是"true",只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是"true"。
5. 非空性,x.equals(null),永远返回是"false";x.equals(和x不同类型的对象)永远返回是"false"。

第三部分:hashCode()的作用

hashCode()的作用是获取哈希码,也称散列码;实际上是返回一个int整数;此哈希码的作用:确定该对象在哈希表中的索引位置(类似于下标); hashCode()定义在JDK的Object.ava中,意味着Java中的任何类都包含hashCode()函数; 虽然每个Java类都包含hashCode()函数,但是,仅当创建某个“类的散列表”时,该类的hashCode()才有用;作用是确定该类的每一个对象在散列表中的位置,其他情况下,例如创建类的单个对象,或者创建类的对象数组等等,类的hashCode()没有作用;

散列表:Java集合中本质是散列表<key,value>的类,如HashSet,HashMap,HashTable;
也就是说:hashCode()方法在散列表中才有用,在其他情况下没用;散列表中hashCode()的作用是:获取对象的散列码(索引),进而确定该对象在散列表中的位置;

散列码:散列表存储的键值对(key-value),它的特点是:能根据key快速的检索出对应的value,其中就用到了散列码;
散列表的本质是通过数组实现的,当我们要获取散列表中某个值时,实际上是要获取数组中的某个位置的元素;而数组的位置,就是通过键(下标)来获取的;更进一步说,数组的位置(下标),是通过键对应的散列码计算得到的;
补充:哈希是一种找数据位置的算法;如果数据结构是数组,计算的值就是下标;

以HashSet为例,深入说明hashCode()的作用;
将第1001个元素逐个和前面1000个元素进行比较,当插入第1001个元素时,需要怎么处理?因为HashSet是集合,不允许有重复元素;
若将第1001个元素逐个和前面的1000个元素进行比较则效率相当低下;散列表很好的解决了此问题,它根据元素的散列码计算出元素咋子散列表中的位置,然后将该元素插入该位置即可,对于相同的元素只保留一个;
由此可知:
若两个元素相等,则它们的散列码一定相等;
反过来却不一定(即若两个元素的散列码相等,该两个元素不一定相等);
在散列表中:
1.如果两个对象相等,它们的hashCode()值一定相等;
2.如果两个对象的hashCode()值相等,它们并不一定相等;
注意:仅在散列表中才有成立!!!


第四部分:hashCode()和equals()的关系

以类的用途来将hashCode()和equals()的关系分两种情况来说明;

1.第一种,不会创建“类对应的散列表”
即不会创建该类的HashSet集合;
此情况下:该类的hashCode()和equals()方法没有任何关系;
在此情况下:equals()用于比较两对象是否相等,而hashCode()没有任何作用;

举例:查看类的两个对象相等时的hashCode()和不等时的hashCode()取值;
public class Demo {
    public static void main(String[] args) {
     //新建两个相同内容的Person对象:
     Person p1=new Person("梨花",15);
     Person p2=new Person("梨花",15);
     Person p3=new Person("ol",18);
        System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode());//已重写输出true;
        System.out.printf("p1.equals(p3) : %s; p1(%d) p3(%d)\n", p1.equals(p3), p1.hashCode(), p3.hashCode());//输出false;
    }
    /**
     * Person类;
     */
    public  static class Person {
        String name;
        int age;

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

        public String toString() {
            return name + "-" + age;
        }

        @Override
        public boolean equals(Object obj) {
            if(obj==null)return false;
            //如果是同一个对象返回true;
            if(this==obj)return true;
            //判断类型是否相同;
            if(this.getClass()!=obj.getClass()){
                //类型不同;
                return false;
            }
            //到此时,两个判断的对象不是同一个对象,且类型相同,继续判断;
            Person person=(Person)obj;
            return name.equals(person.name)&&age==person.age;
        }
    }
}

运行结果:p1.equals(p2) : true; p1(23934342) p2(22307196)
p1.equals(p3) : false; p1(23934342) p3(10724224)
由此可见:p1和p2相等的情况下,hashCode()也不一定会相等;

2.第二种,会创建”类对应的散列表“
会创建类对应的散列表是指:在HashSet,HashTable,HashMap等本质为散列表的数据结构中用到该类,例如,创建该类的HashSet集合;
在此情况下:该类的hashCode()和equals()是有关系的;

在这种情况下,该类的“hashCode() 和 equals() ”是有关系的:
1)、如果两个对象相等,那么它们的hashCode()值一定相同。
这里的相等是指,通过equals()比较两个对象时返回true。
2)、如果两个对象hashCode()相等,它们并不一定相等。
因为在散列表中,hashCode()相等,即两个键值对的哈希值相等。然而哈希值相等,并不一定能得出键值对相等。补充说一句:“两个不同的键值对,哈希值相等”,这就是哈希冲突。

此外,在这种情况下。若要判断两个对象是否相等,除了要覆盖equals()之外,也要覆盖hashCode()函数。否则,equals()无效。
例如,创建Person类的HashSet集合,必须同时覆盖Person类的equals() 和 hashCode()方法。
如果单单只是覆盖equals()方法。我们会发现,equals()方法没有达到我们想要的效果。

import java.util.*;
import java.lang.Comparable;

/**
* @desc 比较equals() 返回true 以及 返回false时, hashCode()的值。
*
* @author skywang
* @emai kuiwu-wang@163.com
*/
public class ConflictHashCodeTest1{

public static void main(String[] args) {
// 新建Person对象,
Person p1 = new Person("eee", 100);
Person p2 = new Person("eee", 100);
Person p3 = new Person("aaa", 200);

// 新建HashSet对象
HashSet set = new HashSet();
set.add(p1);
set.add(p2);
set.add(p3);

// 比较p1 和 p2, 并打印它们的hashCode()
System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode());
// 打印set
System.out.printf("set:%s\n", set);
}

/**
* @desc Person类。
*/
private static class Person {
int age;
String name;

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

public String toString() {
return "("+name + ", " +age+")";
}

/**
* @desc 覆盖equals方法
*/
@Override
public boolean equals(Object obj){
if(obj == null){
return false;
}

//如果是同一个对象返回true,反之返回false
if(this == obj){
return true;
}

//判断是否类型相同
if(this.getClass() != obj.getClass()){
return false;
}

Person person = (Person)obj;
return name.equals(person.name) && age==person.age;
}
}
}

运行结果

p1.equals(p2) : true; p1(1169863946) p2(1690552137)
set:[(eee, 100), (eee, 100), (aaa, 200)]

结果分析

我们重写了Person的equals()。但是,很奇怪的发现:HashSet中仍然有重复元素:p1 和 p2。为什么会出现这种情况呢?

这是因为虽然p1 和 p2的内容相等,但是它们的hashCode()不等;所以,HashSet在添加p1和p2的时候,认为它们不相等。

下面,我们同时覆盖equals() 和 hashCode()方法。

参考代码 (ConflictHashCodeTest2.java):

import java.util.*;
import java.lang.Comparable;

/**
* @desc 比较equals() 返回true 以及 返回false时, hashCode()的值。
*
* @author skywang
* @emai kuiwu-wang@163.com
*/
public class ConflictHashCodeTest2{

public static void main(String[] args) {
// 新建Person对象,
Person p1 = new Person("eee", 100);
Person p2 = new Person("eee", 100);
Person p3 = new Person("aaa", 200);
Person p4 = new Person("EEE", 100);

// 新建HashSet对象
HashSet set = new HashSet();
set.add(p1);
set.add(p2);
set.add(p3);

// 比较p1 和 p2, 并打印它们的hashCode()
System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode());
// 比较p1 和 p4, 并打印它们的hashCode()
System.out.printf("p1.equals(p4) : %s; p1(%d) p4(%d)\n", p1.equals(p4), p1.hashCode(), p4.hashCode());
// 打印set
System.out.printf("set:%s\n", set);
}

/**
* @desc Person类。
*/
private static class Person {
int age;
String name;

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

public String toString() {
return name + " - " +age;
}

/**
* @desc重写hashCode
*/
@Override
public int hashCode(){
int nameHash = name.toUpperCase().hashCode();
return nameHash ^ age;
}

/**
* @desc 覆盖equals方法
*/
@Override
public boolean equals(Object obj){
if(obj == null){
return false;
}

//如果是同一个对象返回true,反之返回false
if(this == obj){
return true;
}

//判断是否类型相同
if(this.getClass() != obj.getClass()){
return false;
}

Person person = (Person)obj;
return name.equals(person.name) && age==person.age;
}
}
}

运行结果

p1.equals(p2) : true; p1(68545) p2(68545)
p1.equals(p4) : false; p1(68545) p4(68545)
set:[aaa - 200, eee - 100]

结果分析

这下,equals()生效了,HashSet中没有重复元素。
比较p1和p2,我们发现:它们的hashCode()相等,通过equals()比较它们也返回true。所以,p1和p2被视为相等。
比较p1和p4,我们发现:虽然它们的hashCode()相等;但是,通过equals()比较它们返回false。所以,p1和p4被视为不相等。


			

她喜欢所以就做咯