一些关于HashMap的思考

作者: insightcodeyk
出处: https://insightcodeyk.github.io/
声明: 本文采用以下协议进行授权: 自由转载-非商用-非衍生-保持署名|Creative Commons BY-NC-ND 3.0,转载请注明作者及出处。

关于HashMap的详细解析请阅读以下文章

HashMap机制与要点

1 HashMap中的”相等”语义

JDK1.8中HashMap判断两个key是否相等的条件是:

if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))

可以看出首先判断hash值是否相等,再判断内存地址是否相等或者equals方法是否为true。在没有覆盖equals的情况下,第二个条件需要内存地址相同,也就是相同的引用才能判true。多数情况下我们的业务场景需要根据成员变量的值来覆盖equals()方法获得自己定义的”相等”语义。

2 Key和不可变类型

维护Key值得不可变性,也就是说保持(设定)Key类型为不可变类型是非常重要的。在开发过程中常用见的情况是选择主键和常量作为equals方法和hashCode方法的参数。为了减少后来者更改代码可能带来的风险,良好的习惯是对作为参数的成员变量全部设置为final。通过不提供set方法
来保证安全不是推荐的方式。
若Key类型为可变类型在某些情况下会带来的风险就像以下测试代码中展示的:
代码导读:
1.以下class VariedKey是Key的类
2.public类是HashMapTest,以下只展示main方法,HashMapTest类中只有main方法
3.重点部分解释在main方法的代码注释中

    /*  
        这是Key对象的类,其中覆盖了hashCode()和equals(),用两个成员变量作为参数。其中constantKey作为final变量,而variedKey变量可以通过setVariedKey方法进行更改。

    */
    class VariedKey{
    private fianl int constantKey; 
    private int variedKey;

    public VariedKey(int constantKey, int variedKey) {
        this.constantKey = constantKey;
        this.variedKey = variedKey;
    }

    public void setVariedKey(int setKey) {
        variedKey = setKey;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        VariedKey variedKey1 = (VariedKey) o;
        return constantKey == variedKey1.constantKey &&
                variedKey == variedKey1.variedKey;
    }

    @Override
    public int hashCode() {

        return Objects.hash(constantKey, variedKey);
    }
}

main方法:

public static void main(String[] args) {
    //这部分是构建Map
    VariedKey mapElm1 = new VariedKey(1, 2);
    VariedKey mapElm2 = new VariedKey(3, 4);
    VariedKey mapElm3 = new VariedKey(5, 6);
    VariedKey mapElm4 = new VariedKey(7, 8);
    HashMap<VariedKey, String> temMap = new HashMap<>();
    temMap.put(mapElm1, "a");
    temMap.put(mapElm2, "b");
    temMap.put(mapElm3, "c");
    temMap.put(mapElm4, "d");


    Set<VariedKey> keySet = temMap.keySet();
    for(VariedKey e : keySet) {
        System.out.println(e.hashCode() + " : " + temMap.get(e));
    }

    System.out.println();

    mapElm1.setVariedKey(5); //!!!这句更改了第一个元素的variedKey成员变量,变成(1, 5)
    VariedKey mapElem5 = new VariedKey(1, 5);//在这里再put一个成员变量为(1, 5)的元素进Map
    temMap.put(mapElem5, "e");
    System.out.println("SizeMap: " + temMap.size());//Map的值从4变成了5

    //实际上,通过修改该第一个元素的成员变量,我们已经无法再访问到第一个元素放入的value值

    keySet = temMap.keySet();
    System.out.println("SizeKeySet: " + keySet.size());
    System.out.println();
    for(VariedKey e : keySet) {
        System.out.println(e.hashCode() + " : " + temMap.get(e));
    }

    VariedKey mapElm6 = new VariedKey(1, 2);//当我们重新创建一个变量和第一个元素更改前的Key


    System.out.println();
    System.out.println(mapElm6.hashCode() + " : " + temMap.get(mapElm6));//当我们用这个Key去获得value值时,value值为null

    }

结论:对于HashMap的Key元素类型设定一定要保证为不可变类型,最好设置为final以保证后续维护人员清楚这是不可更改的类型。