木心

毕竟几人真得鹿,不知终日梦为鱼

导航

谈一谈HashMap类--key 为自定义对象,修改对象属性后

1、由一个小案例引出本博文的讨论

public class Demo1 {
    public static void main(String[] args) throws Exception {
        Student s1 = new Student();
        s1.setAge(10);

        int capacity = 2; // HashMap集合的容量
        HashMap<Student, String> stus = new HashMap<>(capacity, 1.0f);
        stus.put(s1, "s1");

        // 遍历HashMap
        Set<Entry<Student, String>> entrySet = stus.entrySet();
        for (Entry<Student, String> entry : entrySet) {
            System.out.println(entry.getKey().getAge() + "-" + entry.getValue());
        }
        System.out.println("== 分割线 ==");

        System.out.println("通过key获取value: " + stus.get(s1));  //s1
        s1.setAge(20);
        System.out.println("修改对象s1的age属性值后,通过key获取value: " + stus.get(s1)); // null
    }

    /**
     * HashMap类(jdk1.7.0_60)中的方法:计算Object的hash值
     */
    final static int hash(Object k) {
        int h = 0;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }
        h ^= k.hashCode();
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }
}
public class Student {
    private String name;
    private int age;
    // getter和setter方法省略

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + age;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Student other = (Student) 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;
    }
}

 

控制台打印结果:

  10-s1
  == 分割线 ==
  通过key获取value: s1
  修改对象s1的age属性值后,通过key获取value: null

分析:

  1)自定义了一个Student类,重写写hashCode()和equals()方法;然后创建了一个HashMap集合,往集合中添加一个元素stus.put(s1, "s1"),其中key为Student类型的对象s1;

  2)然后,查找集合中指定key的value值,即执行代码stus.get(s1),可以获取到value值;

  3)此时,修改对象s1的age属性值,再次执行代码stus.get(s1),就不能获取到value值,这是为什么呢?

 

2、查看HashMap类(jdk1.7.0_60)源码

/**
 * 根据key获取value值
 */
public V get(Object key) {
    if (key == null)
        return getForNullKey();
    Entry<K,V> entry = getEntry(key);

    return null == entry ? null : entry.getValue();
}
/**
 * 根据key获取Entry
 */
final Entry<K,V> getEntry(Object key) {
    if (size == 0) {
        return null;
    }

    int hash = (key == null) ? 0 : hash(key);
    // indexFor(hash, table.length):根据key的hash值定位数组索引
    for (Entry<K,V> e = table[indexFor(hash, table.length)];
         e != null;
         e = e.next) {
        Object k;
        
        // 只有当e.hash == hash 并且 key相同时才能查找成功
        // 那么e.hash == hash?
        // 现在需要回答一个问题:修改Entry<key,value>的key的属性后,Entry的hash属性的值变吗?
        if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
            return e;
    }
    return null;
}

 

3、修改Entry<key,value>的key的属性后,Entry的hash属性的值变吗?

public class Demo1 {
    public static void main(String[] args) throws Exception {
        Student s1 = new Student();
        s1.setAge(10);
        System.out.println("hash(s1) = " + hash(s1));

        int capacity = 2; // HashMap集合的容量
        HashMap<Student, String> stus = new HashMap<>(capacity, 1.0f);
        stus.put(s1, "s1");

        // 遍历HashMap
        Set<Entry<Student, String>> entrySet = stus.entrySet();
        for (Entry<Student, String> entry : entrySet) {
            System.out
                    .println(entry.getKey().getAge() + "-" + entry.getValue() + " entry对象的hash属性的值:" + getHash(entry));
        }
        System.out.println("== 分割线 ==");

        System.out.println("通过key获取value: " + stus.get(s1)); // s1
        s1.setAge(20);
        System.out.println("修改对象s1的age属性值后,hash(s1) = " + hash(s1));
        System.out.println("修改对象s1的age属性值后,通过key获取value: " + stus.get(s1)); // null
        for (Entry<Student, String> entry : entrySet) {
            System.out
                    .println(entry.getKey().getAge() + "-" + entry.getValue() + " entry对象的hash属性的值:" + getHash(entry));
        }
    }

    /**
     * HashMap类(jdk1.7.0_60)中的方法:计算Object的hash值
     */
    final static int hash(Object k) {
        int h = 0;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }
        h ^= k.hashCode();
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

    /**
     * 自定义方法:通过反射获取Entry<?, ?>类型的对象的hash属性值
     */
    public static int getHash(Entry<?, ?> entry) throws Exception {
        Field field;
        field = entry.getClass().getDeclaredField("hash");
        field.setAccessible(true);
        int hash = (int) field.get(entry);
        return hash;
    }
}

 

控制台打印结果:

  hash(s1) = 1201
  10-s1 entry对象的hash属性的值:1201
  == 分割线 ==
  通过key获取value: s1
  修改对象s1的age属性值后,hash(s1) = 1603
  修改对象s1的age属性值后,通过key获取value: null
  20-s1 entry对象的hash属性的值:1201

分析:

  根据控制台打印结果可知:修改Entry<key,value>中key的属性后,Entry的hash属性的值不变。

  

4、上面遇到的问题在put(key, value)也是一样的

public class Demo2 {
    public static void main(String[] args) throws Exception {
        Student s1 = new Student();
        s1.setAge(10);

        int capacity = 2; // HashMap集合的容量
        HashMap<Student, String> stus = new HashMap<>(capacity, 1.0f);
        stus.put(s1, "s1");
        System.out.println("hash(s1)=" + hash(s1) + ", s1在哈希桶中存储的index:" + (hash(s1) % capacity));

        Set<Entry<Student, String>> entrySet = stus.entrySet();
        for (Entry<Student, String> entry : entrySet) {
            System.out.println(entry.getKey().getAge() + "-" + entry.getValue() + " entry对象的hash属性的值:" + getHash(entry));
        }
        System.out.println("== 分割线 ==");

        s1.setAge(20);
        stus.put(s1, "s1");
        System.out.println("hash(s1)=" + hash(s1) + ", 第二次添加s1在哈希桶中存储的index:" + (hash(s1) % capacity));
        for (Entry<Student, String> entry : entrySet) {
            System.out.println(entry.getKey().getAge() + "-" + entry.getValue() + " entry对象的hash属性的值:" + getHash(entry));
        }
    }

    /**
     * HashMap类中的方法:计算Object的hash值
     */
    final static int hash(Object k) {
        int h = 0;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }
        h ^= k.hashCode();
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

    /**
     * 自定义方法:通过反射获取Entry<?, ?>类型的对象的hash属性值
     */
    public static int getHash(Entry<?, ?> entry) throws Exception {
        Field field;
        field = entry.getClass().getDeclaredField("hash");
        field.setAccessible(true);
        int hash = (int) field.get(entry);
        return hash;
    }
}

 

控制台打印结果:

  hash(s1)=1201, s1在哈希桶中存储的index:1
  10-s1 entry对象的hash属性的值:1201
  == 分割线 ==
  hash(s1)=1603, 第二次添加s1在哈希桶中存储的index:1
  20-s1 entry对象的hash属性的值:1603
  20-s1 entry对象的hash属性的值:1201

查看HashMap类(jdk1.7.0_60) put(key,value) 方法的源码

public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

 

============================================================================

  需要修改 s1 对象的属性,先从 HashMap 中删除,在重写放入

public static void main(String[] args) throws Exception {
    Student s1 = new Student();
    s1.setAge(10);

    int capacity = 2; // HashMap集合的容量
    HashMap<Student, String> stus = new HashMap<>(capacity, 1.0f);
    stus.put(s1, "s1");

    // 遍历HashMap
    Set<Map.Entry<Student, String>> entrySet = stus.entrySet();
    for (Map.Entry<Student, String> entry : entrySet) {
        System.out.println(entry.getKey().getAge() + "-" + entry.getValue());
    }
    System.out.println("== 分割线 ==");

    System.out.println("通过key获取value: " + stus.get(s1));  //s1

    // 需要修改 s1 对象的属性,先从 HashMap 中删除,在重写放入
    stus.remove(s1);
    s1.setAge(20);
    stus.put(s1, "s1");

    System.out.println("修改对象s1的age属性值后,通过key获取value: " + stus.get(s1)); // s1

    // 遍历HashMap
    entrySet = stus.entrySet();
    for (Map.Entry<Student, String> entry : entrySet) {
        System.out.println(entry.getKey().getAge() + "-" + entry.getValue());
    }

}

---

posted on 2019-03-09 17:48  wenbin_ouyang  阅读(932)  评论(0编辑  收藏  举报