Java——equals和==,HashCode的比较如何重写equals方法

本节我们先来看一段代码,对于有一点编程基础的人来说,这段代码我相信是能看懂的:

public class Main {
    public static void main(String[] args) {
        int a = 2;
        int b = 2;

        if(a==b) {
            System.out.println(true);
        }else {
            System.out.println(false);
        }
        
        char c = 'c';
        char d = 'c';
        if(c == d) {
            System.out.println("c == d " + true);
        }else {
            System.out.println("c != d" + false);
        }
        
        String  str = "cc"; //在常量池中进行查找,查找不到新建一个对象
        String str0 = new String("cc"); //str0是一个新的引用
        String str1 = str;
        String str2 = "cc";
        if(str == str0) {
            System.out.println("str == str0 " + true);
        }else {
            System.out.println("str != str0 " + false);
        }
        
        if(str == str1) {
            System.out.println("str == str1 " + true);
        }else {
            System.out.println("str != str1 " + false);
        }
        
        if(str0 == str1) {
            System.out.println("str0 == str1 " + true);
        }else {
            System.out.println("str0 != str1 " + false);
        }
        
        if(str0.equals(str1)) {
            System.out.println("str0.equals(str1) " + true);
        }else {
            System.out.println("!str0.equals(str1)" + false);
        }
    }
}

运行结果:

true
c == d true
str != str0 false
str == str1 true
str0 != str1 false
str0.equals(str1) true

1、==

==是关系操作符,下面的的讲解就需要结合上面的代码了,在上面的代码中可以看到,其实是有两种类型的比较的,一种是java中基本类型的比较,另一种是对非java基本类型的比较的,同时还可以发现,对于基本类型直接用==进行比较是否相等的结果都是true

我们现在来分析:

对于java八种基本数据类型的变量,变量直接存储的是“值”。因此,在使用关系操作符 == 来进行比较时,比较的就是“值”本身。要注意的是,浮点型和整型都是有符号类型的(最高位仅用于表示正负,不参与计算【以 byte 为例,其范围为 -2^7 ~ 2^7 - 1,-0即-128】),而char是无符号类型的(所有位均参与计算,所以char类型取值范围为0~2^16-1);
而非基本类型即引用类型的变量呢?在Java中,引用类型的变量存储的并不是“值”本身,而是与其关联的对象在内存中的地址。在上面的代码中我们可以看到String str = "cc";这句代码的意思是,首先去常量池中进行查找是否有这个对象,如果有则str引用地址,所以当我们后面再次声明String str2 = "cc的时候,拿到的地址和str是一样的。String str0 = new String("cc");这句话,str0不会去常量池中寻找是否有“cc”,因为已经new了,是一个新对象,不在是同一个内存地址了。我们因此也不难知道,当内存地址相同的时候==进行判断的时候才返回true,也就是说==判断的是内存地址是否相等

2、equals方法

来源是Object

我们都知道Object是所有对象的父类,可以说是老祖宗了,在Object中的equals方法:

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

不难发现,在Object中equals方法还是对内存地址的比较,即是否指向同一个对象。在文章开始的代码中我们可以发现当String类型用equals方法进行比较的时候,内存地址不一样的返回的也是true,这是为什么呢?这是因为String重写了Object的equals方法,这是源码:

 /**
     * Compares this string to the specified object.  The result is {@code
     * true} if and only if the argument is not {@code null} and is a {@code
     * String} object that represents the same sequence of characters as this
     * object.
     *
     * @param  anObject
     *         The object to compare this {@code String} against
     *
     * @return  {@code true} if the given object represents a {@code String}
     *          equivalent to this string, {@code false} otherwise
     *
     * @see  #compareTo(String)
     * @see  #equalsIgnoreCase(String)
     */
    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String) anObject;
            int n = length();
            if (n == anotherString.length()) {
                int i = 0;
                while (n-- != 0) {
                    if (charAt(i) != anotherString.charAt(i))
                            return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

我们通过这段源码可以看到,当我们使用equals进行比较的时候当要比较的两个字符串内存地址即指向的引用一致时,直接返回true,在这里我们看到了一个这样的关键字“instanceof ”,稍微说一下: instanceof 是 Java 的一个二元操作符,类似于 ==,>,< 等操作符。instanceof 是 Java 的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回 boolean 的数据类型。所以说这里的** if (anObject instanceof String)**判断是否都是String类型,Java 中所有内置的类的 equals 方法的实现步骤均是如此,特别是诸如 Integer,Double 等包装器类 。
很多时候Java内置的equals方法不能满足我们所有的需求,比如判断两个JavaBean对象是否相等,那我们就要重写equals方法,如何重写呢?重写equals方法我们要遵循几大原则:
(1)、对称性: 如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true” ;
(2)、自反性: x.equals(x)必须返回是“true” ;
(3)、一致性: 如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是“true” ;
(4)、对称性: 如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”
(5)、任何情况下,x.equals(null)【应使用关系比较符 ==】,永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”
如何重写equals方法我们不要着急,慢慢向下看。。。。

3、hashCode

什么是哈希表?
哈希表是通常的数组概念的推广。在一个普通的数组中,最有效的查找一个元素的方法是直接寻址。时间复杂度是O(1)。直接寻址的思想是:如果存储空间允许,我么可以为每个关键字分配一个位置,每个元素的存储地址就是关键字对应的数组下标。
了解一下什么是hashCode,以下是官方对hashCode的定义:

hashcode方法返回该对象的哈希码值。支持该方法是为哈希表提供一些优点,例如,java.util.Hashtable 提供的哈希表。

hashCode 的常规协定是:

在 Java 应用程序执行期间,在同一对象上多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是对象上 equals 比较中所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
如果根据 equals(Object) 方法,两个对象是相等的,那么在两个对象中的每个对象上调用 hashCode 方法都必须生成相同的整数结果。
以下情况不 是必需的:如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么在两个对象中的任一对象上调用 hashCode 方法必定会生成不同的整数结果。但是,程序员应该知道,为不相等的对象生成不同整数结果可以提高哈希表的性能。
实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。)
当equals方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码

hashCode在Object中的定义:

public native int hashCode();

可以看出是基类Object中的 实例native方法,因此对所有继承于Object的类都会有该方法。
我们可以在开头的代码中加上:

        System.out.println(str.hashCode());
        System.out.println(str0.hashCode());
        System.out.println(str1.hashCode());
        System.out.println(str2.hashCode());

输出结果:

3168
3168
3168
3168

我们和equals进行对比可以发现:
都是Object里面的方法
如果 x.equals(y) 返回 “true”,那么 x 和 y 的 hashCode() 必须相等
如果 x.equals(y) 返回 “false”,那么 x 和 y 的 hashCode() 有可能相等,也有可能不等 ;
如果 x 和 y 的 hashCode() 不相等,那么 x.equals(y) 一定返回 “false”
一般来讲,equals 这个方法是给用户调用的,而 hashcode 方法一般用户不会去调用
当一个对象类型作为集合对象的元素时,那么这个对象应该拥有自己的equals()和hashCode()设计,而且要遵守前面所说的几个原则。

4、如何重写呢?

举栗子:
首先新建一个javabean


public class Bean {
     private int age;
        private String name;
        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 Bean() {
            super();
        }
        public Bean(int age, String name) {
            super();
            this.age = age;
            this.name = name;
        }
        @Override
        public boolean equals(Object obj) {
            if (this == obj)//如果是同一个对象 直接返回true
                return true;
            if (obj == null)//如果比较的为null 直接返回false
                return false;
            if (getClass() != obj.getClass())//两者类类型不一样直接返回false
                return false;
            Bean other = (Bean) obj;//类型转换
            if (age != other.age)//判断数据是否相等
                return false;
            if (name == null) {//对于对象内的对象比较 也要注意避免出现空指针异常
                if (other.name != null)
                    return false;
            } else if (!name.equals(other.name))//不会出现空指针异常 因为前面判断了
                return false;
            return true;
        }
}
    public static void main(String[] args) {

                Bean pn1 = new Bean(1,"test");
                Bean pn2 = new Bean(1,"test");
                Map testMap = new HashMap();
                testMap.put(pn1, "1");
                System.out.println(testMap.get(pn2));//null
}

我们会发现为null,经常用HasMap的会问到,因为没有put这个pn2,但是是为什么呢?
因为在java的哈希系列存储结构中,get(Object obj)方法首先会根据obj.hashCode()找到obj这个对象在这个哈希存储结构中的存储位置,然后比较obj和这个存储位置上的元素是否相等,这样就增加了效率,如果hashCode不一样,说明这个key值和obj不对应(毕竟存储位置都不一样,对象当然不一样);如果hashCode一样,然后才调用equals方法,判断这个位置存储的哪个元素是自己要寻找的,或者这个位置存储的所有元素都不是要找的obj。所以当我们没有重写hashCode方法的时候,虽然get(Object obj)中的obj和需要判断的key对象是相等的,但是hashCode不一样,于是便不能找到obj这个key的位置,当然也就找不到对应的value。
我们应该在重写hashcode方法:

    @Override
    public int hashCode() {
        final int prime = 31;//为什么是31?因为这个数需要是质数 31是经验验证的一个能够很好地减小哈希碰撞的质数
        int result = 1;
        result = prime * result + age;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

重写之后再试一下,你会发现结果不在是null了。

你可能感兴趣的:(Java——equals和==,HashCode的比较如何重写equals方法)