【Java基础】==、equals、hashcode方法详细解读与测试

31 篇文章 4 订阅

一.基础知识:

  • 如果一个变量指向的数据是对象类型,这时候涉及两块内存,对象本身占用一块内存(堆内存),变量也占用一块内存。例如:Object o = new Object(); 变量o占一个内存,new Object()是占另一个内存。通俗来讲:new Object()创造了一个对象,类型是Object,而Object o则创造了一个可以存储Object类型对象引用的“引用存储器”,而等号=则把new Object()这个对象的引用存到了o之中,这样就可以用o来使用这个对象数据和方法了。

  • 举一个案例(图示):

//构造方法创建   ---new
String s1 = new String("aaa");//这里把"aaa"代表的对象的引用存到了s1中
s1= new String("bbb");//这里又把另一个对象"bbb"存到了s1中,所以s1就不是指向原来的"aaa"了,而是指向"bbb"了,新的空间, aaa等着回收

创建对象

二.equals和==的区别

  • 测试的代码有两个类,Test类和User类,可以自己粘贴测试一下
  • Test类:
import java.util.HashSet;

public class Test {
    public static void main(String[] args) {
        test_eq();    
//        test_hashcode();    
    }

    public static void test_eq() {
        int a = 1;
        int b = 1;
        System.out.println(a == b);  //true 比较的是值

        User u1 = new User("tom",12,1001);
        User u2 = new User("tom",12,1001);
        System.out.println(u1+","+u2);  //User@1b6d3586,User@4554617c
        //这里==比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的地址是否相同,即是否是指相同一个对象
        System.out.println(u1 == u2);   //false   比较的是地址,不是值
        System.out.println(u1.equals(u2));

        //String类中对equals方法进行了重写,字符串与字符串比较的是值
        String s1 = "jack"; //字符常量,放在常量池
        String s2 = "jack"; //创建流程:先判断常量池是否存在jack,如果有,就直接指向,如果没有则再创建一个对象
        System.out.println(s1.equals(s2));  //true 字符串  实际调用的是String类中重写的方法,比较的是值
    }

    private static void test_hashcode() {
        User u1 = new User("tom",12,1001);
        User u2 = new User("tom",12,1001);
        HashSet<Object> set = new HashSet<>();
        set.add(u1);
        set.add(u2);
        System.out.println(u1.hashCode());  //110571150
        System.out.println(u2.hashCode());  //110571150
        System.out.println(u1.hashCode()==u2.hashCode());   //true
        System.out.println(Integer.toHexString(u1.hashCode())); //6972e8e
        System.out.println(u1); // User@6972e8e  包名+类名 + @ + 16进制的hashCode值

        System.out.println(u1.equals(u2));

    }

}

  • User类:
import java.util.Objects;

public class User {
    private String name;
    private int age;
    private int id;

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

    public User() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    //按快捷键自动生成equals和hashcode方法
//    @Override
//    public boolean equals(Object o) {
//        if (this == o) return true;
//        if (o == null || getClass() != o.getClass()) return false;
//        User user = (User) o;
//        return age == user.age &&
//                id == user.id &&
//                Objects.equals(name, user.name);
//    }
//
//    @Override
//    public int hashCode() {
//        return Objects.hash(name, age, id);
//    }

    //这个equals()方法主要测试equals和==的
    @Override
	public boolean equals(Object obj) {
        //instanceof运算符是用来在运行时指出对象是否是特定类的一个实例,instanceof通过返回一个布尔值指出true或者false
        //在这里是Test类创建的User类的u1和u2是否是User的实例,所以要判断,通俗来讲:你不能在大街上随便拉一个人做手术,必须要有医师执照
		if(obj instanceof User) {
		    //属于引用类型的强制转换,将父类类型转为子类,向下转后,可以访问子类特有的属性和方法
			User u = (User)obj;
			//这里equals和==是一样的,都是比较的是值
			if(this.name.equals(u.getName()) && this.age == u.getAge() && this.id==u.getId()) {
				return true;
			}
		}

		return false;
	}
}


  • ==判断的是是否引用同一个对象(判断基本类型比较的是值,判断引用类型比较的是地址)
		int a = 1;
        int b = 1;
        System.out.println(a == b);  //true 比较的是值 
  • 基本类型有8个(byte,short,int,long,float,double,boolean,char),基本形式:数据类型 变量名 = 值,输出时显示的是具体的值
		User u1 = new User("tom",12,1001);
        User u2 = new User("tom",12,1001);
        System.out.println(u1+","+u2);  //User@1b6d3586,User@4554617c
        System.out.println(u1 == u2);   //false   比较的是地址,不是值
  • 这里==比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的地址是否相同,即是否是指相同一个对象
  • 引用类型:除了基本类型以外都是引用类型,如String 数组 类 接口 枚举 基本形式:数据类型 名称 = new 数据类型(),输出的显示的是内存地址
		User u1 = new User("tom",12,1001);
        User u2 = new User("tom",12,1001);
        System.out.println(u1+","+u2);  //User@1b6d3586,User@4554617c
        System.out.println(u1 == u2);   //false   比较的是地址,不是值
        System.out.println(u1.equals(u2));//false
  • 上述代码增加了一行equals的比较

1. 没有在User类重写equals()方法

  • 如果equals没有被重写,默认和“==”一样。也就是User类中没有重写equals方法,只写了用户类的属性,有参和无参构造,还有get和set方法,其余没有。
  • 这时候运行出来的结果是false,在User类中没有重写equals方法之前,比较的是地址

2.在User类重写equals()方法

  • User类:
//    @Override
//    public boolean equals(Object o) {
//        if (this == o) return true;
//        if (o == null || getClass() != o.getClass()) return false;
//        User user = (User) o;
//        return age == user.age &&
//                id == user.id &&
//                Objects.equals(name, user.name);
//    }
//
//    @Override
//    public int hashCode() {
//        return Objects.hash(name, age, id);
//    }

    @Override
	public boolean equals(Object obj) {
        //instanceof运算符是用来在运行时指出对象是否是特定类的一个实例,instanceof通过返回一个布尔值指出true或者false
        //在这里是Test类创建的User类的u1和u2是否是User的实例,所以要判断,通俗来讲:你不能在大街上随便拉一个人做手术,必须要有医师执照
		if(obj instanceof User) {
		    //属于引用类型的强制转换,将父类类型转为子类,向下转后,可以访问子类特有的属性和方法
			User u = (User)obj;
			//这里equals和==是一样的,都是比较的是值
			if(this.name.equals(u.getName()) && this.age == u.getAge() && this.id==u.getId()) {
				return true;
			}
		}

		return false;
	}
  • Test类:
		User u1 = new User("tom",12,1001);
        User u2 = new User("tom",12,1001);
        System.out.println(u1+","+u2);  //User@1b6d3586,User@4554617c
        System.out.println(u1 == u2);   //false   比较的是地址,不是值
        System.out.println(u1.equals(u2));//true
  • 上述代码是User类中重写equals方法后(在重写equals方法时,系统会自动将equals和hashCode两个方法都重写了),这时如果想用系统自动生成的equals方法进行测试,可以先将hashCode方法给注释掉。这里我用的是自己写的equals方法进行的测试,再运行程序时,equals比较出来的是true

提醒:equals方法的重写,用系统的或者用自己写的equals方法,性质都是一样的,只能使用其中一个equals()方法。

  • 源码equals()方法:
    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }
  • 因为这时候是按照重写后的规则进行比较,而源码equals方法返回的是“==”的值,比较的是值或者内容是否相等。如果将u1中的name值改变,equals返回的就是false,因为u1和u2实例内容不一样呢。
		String s1 = "jack"; //字符常量,放在常量池
        String s2 = "jack"; //创建流程:先判断常量池是否存在jack,如果有,就直接指向,如果没有则再创建一个对象
        System.out.println(s1.equals(s2));  //true 字符串  实际调用的是String类中重写的方法,比较的是值
  • 看一个知识:因为String类经常会被用到,所以String类中对equals方法就已经进行了重写,字符串与字符串比较的是值
  • String常量:使用双引号直接创建的字符串,称为字符常量,字符常量放在内存中的常量池,在我的下一篇博客中已经写了关于常量池的介绍。
  • 代码常量池示意图
    常量池

总结:

  • == 判断的是是否引用同一个对象( 判断基本类型比较的是值,判断引用类型比较的是地址 )
  • 如果equals 没有被重写,默认和 == 一样
    String类中对equals 方法进行了重写,字符串与字符串比较的是值
  • 如果重写了equals 方法,则按照重写后的规则进行比较
  • 自定义类可以重写equals方法,实现对特定属性的比较

三.equals和hashcode的使用:

  • 为什么要使用hashCode():
    java.lang.Object 类 ,所有类的根,所有类都直接或者间接的继承了Object类。其中hashcode()和equals()方法,经常会用到。
    集合Set中的元素是无序不可重复,那判断两个元素是否重复的依据是什么呢?
    有小伙伴说:比较对象是否相等当然用Object类中的equals()方法啊。但是,如果Set集合中存在大量对象,后添加到集合中Set的对象元素比较次数会逐渐增多,大大的降低了程序运行效率。Java中采用哈希算法(也叫散列算法)来解决这个问题,将对象(或数据)依特定算法直接映射到一个地址上,对象的存取效率大大提高。
		User u1 = new User("tom",12,1001);
        User u2 = new User("tom",12,1001);
        HashSet<Object> set = new HashSet<>();
        set.add(u1);
        set.add(u2);
        System.out.println(u1.hashCode());  //110571150
        System.out.println(u2.hashCode());  //110571150
        System.out.println(u1.hashCode()==u2.hashCode());   //true
        System.out.println(Integer.toHexString(u1.hashCode())); //6972e8e
        System.out.println(u1); // User@6972e8e  包名+类名 + @ + 16进制的hashCode值

        System.out.println(u1.equals(u2));

1.equals()方法注释掉,使用hashCode()方法

//    @Override
//    public boolean equals(Object o) {
//        if (this == o) return true;
//        if (o == null || getClass() != o.getClass()) return false;
//        User user = (User) o;
//        return age == user.age &&
//                id == user.id &&
//                Objects.equals(name, user.name);
//    }
//
    @Override
    public int hashCode() {
        return Objects.hash(name, age, id);
    }
  • 以上这个示例,我们只是重写了hashCode方法,u1和u2通过equals()方法比较返回的是false
  • 从上面的结果可以看出:虽然两个对象的hashCode相等,但是实际上两个对象并不是相等;,我们没有重写equals方法,那么就会调用object默认的equals方法,是比较两个对象的引用是不是相同,显示这是两个不同的对象(堆内存),两个对象的引用肯定是不定的。这里我们将生成的对象放到了HashSet中,而HashSet中只能够存放唯一的对象,也就是相同的(适用于equals方法)的对象只会存放一个,但是这里实际上是两个对象a,b都被放到了HashSet中,这样HashSet就失去了他本身的意义了。

2.User类同时使用equals()方法和hashCode()方法

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return age == user.age &&
                id == user.id &&
                Objects.equals(name, user.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age, id);
    }
  • 以上这个示例,我们同时重写了equals和hashCode方法,这时u1和u2通过equals()方法比较返回的是true

  • 从上面的结果可以看出:把User类中的equals方法写上,比较的就是两个对象的内容是否相等(也就是比较的是值,不是地址)

  • 那集合(Set)如何判重呢?

    • 添加元素时,调用该对象的hashCode方法,获取一个哈希值
    • 根据哈希值,使用哈希算法(有好几种算法,一般用除留取余法)通过余数确定一个哈希表中的位置
    • 如果该位置没有元素,说明此对象是第一次存储到集合Set中,则直接将此对象存储在此位置中
    • 如果该位置有元素了,则继续调用两个对象的equals()方法进行比较
      - true:则认为两个对象为同一个对象,无法添加,舍弃当前对象
      - false:则认为两个对象不是同一个对象,以链表结构向当前位置进行追加,这属于哈希表中解决冲突的方法称为:链地址法。还有一个方法叫线性探查法,这属于数据结构中哈希表中的知识。
  • 通俗来讲:有两个人名字一样(hashCode()值一样),你不能说他们是同一个人。名字相同,还要通过equals()判断两个人是否相等。

  • 下面是我学习数据结构中看视频截的图,哈希表解决冲突的线性探查法和链地址法讲解图示:
    线性探查法
    链地址法

  • 这里首先明白一个问题:
    如果两个对啊ing的equals方法比较为true时,则两个对象的hashCode值也一定相同。先判断hashCode(),然后再判断equals()。如果两个对象的equals方法比较为false,却不能证明两个对象的hashCode值可以不相同,也可以相同。

总结:

  • 判断重复的依据,两个对象hashCode值一致,并且equals也返回true— 同一个对象–集合中只添加一个进入
  • 在set结合中防止相同对象被添加,需要hashCode 和equals都重写

如果对你有帮助,点个赞吧0.0
若有不正之处,请多多谅解并欢迎批评指正,不甚感激

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值