结合JVM深入的聊聊Java中的==、equals()、hashcode()的区别和联系

==、equals()、hashcode()作为面试和学习中的易混点,搜索了很多资料,做了如下总结,欢迎大家一起探讨。

1 对象的相等

首先要区别一下的什么叫做对象什么叫做对象的引用,建议参考这篇博文,作者讲的十分的详细。对象和对象的引用
在我的理解中对象是一个实例存储在内存中的某个物理地址处。对象的引用是一个符号,它指向了对象,符号中存储着它所指向的对象的唯一地址。

String a = ;
a = "abc";

a就是对象的引用,“abc”才是对象。我们常说的两个对象是否相等这句话应该理解为两个对象的引用所指向的对象是否为同一个对象(不包括重写)。无论是基本数据类型还是引用数据类型比较的都是都是这样的判断。
我的理解,在Java语言自身的定义中,对象的引用的相等为两种。

2 ==

Java语言中最常用的比较两个对象的方法,我们常说:
1.基本类型的
对于基本类型的对象,我们常说**==**比较两个对象引用所表示的值是否相等。这是因为,这里会涉及到Java的对象创建过程。我们举一个简单的例子:

int a = 2;
int b = 2;

对象的创建过程如下:
对象创建的过程代码中,int a 创建了一个名称为a的对象引用,行a=2执行时首先去栈的常量池中去看看有没有值为2的对象,如果没有就创建一个对象,其值为2。程序int b = 2;执行时,首先创建一个名称为b的对象引用,检查栈的常量池中是否有值为1的对象,如果有,b就指向常量池的2。对象引用a和对象引用b指向的是同一个对象,因此输出为true。
2.引用数据类型的
==比较两个对象引用所指向的地址值是否相等。
(1) String对象
看如下代码:

	String a = "cxy";
    String b = "cxy";
    String c = new String("cxy");
    System.out.println(a == b);//true
    System.out.println(a == c);//false

为什么一个是true一个是false呢?字符串对象在JVM中可能有两个存放的位置:字符串常量池或堆内存。
使用常量字符串初始化的字符串对象的引用,它的值存放在字符串常量池中;
使用字符串构造方法创建的字符串对象,它的值存放在堆内存中;
看如下的图会更加清楚
在这里插入图片描述
对于使用常量字符串初始化变量的引用a或者b的时候,与基本数据类型创建对象的引用时指向的流程相同,那么ab 自然为true。
对于使用构造方法创造的对象存在堆中,地址自然是与常量池中的对象地址是不一样的,因此a
c自然而然输出为false。

对于String类型的比较相等的,还有一个点需要注意的地方,我们来看如下的代码。

public static void main(String[] args) {
        String s1 = "a";
        String s2 = "a" + "b";
        String s3 = s1 + "b";

        System.out.println(s2 == "ab");//true
        System.out.println(s3 == "ab");//false
    }

为什么会出现这样的结果呢?第一条语句输出true,第二条语句输出false。这说明javac编译可以对字符串常量直接相加的表达式进行优化,不必要等到运行期去进行加法运算处理,而是在编译时去掉其中的加号,直接将”a“+"b"其编译成一个字符串常量”ab“。上面我们也说了,在JVM对于常量数据是直接放到字符串常量池中的,那么在创建对象s2的时候直接指向常量池中的s2即可,”ab“存储的地址与”s2“对象的引用指向的地址i相同,自然两者相等。而s3是通过对象与字符串相连,其创建的对象是放在堆当中,地址不同,因此必然不等。
(2)普通的对象
在这里创建一个User类

public class User {
    private String name;
    private String password;

    public User(String name, String password) {
        this.name = name;
        this.password = password;
    }
}

主程序代码:

public static void main(String[] args) {
        User u1 = new User("zhangsan","123");
        User u2 = new User("zhangsan","123");

        System.out.println(u1==u2);//false
    }

通过new创建的对象,JVM都会在堆中为其开辟一个空间存储,因此自然也就不等。

总结一下==比较对象的引用,无论是比较基本数据类型还是引用数据类型比较的都是对象的引用所指向的地址。

3 equals()方法

1.源码

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

在equals()方法如果没有重写的话实际上就是==罢了。

2.重写equals()
没有重写equals()方法之前,看如下代码:

public static void main(String[] args) {
        User u1 = new User("zhangsan", "123");
        User u2 = new User("zhangsan", "123");

        System.out.println(u1 == u2);//false
        System.out.println(u1.equals(u2));//false
    }
}

显然两者输出的都是false。
如果我们在User类中重写equals()方法,重写代码如下:

@Override
    public boolean equals(Object object) {
        //当两个对象指向的地址相同时,那么两个对象inag
        if (this == object) return true;

        if (object == null || getClass() != object.getClass()) return false;

        User user = (User) object;

        //判断name是否相等,不等则返回false,相等返回true
        if (name != null ? !name.equals(user.name) : user.name != null) return false;
        //判断password是否相等,不等则返回false,相等返回true
        return password != null ? password.equals(user.password) : user.password == null;
    }

根据上面这样重写equals()方法,对于User对象的equals()方法来说,两个User对象引用相等只要字面量相等即可而不用强制他们指向相同的地址才认为是相等的。当然,如果你直接让User的equals()方法返回true也是可以的,不过这样的话写重写不就没什么意思了吗?
JAVA中常见的类基本上都重写了equals()方法,如果有兴趣的话可以查查API文档,这也是为什么我们这里可以直接使用name.equals(user.name)来比较两个对象的字面量是不是相等的原因了。

4 hashcode()方法

我想大部分人都知道重写equals()方法的时候一定要重写hashcode()方法,为什么呢?王八的屁股–龟腚(规定)。
hashCode()与equals()相关规定:
在hashCode()与equals()相关规定中,这里所说的两个对象相等,讲的是两个对象通过equals()比较位true的。
1.如果两个对象相等,则hashcode必须相等。
2.如果两个对象相等,对其中的一个对象调用equals()必须返回true,也就是说a .equals(b),则 b.equals(a)也返回true.
3.如果两个对象有相同的hashCode值,他们也不一定相等。但若两个对象相等,则hashcode值一定相等。
4.因此若equals()被覆盖过,则hashcode()也必须被覆盖。
5.hashCode()的默认行为实在对heap上的对象产生独特的值。如果没有override过hashCode(),则该class的两个对象怎么都不会被认为是相同的。
6.equals()的默认行为是执行==的比较。也就是说会去测试两个引用是否对上heap上同一个对象。如果equals()没有被覆盖过,两个对象永远都不会被视为相同的。因此不同的对象有不同的字节组合。

那么就意味着:
a.equals(b)必须与a.hashCode()==b.hashCode()等值。
但a.hashCode()==b.hashCode不一定要与a.equals(b)等值。
(1) 所以在没有重写User的euqals()方法前我们执行如下的代码:

public static void main(String[] args) {
        User u1 = new User("zhangsan", "123");
        User u2 = new User("zhangsan", "123");
        User u3 = u1;

        System.out.println(u1 == u2);//false
        System.out.println(u1.hashCode() == u2.hashCode());//false
        System.out.println(u1.hashCode() == u3.hashCode());//true
    }

两个对象的地址不同计算出来的hashcode()值一定不同所以第二个计算出来的结果为false。
(2)重写hashcode()方法
User中hashcode()方法如下:

@Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + (password != null ? password.hashCode() : 0);
        return result;
    }

我们在此执行如上的代码,System.out.println(u1.hashCode() == u2.hashCode());得到的结果就true了。

总结:通过 == 比较两个对象是否相等的时候,最终比较的还是两个对象的地址是否相等。通过equals()方法比较两个对象是否相等时,我们关注的是两个对象的字面量是否相等。而hashcode() 在这里经常被提到的原因是:JAVA中重写了某个类的equals方法的使得对象相等,那么一定要重写hashcode()方法。即满足条件:a.equals(b)必须与a.hashCode()==b.hashCode()等值。
最后,个人总结难免有所疏漏或者讲的不准确的地方,如果有什么不对的地方欢迎大家私信或者评论指出,谢谢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值