zoukankan      html  css  js  c++  java
  • Java里 equals 和 == 以及 hashcode

    本文探讨的是老掉牙的基础问题,先建个实体类

    package com.demo.tools;
    
    public class User {
    
        private String name;
    
        public User(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }

    测试:

        public static void main(String[] args){
            User a = new User("A");
            User b = new User("A");
            System.out.println(a.equals(b));
        }

    输出 false。明明存的都是A,为啥equal的结果是false呢?不是说【equal比较的是值,==比较的是地址】吗?因为默认创建的类是继承Object的,看一下Object类里面的两个方法:

        /**
         * 返回对象的哈希码值。
         *
         * {@code hashCode}的一般约定为:
         * 在Java应用程序执行期间对同一对象多次调用{@code hashcode}方法时,只要在对象{@code equals}的比较中使用的信息没有被修改,则该方法必须一致地返回相同的整数。
         * 从应用程序的一次执行到同一应用程序的另一次执行,此整数不必保持一致。
         * 
         * 如果调用{@code equals(object)}方法,两个对象相等,那么两个对象调用{@code hashcode}方法必须产生相同的整数结果。
         * 根据{@link java.lang.object equals(java.lang.object)}方法,如果两个对象不相等,则这两个对象调用{@code hashcode}方法都必须产生不同的整数结果。
         *
         * @return  a hash code value for this object.
         * @see     java.lang.Object#equals(java.lang.Object)
         * @see     java.lang.System#identityHashCode
         */
        public native int hashCode();
    
        /**
         * {@code equals}方法在非空对象引用上实现等价关系:
         *
         * 它是自反的:对于任何非空的引用值{@code x},{@code x.equals(x)}应该返回{@code true}。
         * 它是对称的:对于任何非空的引用值{@code x}和{@code y},{@code x.equals(y)}应该返回{@code true};反过来也应该一样{@code y.equals(x)}返回{@code true}。
         * 它是可传递的:对于任何非空的引用值{@code x}、{@code y}和{@code z},如果{@code x.equals(y)}返回{@code true},{@code y.equals(z)}返回{@code true},则{@code x.equals(z)}应返回{@code true}。
         * 它是一致的:对于任何非空的引用值{@code x}和{@code y},多次调用{@code x.equals(y)}应该一致的返回{@code true}或{@code false},前提是在{@code equals}比较的对象信息没有被修改。
         * 对于任何非空引用值{@code x},{@code x.equals(null)}应返回{@code false}。
         *
         *
         * 对于任何非空的引用值{@code x}和{@code y},当且仅当{@code x}和{@code y}引用同一对象,此方法返回{@code true}【此时{@code x==y}的值是{@code true}】。
         *
         * 注意,通常需要重写{@code hashcode}方法,以便保持{@code hashcode}方法的一般约定,它声明:相等的对象必须有相等的哈希代码。
         *
         * @param   obj   the reference object with which to compare.
         * @return  {@code true} if this object is the same as the obj
         *          argument; {@code false} otherwise.
         * @see     #hashCode()
         * @see     java.util.HashMap
         */
        public boolean equals(Object obj) {
            return (this == obj);
        }

    可以看到: 1. 默认的hashcode方法是个本地方法,也就是对象的内存地址。 2. 而equals方法则是直接使用了==操作符,也是比较内存地址。这样看来,equals和hashcode是一样的。

    而根据注释所说: 1. 只要equals返回true,那么hashcode一定相等。 2. 只要equals返回false,那么hashcode一定不相等。3. 重写了equals方法,也要重写hashcode方法,因为相等的对象必须有相同的哈希码。

    尝试一:仅仅重写equals

    package com.demo.tools;
    
    public class User {
    
        public static void main(String[] args){
            User a = new User("A");
            User b = new User("A");
            System.out.println(a.equals(b));
            System.out.println(a == b);
            System.out.println(a.hashCode() + "," + b.hashCode());
        }
    
        private String name;
    
        public User(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public boolean equals(Object obj) {
            User user = (User) obj;
            return this.name.equals(user.name);
        }
    }

    输出:

     可以看到,值比较成功了,可是hashcode不一样,这里的hashcode仍然是内存地址。这就违反了约定。

    尝试二:重写equals和hashcode方法

    package com.demo.tools;
    
    public class User {
    
        public static void main(String[] args){
            User a = new User("A");
            User b = new User("A");
            System.out.println(a.equals(b));
            System.out.println(a == b);
            System.out.println(a.hashCode() + "," + b.hashCode());
        }
    
        private String name;
    
        public User(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public boolean equals(Object obj) {
            User user = (User) obj;
            return this.name.equals(user.name);
        }
    
        @Override
        public int hashCode() {
            return this.name.hashCode();
        }
    }

    输出:

     可以看到,值和hashcode一致了,地址不一样很正常,因为这是两个对象。

    尝试三:hashcode相同,equals就相等吗?

        public static void main(String[] args){
            User a = new User("Aa");
            User b = new User("BB");
            System.out.println(a.equals(b));
            System.out.println(a == b);
            System.out.println(a.hashCode() + "," + b.hashCode());
        }

    输出:

     可以看到,两个不同的字符串,hashcode可能一样。

    源码一:Map里的equals方法和hashcode方法

        public boolean equals(Object o) {
            // 地址一样,返回true
            if (o == this)
                return true;
    
            // 不是Map的子类,返回false
            if (!(o instanceof Map))
                return false;
            Map<?,?> m = (Map<?,?>) o;
            // 大小不同,返回false
            if (m.size() != size())
                return false;
    
            try {
                // 遍历Map内容,一一比较
                Iterator<Map.Entry<K,V>> i = entrySet().iterator();
                while (i.hasNext()) {
                    Map.Entry<K,V> e = i.next();
                    K key = e.getKey();
                    V value = e.getValue();
                    // HashMap里允许存储null值
                    if (value == null) {
                        // 对方不存在此key,返回false
                        if (!(m.get(key)==null && m.containsKey(key)))
                            return false;
                    } else {
                        // 都有此key,但是值不一样,返回false
                        if (!value.equals(m.get(key)))
                            return false;
                    }
                }
            } catch (ClassCastException unused) {
                return false;
            } catch (NullPointerException unused) {
                return false;
            }
    
            return true;
        }

    这里能回答一个问题:Map中的对象是自定义的,要不要重写对象的equals方法?

    看源码我们能知道,默认Map里的equals方法,在内部仅仅调用了value的equals方法,默认比较的是内存地址。在调用Map的equals方法时:

    1. 默认一一比较对象的地址,也就是说,两个Map中每一组比较的对象,都引用同一个地址,那才算两个Map相等。

    2. 如果我们业务规定了相等的判定,那么默认的则不符合实际需求,所以要重写value的equals方法,不然调用Map的equals方法可能不符合预期,既然重写了equals,那么也要重写hashcode了。

        public int hashCode() {
            int h = 0;
            Iterator<Entry<K,V>> i = entrySet().iterator();
            while (i.hasNext())
                h += i.next().hashCode();
            return h;
        }

    不难看出,Map的hashcode方法是把内部元素的hashcode加在一起并返回。

    源码二:List的equals方法和hashcode方法

        public boolean equals(Object o) {
            // 本身比较,返回true
            if (o == this)
                return true;
            // 非List的子类,返回false
            if (!(o instanceof List))
                return false;
    
            ListIterator<E> e1 = listIterator();
            ListIterator<?> e2 = ((List<?>) o).listIterator();
            // 当两个迭代器的hasNext都为true的时候,进行比较
            while (e1.hasNext() && e2.hasNext()) {
                E o1 = e1.next();
                Object o2 = e2.next();
                // 这里很巧妙,利用三目运算符完成值的比较
                if (!(o1==null ? o2==null : o1.equals(o2)))
                    return false;
            }
            // 当任意一方hasNext为true,返回false;当双方都为false,返回true
            return !(e1.hasNext() || e2.hasNext());
        }

    基本逻辑和Map的一样,都是遍历元素进行比较。hashcode也是一样。

        public int hashCode() {
            int hashCode = 1;
            for (E e : this)
                hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
            return hashCode;
        }

    源码三:String的equals方法和hashcode方法

        public boolean equals(Object anObject) {
            // 地址一样,返回true
            if (this == anObject) {
                return true;
            }
            // 类型必须是String
            if (anObject instanceof String) {
                String anotherString = (String)anObject;
                int n = value.length;
                // 比较两个String的长度
                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;
        }

    hashcode

        private final char value[];
        private int hash; // Default to 0
        public int hashCode() {
            int h = hash;
            if (h == 0 && value.length > 0) {
                char val[] = value;
                // 这里并不是把元素的hashcode加在一起,而是把元素的ASCII码加在一起
                for (int i = 0; i < value.length; i++) {
                    h = 31 * h + val[i];
                }
                hash = h;
            }
            return h;
        }

    总结

    不管平时有没有重写过equals和hashcode方法,看了Map和Set的源码就可以知道重写的必要性。因为Set也是基于Map实现的(是一个key不一样,value全一样的Map),所以只用Map举证即可。

        public V put(K key, V value) {
            return putVal(hash(key), key, value, false, true);
        }
    
    
        final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                       boolean evict) {
            Node<K,V>[] tab; Node<K,V> p; int n, i;
            if ((tab = table) == null || (n = tab.length) == 0)
                n = (tab = resize()).length;
            if ((p = tab[i = (n - 1) & hash]) == null)
                tab[i] = newNode(hash, key, value, null);
            else {
                Node<K,V> e; K k;
                if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                    e = p;
                else if (p instanceof TreeNode)
                    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                else {
                    for (int binCount = 0; ; ++binCount) {
                        if ((e = p.next) == null) {
                            p.next = newNode(hash, key, value, null);
                            if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                                treeifyBin(tab, hash);
                            break;
                        }
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            break;
                        p = e;
                    }
                }
                if (e != null) { // existing mapping for key
                    V oldValue = e.value;
                    if (!onlyIfAbsent || oldValue == null)
                        e.value = value;
                    afterNodeAccess(e);
                    return oldValue;
                }
            }
            ++modCount;
            if (++size > threshold)
                resize();
            afterNodeInsertion(evict);
            return null;
        }

    首先值的位置是根据hashcode决定的,然后判断值相等的条件是equals为true,hashcode相等。其它都为不相等,有一种情况就是equals为true,hashcode不相等,如果插入Set集合,就不能达到去重的效果:

    package com.demo.tools;
    
    import java.util.HashSet;
    import java.util.Objects;
    import java.util.Set;
    
    
    public class Main {
    
        public static void main(String[] args) throws Exception {
            Set<Foo> set  = new HashSet<>();
            Foo a = new Foo("A", 12);
            Foo b = new Foo("A", 13);
            set.add(a);
            set.add(b);
            set.forEach(e -> System.out.println(e.name));
            System.out.println(a.equals(b));
            System.out.println(a.hashCode() + "," + b.hashCode());
        }
        
        static class Foo {
            private String name;
            private Integer age;
    
            public Foo(String name, Integer age) {
                this.name = name;
                this.age = age;
            }
    
            @Override
            public boolean equals(Object o) {
                if (this == o) return true;
                if (o == null || getClass() != o.getClass()) return false;
                Foo foo = (Foo) o;
                // 这里我们认为name一样,则相等
                return Objects.equals(name, foo.name);
            }
        }
        
    }

    equals为true,但是hashcode不一样:

    所以,如果重写了equals,一定要重写hashcode,以避免不必要的问题。

    小技巧:IDEA里可以自动生成两个方法

    快捷键:Alt+Insert

     

     

     标准equals和hashcode,值得借鉴

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Foo foo = (Foo) o;
        return Objects.equals(name, foo.name) &&
                Objects.equals(age, foo.age);
    }
    
    @Override
    public int hashCode() {
    
        return Objects.hash(name, age);
    }
  • 相关阅读:
    Cocos2d-x之绘制圆形
    Cocos2d-x之绘制填充矩形
    Cocos2d-x之绘制矩形
    Cocos2d-x之绘图API说明
    cocos2d-x之监听手机的物理按键
    cocos2d-x之加速度传感器
    cocos2d-x之多点触摸事件
    cocos2d-x之事件传递(onTouchBegan的返回值的作用)
    pickle
    配置文件
  • 原文地址:https://www.cnblogs.com/LUA123/p/11599539.html
Copyright © 2011-2022 走看看