equals和==和hashcode的恩怨情仇

一、前言:

 equals和==和hashcode是java中的基础中的基础,但是确实容易被问到,而且因为工作中经常用所以很容易在复习时遗漏掉,这几天面了个大厂就在这个问题上翻车了,不仅记混了,而且很坚信自己记混的答案,好在其他问题答的不错,加上面试官容忍度高放过一马,所以今天也就详细整理了下它们三个的恩怨情仇

二、【==】双等号

【==】其实没那么复杂,它的功能就只是比较两边的值是否相等。只是如果变量是引用类型Integer、String、Object)的话,比较的就是内存地址,因为引用类型变量存的值就是对象的地址而已。而基本类型int、double)的变量存的就是它们的值,和内存地址什么的无关。

所以我们在用【==】比较引用类型的变量时,就麻烦了。如果引用类型是Integer、String、Long这种,我们比较它的时候肯定是打算比较它们代表的值,而不是比较什么内存地址。

三、equals方法:

equals()是Object类的方法,而Object类又是所有类的祖宗(父类),所以所有的引用类型都有equals()方法。但是有很多类都会重新equals以及hashcode方法

1.object类的equals

先看下object的equals方法

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

object类的equals方法很简单其实就是【==】

2.Integer类的equals

可以看出Integer类的equals方法其实时比较的值

 public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }

3.String类的equals方法

public boolean equals(Object anObject) {
        //判断引用
        if (this == anObject) {
            return true;
        }
        //判断是否是String
        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;
    }

大概流程是:

(1)判断引用是否相等

(2)判断是否是String

(3)判断字符长度是否相等

(4)遍历判断每个字符是否相等

四、==和equals的区别

如果是基本类型,只能用【==】,它们没有equals方法。

如果是引用类型,直接【==】的话是比较内存地址。如果这个引用类型重写过equals()方法,可以用equals()方法比较内容,如Integer、String……等常用的引用类型都是有重写过euqals()的。

五、int、Integer和new Integer()多种赋值比较区别

1.int

int无需多说,基本类型,无论是声明变量还是==比较我们都很清楚了,都只是比较值而已。

2.Integer

Integer的初始化就不太一样了

Integer i = 10;
Integer j = 10;
System.out.println(i==j);

如果这么进行赋值,那么返回是true,按照之前的说法比较的是地址那么必然是不一样的才对啊,这里就涉及了java自动装箱

系统会自动将赋给Integer变量的int值封装成一个Integer对象,例如Integer a1=3,实际上的操作是

Integer a = Integer.valueof(3)

但是这个自动装箱也是有范围限制的,当int的范围在-128——127之间时,会通过一个IntegerCache缓存来创建Integer对象;当int不在该范围时,就直接是new Integer()创建对象

3.new Integer()

这种赋值就不用多说了,等号会比较引用,所以不会相等

4.其他包装类

例如Long和Integer是一样的,有一个-128~127的缓存,Double和Float则是没有缓存直接返回new。

六、String直接赋值和new String()比较的区别

String也需要特别的说明一下,因为它并不属于基本类型,所以没有int、long那种类型,这种情况我们只需要比较两种情况,直接赋值和new,也就是比较:

//第一组
String a=new String("test");
String b="test";
System.out.println(a==b);


//第二组
System.out.println("a"=="a");

//第三组

String a=new String("test");
String b=new String("test");
System.out.println(a ==b);

答案是 false,true,false,因为只要有一边是new的方式初始化的变量,那地址肯定是不一样的,并且这里也是用【==】进行比较地址,自然是false。

字符串常量池

关于String的直接赋值,则需要先说明一下字符串常量池。

String类是我们平时项目中使用的很多的对象类型,jvm为了提升性能和减少内存开销,以及避免字符的重复创建,专门维护了一块字符串常量池的内存空间。当我们需要使用字符串时,就会先去这个常量池中找,如果存在该字符串,就直接将该字符串的地址返回去直接用。如果不存在,则用new进行初始化并将这个字符串投入常量池中以供后面使用。

这个字符串常量池就是通过直接赋值时使用的,所以如果是直接赋值的方式初始化相同内容的String,那么其实都是从同一个常量池里取到字符串,地址指向的是同一个对象,自然结果都是相同的。

字符串的拼接比较

String a = "hello";
String d = "helloworld";
final String c = "hello";
System.out.println(d == a + "world");          //false
System.out.println(d == "hello" + "world");    //true
System.out.println(d == c + "world");          //false

如果只看内容,d都是和helloworld进行了比较,但是带有变量的就是false,纯字符串的就是true,这是为什么呢?

其实这跟jvm的编译期有关,在进行编译时,它可以识别出"hello" + “world"这样的字面量和"helloworld"是相同的,认为这是直接的字面量赋值。通过反编译其实可以看出来,编译后,它直接将"hello” + “world"编译成了"helloworld”。所以自然都是在同一个常量池里找,比较起来也是相同的。


但是加上final就有不一样了,这里由于c被视为了常量,所以同样认为是字面量赋值,最终还是在常量池中获取的值,结果就是true了。

如果有任一边是通过new赋值的,那么结果肯定是false。

如果两边都是直接赋值的,或是通过final变量进行拼接赋值的,结果是true。只要有一边有涉及非final变量,结果就是false。

七、 hashCode()

1.简介:

hashCode()方法的作用是获取哈希码,也称为散列码,它实际上只是返回一个int整数。

但是它主要是应用在散列表中,如:HashMap,Hashtable,HashSet,在其他情况下一般没啥用,原因后面会说明。

Java中的hashCode并没有真正的实现为每个对象生成一个唯一的hashCode,还是会有一定的重复几率。

object中的hashcode()

public native int hashCode();

2.重写hashCode()

核心是保证相同的对象能返回相同的hash值,尽量做到不同的对象返回不同的hash值。主要能保证核心的规则即可。例如Integer的hashcode就很简单粗暴,直接返回它所代表的的value值。也就是1的hashcode还是1,100的hashcode还是100。

    public int hashCode() {
        return Integer.hashCode(value);
    }

String 的hashCode就相对复杂一些了通过这个公式计算的hashcode

s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]

    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

在java的很多类中都会重写equals和hashCode方法,这是为什么呢?比如我定义两个字符相同的字符串,那么对它们进行比较时,我想要的结果应该是相等的,如果你不重写equals和hashCode方法,他们肯定是不会相等的,因为两个对象的内存地址不一样。

3.hashCode()和equals() 的关系

和equal()方法作用类似,hashCode()在java中的也是用于比较两个对象是否相等。我们应该都大概听过一句话:重写equals()方法的话一定要重写hashCode()。这里就说明一下这点。

分两种情况:

(1)首先一种情况是确定了不会创建散列表的类,我们不会创建这个类的HashSet、HashMap集合之类的。这种情况下,这个类的hashCode()和equals() 完全没有任何关系,当我们比较这个类的两个对象时,直接用的就是equals(),完全用不上hashCode()。自然,也没啥必要重写。

(2)另一种情况自然就是可能会需要使用到散列表的类了,这里hashCode()和equals()就比较有关系了:

在散列表的使用中,经常需要大量且快速的对比,例如你每次插入新的键值对,它都必须和前面所有的键值对进行比较,确保你没有插入重复的键。这样如果每个都用equals,可想而知,性能是十分可怕的。如果你的equals再复杂一些,那就凉凉了。

这时候就需要hashCode()了,如果利用hashCode()进行对比,只要生成一个hash值比较数字是否相同就可以了,能显著的提高效率。但是虽然如此,原本的equals()方法还是需要的,hashCode()虽然效率高,可靠性却不足,有时候不同的键值对生成的hashcode也是一样的,这就是哈希冲突。

在使用时,hashCode()将与equals结合使用。虽然hashcode可能会让不同的键产生相同的hashcode,但是它能确保相同的对象返回的hashcode一定是相同的(除非你重写没写好),我们只需要利用后面这点,一样可以提高效率。

在散列表中进行对比时,先比较hashCode(),如果它不相等,那说明两个对象肯定不可能相同,就可以直接进行下一个比较了。但如果hashCode()相同,因为哈希冲突的缘故我们无法直接判断两个对象是相同的,就必须继续比较equals()来获取可靠的结果。

所以如果这个类可能会创建散列表的情况下,重写了equals方法,就必须重写配套的hashcode方法,他们两个在散列表中是搭配使用的。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值