2

问题描述

一道来自Java官方twitter的问题。风格很像目前国内各大互联网公司的笔试题。

clipboard.png

public class MapEqualsChallenge {

    public static void main(String[] args) {
        Map<Stark, String> map = new LinkedHashMap<>();

        map.put(new Stark("Arya"), "1");
        map.put(new Stark("Ned"), "2");
        map.put(new Stark("Sansa"), "3");
        map.put(new Stark("Bran"), "4");
        map.put(new Stark("Jaime"), "5");

        map.forEach((key, value) -> System.out.println(value));
    }

    static class Stark {

        String name;

        public Stark(String name) {
            this.name = name;
        }

        @Override
        public boolean equals(Object obj) {
            return ((Stark) obj).name.length() == this.name.length();
        }

        @Override
        public int hashCode() {
            return 4000 << 2 * 2000 / 10000;
        }
    }
}

挺有意义的一道题,绝对是学习equalshashcode的经典案例,为官方社区点个赞。

分析

Java 集合

这是一个有关Java集合的关系图,最初觉得这个好复杂,但是现在再去看看,其实也很简单。

clipboard.png

CollectionMap接口都是我们常用的,这个图有一点问题,JDK的源码中,MapCollection毫无关系

clipboard.png

List:强调数据在集合中的位置。

Set:强调集合中元素不允许重复。

Map:强调集合中的元素根据key来查询。

如果你不明白三者的区别与联系,请看《Head First Java 第二版》557页,书中用图示对三者进行了详细的阐述。

LinkedHashMap

之前研究过HashMapConcurrentHashMap(线程安全的HashMap),这个LinkedHashMap倒是第一次见。

LinkedHashMap继承自HashMap,二者区别不大。

clipboard.png

存放数据,根据key计算哈希值,然后根据哈希值计算该元素应当存储在数组中的位置,如果hash冲突,并且不是一个对象,则用链表处理。

HashMap实现的是单向链表,LinkedHashMap实现的是双向链表。

hashCode

这是源码中计算哈希值的方法,先取对象的哈希值,然后再进行相应的处理。

clipboard.png

所以再去看put过程:key是一个对象,然后LinkedHashMap会调用keyhashCode方法。

map.put(new Stark("Arya"), "1");

我们可能一看这个hashCode方法就吓蒙了,这怎么算啊?其实我们完全没必要去计算这个表达式,这是个常量表达式,所以不管new出来多少个对象,哈希值都是一样的。再去调用LinkedHashMaphash方法,返回值也是一样。

@Override
public int hashCode() {
    return 4000 << 2 * 2000 / 10000;
}

clipboard.png

先不去管到底是双向链表还是单向链表,反正哈希值相同,这些个键值对肯定在一个链表上。

map.put(new Stark("Arya"), "1");

执行第一行代码,插入keyArya对象,value1的节点对象。

clipboard.png

equals

map.put(new Stark("Ned"), "2");

放入第二个元素,再计算哈希值,发现与第一个节点key的哈希值相同。

如果两个对象的哈希值相等,则这两个对象有可能相等;如果两个对象的哈希值不相等,那这两个对象肯定不相等。

哈希值相等,然后用equals方法判断两个对象是否相等。

@Override
public boolean equals(Object obj) {
    return ((Stark) obj).name.length() == this.name.length();
}

重写过的equals是根据对象名字的长度来判等的,如果两个对象名字长度相等,那就认为是同一对象。

AryaNed长度不相等,所以LinkedHashMap认为这是两个对象,再去新建一个Node,指过去。

clipboard.png

注意:这里有一个插入的位置问题,到底是在链表头部插新节点还是在尾部插新节点?据说面试官还考过?

答案是:在Java8之前,是在头部插入节点;在Java8中,是在尾部插入节点。

文章链接:HashMap到底是插入链表头部还是尾部

map.put(new Stark("Sansa"), "3");

又来一个,哈希值相等,名字长度为5,没有一样的key,再插一个新节点。

clipboard.png

覆盖

map.put(new Stark("Bran"), "4");

又来一个,哈希值相等,长度为4,与Arya 1是相等的,直接将原节点的值覆盖。由1修改为4

clipboard.png

map.put(new Stark("Jaime"), "5");

第五个,哈希值相等,长度为5,与Sansa 3相等,覆盖,由3修改为5

clipboard.png

结果

分析了一大堆,切换到IDE中运行,与预期结果一致。

clipboard.png

总结

学然后知不足,教然后知困!

之前觉得自己对HashMap还是听明白的,但当自己去画各种示意图时,发现还有很多细节去需要学习。


张喜硕
2.1k 声望423 粉丝

浅梦辄止,书墨未浓。