==和equals()、hashcode()+hashmap、hashset相关

== 和 equals() 的区别

·== 对于基本类型和引用类型的作用效果是不同的:

  对于基本数据类型来说,== 比较的是值。

  对于引用数据类型来说,== 比较的是对象的内存地址。

  因为 Java 只有值传递,所以,对于 == 来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是引用类型变量存的值是对象的地址。

·equals() 不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等(所有整型包装类型对象之间值的比较都用equals())。equals()方法存在于Object类中,而Object类是所有类的直接或间接父类,因此所有的类都有equals()方法。

equals() 方法存在两种使用情况:
  1.类没有重写 equals()方法 :通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是 Object类equals()方法。
  2.类重写了 equals()方法 :一般我们都重写equals()方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。

String 中的 equals 方法是被重写过的,因为 Objectequals 方法是比较对象的内存地址,而 Stringequals 方法比较的是对象的值

为什么重写 equals() 时必须重写 hashCode() 方法?

按两种情况:

第一种,不会在HashSet, Hashtable, HashMap等等这些本质是散列表的数据结构中,用到该类:这种情况下,equals() 用来比较该类的两个对象是否相等。而hashCode() 则根本没有任何作用。

第二种,我们会在HashSet, Hashtable, HashMap等等这些本质是散列表的数据结构中,用到该类:

  由图可知:相同对象必然导致相同哈希值;不同哈希值必然是由不同对象导致

  hashCode方法实际上必须要完成的一件事情就是,为该equals方法认定为相同的对象返回相同的哈希值

  Object类中的equals方法区分两个对象的做法是比较地址值,即使用“==”。而我们如若根据业务需求改写了equals方法的实现,那么也应当同时改写hashCode方法的实现。否则hashCode方法依然返回的是依据Object类中的依据地址值得到的integer哈希值。

  带入String类的例子:

复制代码
public class StringDemo {
    public static void main(String[] args) {
        String str1 = "www.jpc.com";
        String str2 = new String("www.jpc.com");
        System.out.println(str1.equals(str2));    //结果为true

        HashMap<String,Integer> map=new HashMap<>();
        map.put(str1, 111);
        map.put(str2, 222);
        map.get(str1); // 222
    }
}
复制代码
复制代码
//String类重写equals()
public boolean equals(Object anObject) {
    if (this == anObject) {//比较地址
        return true;
    }
    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;
}
复制代码

  String对象在调用equals方法比较另一个对象时,会有两种情况返回true:(1)地址相同(2)长度和每个字符都相等

  如果不重写hashCode(),会发生:对于两个字符串对象,使用他们各自的地址值映射为哈希值。被因为它们的地址值不同,String类中的equals方法认定为相等的两个对象拥有两个不同的哈希值

  所以有啥问题呢?对开发到底有啥影响呢?为什么要保证“equals方法认定为相同的两个对象拥有相同的哈希值”这个hashCode()重写原则?

复制代码
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    // table未初始化或者长度为0,进行扩容
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // (n - 1) & hash 确定元素存放在哪个桶中,桶为空,新生成结点放入桶中(此时,这个结点是放在数组中)
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    // 桶中已经存在元素(处理hash冲突)
    else {
        Node<K,V> e; K k;
        // 判断table[i]中的元素是否与插入的key一样,若相同那就直接使用插入的值p替换掉旧的值e。
        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);
                    // 结点数量达到阈值(默认为 8 ),执行 treeifyBin 方法
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                // 判断链表中结点的key值与插入的元素的key值是否相等
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                // 用于遍历桶中的链表,与前面的e = p.next组合,可以遍历链表
                p = e;
            }
        }
        // 表示在桶中找到key值、hash值与插入元素相等的结点
        if (e != null) {
            // 记录e的value
            V oldValue = e.value;
            // onlyIfAbsent为false或者旧值为null
            if (!onlyIfAbsent || oldValue == null)
                //用新值替换旧值
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
}
复制代码

  putVal方法中,pink代码处,为了证明两个对象是同一对象,我们要求(二者哈希值相等)且(二者地址值相等或调用equals认定相等),如果不重写hashCode方法,则str1和str2可以满足equals相等,但是哈希值是不同的,则查不到str1已存在,p节点始终为null,此时就会创建新的节点,就相当于执行了map.put("str1","111"),map.put("str2","222"),而不是用map.put("str1","222")替换map.put("str1","111")。这样map.get(str1)得到的还是111,HashMap就乱套了。

复制代码
//String类中重写hashCode()
public int hashCode() {
    int h = hash; //默认0
    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;
}
复制代码

 

在类中equals()和hashCode()重写思路:

  equals()方法重写的思路:先判断二者的地址是否一样,若一样,则为同一对象,必然相同;再判断是否是相同类或其子类,如果不是那直接判false;再判断长度或具体的属性值是否相同;

@Override
public boolean equals(Object obj) {
     if (this == obj) return true; // 同一引用
     if (obj == null || getClass() != obj.getClass()) return false; // 空或者类型不同
     Person person = (Person) obj; // 强制转换
     return age == person.age && Objects.equals(name, person.name); // 比较字段
}

  hashcode()方法重写的思路:按照equals( )中比较两个对象是否一致的条件用到的属性来重写hashCode()。

@Override
public int hashCode() {
    int result = 17; // 任意一个非零常数
    result = 31 * result + name.hashCode(); // 字符串类型调用自身hashCode方法
    result = 31 * result + age; // 基本类型直接使用值
    return result;
}
//任何数n*31都可以被jvm优化为(n<<5)-n,移位和减法的操作效率比乘法的操作效率高很多

 

HashSet 如何检查重复?

当你把对象加入HashSet时,HashSet 会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他加入的对象的 hashcode 值作比较,如果没有相符的 hashcodeHashSet 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让加入操作成功。

在 JDK1.8 中,HashSetadd()方法只是简单的调用了HashMapput()方法,并且判断了一下返回值以确保是否有重复元素。直接看一下HashSet中的源码:

// Returns: true if this set did not already contain the specified element
// 返回值:当 set 中没有包含 add 的元素时返回真
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

而在HashMapputVal()方法中也能看到如下说明:

// Returns : previous value, or null if none
// 返回值:如果插入位置没有元素返回null,否则返回上一个元素
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {...}

也就是说,在 JDK1.8 中,实际上无论HashSet中是否已经存在了某元素,HashSet都会直接插入,只是会在add()方法的返回值处告诉我们插入前是否存在相同元素。

posted @   壹索007  阅读(24)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示