java中 ==和equals和hashCode的区别

面试的时候,经常被问到,总结一下,如有不正确的地方,多多指教。

1 、关于“==”

关于“!=”,看完下面的内容,大家应该可以自己脑补;“==”可以进行下面三种类型的比较;

  • 数值类型==

可以在基本类型之间进行比较,比如整型类型,int,long;浮点类型float,double;
(1)基本类型之间可以相互比较,如果对应的值相等,则返回true,否则,返回false;
(2)基本类型与对应的包装类型之间也可以进行比较,比较的时候,包装类型要进行拆箱操作
(3) 布尔类型与数值类型(整型、浮点型)是不能直接用“==”比较的。

示例代码:

  public static void main(String []args){
        int i = 9;
        float f = 9f;
        long l = 9l;
        double d = 9d;
        Long L = new Long(9l);
        double D = new Double(9d);
        double D1 = new Double(9.1d);

        System.out.println("int == float : " +(i==f));
        System.out.println("int == long : " +(i==l));
        System.out.println("int == double : " +(i==d));
        System.out.println("Long == Double : " +(L==D));
        System.out.println("Long != Double : " +(L==D1));
    }

运行结果:

int == float : true
int == long : true
int == double : true
Long == Double : true
Long != Double : false
  • 布尔类型==

(1) 两个操作数操作数类型为boolean,或者是其包装类型Boolean;
(2) 如果其中一个操作数是类型Boolean,则进行拆箱转换。
(3) 如果两个操作数都为true或者false,结果为true,否则,结果为false;

示例代码:

    public static void main(String []args){
        boolean t = true;
        Boolean t1 = true;
        boolean f = false;
        Boolean f1 = false;
        System.out.println("boolean t == Boolean  t1 : " +(t==t1));
        System.out.println("boolean t == Boolean  f  : " +(t==f));
        System.out.println("boolean f == Boolean  f1 : " +(f==f1));
        System.out.println("Boolean t1 == Boolean f1 : " +(t1==f1));
    }

运行结果:

boolean t == Boolean  t1 : true
boolean t == Boolean  f  : false
boolean f == Boolean  f1 : true
Boolean t1 == Boolean f1 : false
  • 引用类型

运算符的操作数是引用类型或者是null类型,引用类型可以是对象或者数组;
(1)在比较时,如果两个操作数都是null或者都引用同一个对象或数组,则结果为true,否则,结果为false;
(2)虽然“==”可以用于比较类型的引用String,但是这样的相等性测试是确定两个操作数是否引用相同的String 对象;因此,可能会出现两个new的String对象字符串序列相同,但是“==”结果却是false的情况;
(3)两个字符串的内容s和t是否相等可以通过s.equals(t)方法进行计较。

示例代码:

    public static void main(String []args){
        Object o1 = new Object();
        Object o2 = o1; //o2与o1引用同一对象
        Object o3 = new Object();
        Object o4 = null;
        Object o5 = null;
        System.out.println("o1 == o2 : " +(o1==o2));
        System.out.println("o1 == o3 : " +(o1==o3));
        System.out.println("o1 == o4 : " +(o1==o4));
        System.out.println("o4 == o5 : " +(o4==o5));

    }

运行结果:

o1 == o2 : true
o1 == o3 : false
o1 == o4 : false
o4 == o5 : true

2 、关于“equals”

boolean equals​(Object obj) 方法来自java.lang.Object类,代码如下:

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

是不是感觉有点眼熟,实际上,如果某个类没有覆盖equals()方法,当它的通过equals()比较两个对象时,是在比较两个对象是不是同一个对象。这时,等价于通过“==”去比较这两个对象。

我们可以覆盖类的equals()方法,来让equals()通过其它方式比较两个对象是否相等。通常的做法是:若两个对象的内容相等,则equals()方法返回true;否则,返回false;

由于 Java类,String 、Math、Integer、Double等这些封装类在使用equals()方法时,已经覆盖了object类的equals()方法,因此,是比较对象的内容是否相等;自定义类的话就需要自己覆盖equals()方法来判断两个对象的内容是否相等。

示例代码:

package com.example.demo;

import com.sun.tools.doclets.formats.html.SourceToHTMLConverter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.concurrent.ConcurrentHashMap;

public class EqualsTest {
    public static void main(String []args){

        Person p1 = new Person("金毛狮王",100);
        Person p2 = new Person("金毛狮王",100);
        System.out.println("未覆盖equals:  "+p1.equals(p2));

        Person1 p3 = new Person1("金毛狮王",100);
        Person1 p4 = new Person1("金毛狮王",100);
        System.out.println("覆盖equals:   "+p3.equals(p4));
    }
}

class Person{
    String name;
    int age;

    public int getAge() {
        return age;
    }

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

    public String getName() {
        return name;
    }

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

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

class Person1{
    String name;
    int age;

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

    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;
    }

    //覆盖之后,如果两个对象的内容相同,或者是属于同一个对象的引用,都返回true,否则,返回false
    @Override
    public boolean equals(Object obj) {
        if(obj == null)
            return false;
        //如果是同一个对象返回true
        if(this == obj)
            return true;
        //判断是否类型相同
        if(this.getClass()!=obj.getClass())
            return false;
        Person1 p = (Person1)obj;
        return this.name == p.name && this.age == p.age;
    }
}

运行结果:

未覆盖equals:  false
覆盖equals:   true

equals方法在非空(null)对象引用上有如下的等价关系:

  • 自反性: 对于任何非空引用值x , x.equals(x)应该返回true;
  • 对称性:对于任何非空引用值x和y,如果x.equals(y) 返回true,那么 y.equals(x)也一定返回true;
  • 传递性:对于任何非空引用值x,y,z,如果x.equals(y)返回true,y.equals(z)返回true, 那么x.equals(z)也一定返回true;
  • 一致性:对于任何非空引用值 x和y,在多次调用 x.equals(y)始终返回true 或始终返回false,除非equals的比较对象被修改(这里多说一句,所谓的“修改”就是说你在覆盖equals方法时,进行对象内容比较,用到的对象中的某个成员的值发生了改变)。
  • 对于任何非空引用值x, x.equals(null)应返回false。

3 、关于“hashCode”

public int hashCode()也是来自java.lang.Object类,它返回对象的哈希码(也叫散列码,一个整型数字);

哈希码是用来确定该对象在哈希表中的索引位置,在Java集合中,HashMap,HashTable,HashSet等都属于哈希(散列)表;因此,如果当前对象不会在散列表中被用到(就是不会作为HashSet的元素,或者HashMap的Key),那么完全可以忽略hashCode;可能你现在在想我平时用HashMap的时候也没有考虑hashCode的问题啊,不也用的好好的;因为,一般情况下,都用String或者Java已有类型作为Key,已有类型在默认继承Object的时候已经覆盖了hashCode方法,因此在使用HashSet或者HashMap的时候,不会出现重复元素;看几个例子就明白了:

下面分别用HashSet和HashMap做测试,要做的就是保证HashSet里面元素不同,HashMap的key也不相同;

先看HashSet,
示例代码:

package com.example.demo;

import java.util.HashSet;
import java.util.Iterator;

public class HashTest {

    public static void main(String[] args) {
        HashSet<MyString>  stringHashSet = new HashSet<>();
        HashSet<MyString1> myStringHashSet = new HashSet<>();
        stringHashSet.add(new MyString("hello"));
        stringHashSet.add(new MyString("hello"));
        stringHashSet.add(new MyString("hello1"));
        Iterator it = stringHashSet.iterator();
        while(it.hasNext()){
            System.out.println(it.next());
        }
        System.out.println("===========");
        myStringHashSet.add(new MyString1("hello"));
        myStringHashSet.add(new MyString1("hello"));
        myStringHashSet.add(new MyString1("hello1"));
        Iterator<MyString1> it1 = myStringHashSet.iterator();
        while(it1.hasNext()){
            System.out.println(it1.next());
        }
    }
}

class MyString{

    String value;

    public MyString(String value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return value;
    }
}

class MyString1{

    String value;

    public MyString1(String value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return value;
    }

    @Override
    public int hashCode() {
        return value.toUpperCase().hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        MyString1 s = (MyString1)obj;
        return this.value ==s.value;
    }
}
  • 运行结果
hello
hello
hello1
===========
hello1
hello

可以看出在没有覆盖hashcode()方法时,HashSet中的元素可能会出现重复的情况,这就违背了Set集合的设计初衷,而覆盖了hashcode()方法,HashSet中的元素就不重复了;

原因就是一般情况下,不同的对象会产生不同的hashcode,即使对象的内容相同;按照hashcode的约定规则:如果两个对象equals()相等,那么应该有相同的hashcode,因此,覆盖后的hashcode()要符合这一规则。

hashcode()的生成有如下的约定规则:

  • 在Java应用程序执行期间,多次在同一对象上调用它时,hashCode()必须始终返回相同的整数,除非当前对象的内容被修改;从应用程序的一次执行到同一应用程序的另一次执行,该整数不需要保持一致。
  • 如果两个对象equals()后相等,它们在使用hashcode()方法后,生成的hashcode(散列码)应该相同(这就解释了如果要覆盖equals()方法的话,就要覆盖hashcode()方法)。
  • 如果两个对象equals()后不相等,它们在使用hashCode()方法后,产生的散列码可以相同,也可以不同;但是,要明白:为不同的对象生成不同的hashcode(散列码)能够改善散列表的性能。

HashMap的例子不贴了,大家可以自己试一下,只要保证key满足上述特性就可以了;

关于散列表的内容,数据结构与算法(要找经典教材看)中有详细介绍,不太提倡看博客,有时候大神的思路不一定你能跟的上,书上的内容更加全面一些,更通俗易懂。

参考资料

https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.21
https://docs.oracle.com/javase/10/docs/api/java/lang/Object.html#equals(java.lang.Object)
https://www.cnblogs.com/skywang12345/p/3324958.html
https://www.jianshu.com/p/1acdfac2b4e4?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation
http://www.cnblogs.com/skywang12345/p/3311899.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值