理解queals()和hashCode()

一、HashMap中存储自定义对象

        如果要在使用到Hash的集合(HashSet、HashMap...)上用到我们自己创建的实体类,则需要重写hashCode()和queals()方法。否则Hash集合将无法正确区分我们自己创建的实体类对应的对象。

import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<Person, Profession> map = new HashMap<>();
        map.put(new Person("ZanSan"), new Profession("Teacher"));
        map.put(new Person("LiSi"), new Profession("Scientist"));
        Person wangWuInMap = new Person("WangWu");
        System.out.printf("WangWuInMao, hashCode: %d \n", wangWuInMap.hashCode());
        map.put(wangWuInMap, new Profession("Java Developer"));
        System.out.println("map entrys: ");
        map.entrySet()
                .stream()
                .peek(System.out::println)
                .count(); //调用终结操作 stream才会真正执行
        System.out.println("find WangWu: ");
        Person findWangWu = new Person("WangWu");
        System.out.printf("findWangWu, hashCode: %d \n", findWangWu.hashCode());
        Profession wangWu = map.get(findWangWu);
        System.out.println("find Result: " + wangWu);
    }
}

/**
 * Output:
 * WangWuInMao, hashCode: 1956725890  
 * map entrys: 
 * Person{name='WangWu'}=Profession{name='Java Developer'}
 * Person{name='LiSi'}=Profession{name='Scientist'}
 * Person{name='ZanSan'}=Profession{name='Teacher'}
 * find WangWu: 
 * findWangWu, hashCode: 1531448569 
 * find Result: null
 *
 * Process finished with exit code 0
 */

        显然,WangWuInMao和findWangWu的hashCode并不一致,且他们的内存地址不用,使用equals()对比他们是否相等也是会返回false。所有我们要在Person中重写hashCode()和queals()方法。

二、重写hashCode()和queals()

        在Effective Java Programming Language Guide一书中,Joshua Bloch给出了一个生成比较好的hashCode()基本准则。
        1.把一个非零常数,比如17,保存在一个叫result的变量中。
        2.对于对象中的每个重要字段f(即equals()方法会考虑的每个字段),计算出该字段的int类型的哈希码c,具体如表 C-1所示。
        3.组合上面计算出来的哈希码:result = 37 * result + C;。
        4.返回 result 值。
        5.观察得到的 hashCode(),确保相等的实例具有相同的哈希码。

 

        一个适当的equals()方法必须满足以下五个条件。
        1.自反性:对于任意的x,调用 x.equals(x)时应该返回true。
        2.对称性:对于任意的x和y,当且仅当y.equals(x)返回true时,x.equals(y)返回true。
        3.传递性:对于任意的x、y和z,如果x.equals(y) 和 y.equals(z)都返回true,那么x.equals(z) 也应该返回true。
        4.一致性:对于任意的x和y,如果对象中用于相等性比较的信息没有被修改过,那么多次调用 x.equals(y)应该始终返回true或false。
        5.对于任意非空的x,调用x.equals(null)应该返回 false。
        下面是满足这些条件的一系列测试,它可以确定自身与所比较的对象(在这里称为rval)是否相等。
        1.如果rval为null,则两个对象不相等。
        2.如果rval为this对象(即自己和自己比较),则两个对象相等。
        3.如果rval不是this 对象所属的类或子类,则两个对象不相等。
        4.如果以上测试都通过,接下来就需要确定rval 中哪些字段是重要的(并且是一致的),然后对它们进行比较。 

         根据上面的hashCode()和equals()方法的准则,我们为Person生成对应的hashCode()和equals()方法,为了更好的展示,给Person对象添加的id字段。

import java.util.Objects;

public class Person {
    private int id;
    private String name;
    public Person(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public int hashCode() {
        //最简单的实现
        //return name.hashCode() * id;
        int result = 17;
        result = 37 * result + id;
        result = 37 * result + name.hashCode();
        return result;
    }

    @Override
    public boolean equals(Object rval) {
        return rval instanceof Person
                && Objects.equals(id, ((Person) rval).id)
                && Objects.equals(name, ((Person) rval).name);
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
}
import java.util.HashMap;

public class Main {
    public static void main(String[] args) {
        Map<Person, Profession> map = new HashMap<>();
        map.put(new Person(1, "ZanSan"), new Profession("Teacher"));
        map.put(new Person(2, "LiSi"), new Profession("Scientist"));
        Person wangWuInMap = new Person(3, "WangWu");
        System.out.printf("WangWuInMao, hashCode: %d \n", wangWuInMap.hashCode());
        map.put(wangWuInMap, new Profession("Java Developer"));
        System.out.println("map entrys: ");
        map.entrySet()
                .stream()
                .peek(System.out::println)
                .count(); //调用终结操作 stream才会真正执行
        System.out.println("find WangWu: ");
        Person findWangWu = new Person(3, "WangWu");
        System.out.printf("findWangWu, hashCode: %d \n", findWangWu.hashCode());
        Profession wangWu = map.get(findWangWu);
        System.out.println("find Result: " + wangWu);
    }
}

/**
 * Output:
 * WangWuInMao, hashCode: -1711247431
 * map entrys:
 * Person{name='LiSi'}=Profession{name='Scientist'}
 * Person{name='ZanSan'}=Profession{name='Teacher'}
 * Person{name='WangWu'}=Profession{name='Java Developer'}
 * find WangWu:
 * findWangWu, hashCode: -1711247431
 * find Result: Profession{name='Java Developer'}
 */

        经过修改后,能正确的从map中找到内容完全相同的Person对象。
        Person对象中equals()方法没有与equals()方法准则对应的代码是因为:
        1.使用了instanceof进行检查后就不必再检查null了。
        2.与this比较是多余的,只要能正确编写queals()方法,自我比较就不会有问题。

三、使用Objects提供hashCode生成方法 

        通常情况下都不需要我们自己手动实现hashCode的生成方法,而是直接使用

        Objects.hashCode(Object o) //单个字段

        Objects.hash(Object... values) //多个字段

    @Override
    public int hashCode() {
        //最简单的实现
        //return name.hashCode() * id;
        /*int result = 17;
        result = 37 * result + id;
        result = 37 * result + name.hashCode();
        return result;*/
        return Objects.hash(id, name);
    }

四、自定义简单的HashMap

         Java8中HashMap是通过数组+链表+红黑树实现的,这里只是简单的用数组+链表来实现。并且entrySet()方法只是简单的将数据复制到一个HashSet中,对entrySet进行添加和删除并不能直接映射到HashMap上。

import java.util.*;

public class SimpleHashMap<K, V> extends AbstractMap<K, V> {
    //为哈希表的大小分配一个指数,以实现均匀分布
    private int capacity = 997;
    private LinkedList<MapEntry<K, V>>[] table = new LinkedList[capacity];

    @Override
    public V put(K key, V value) {
        int index = Math.abs(key.hashCode() % 997);
        LinkedList<MapEntry<K, V>> mapEntries = table[index];
        if (mapEntries == null) {
            LinkedList<MapEntry<K, V>> newEntries = new LinkedList<>();
            MapEntry<K, V> entry = new MapEntry<>(key, value);
            newEntries.add(entry);
            table[index] = newEntries;
            return value;
        }
        for (MapEntry<K, V> mapEntry : mapEntries) {
            if (Objects.equals(mapEntry.key, key)) {
                V oldValue = mapEntry.getValue();
                mapEntry.setValue(value);
                return oldValue;
            }
        }
        MapEntry<K, V> entry = new MapEntry<>(key, value);
        mapEntries.add(entry);
        return value;
    }

    @Override
    public V get(Object key) {
        int index = Math.abs(key.hashCode() % capacity);
        for (MapEntry<K, V> mapEntry : table[index]) {
            if (key.equals(mapEntry.key)) {
                return mapEntry.value;
            }
        }
        return null;
    }

    @Override
    public Set<Entry<K, V>> entrySet() {
        Set<Entry<K, V>> mapEntries = new HashSet<>();
        for (LinkedList<MapEntry<K, V>> entries : table) {
            if (entries != null && entries.size() > 0)
                mapEntries.addAll(entries);
        }
        return mapEntries;
    }

    class MapEntry<K, V> implements Map.Entry<K, V> {
        private K key;
        private V value;

        MapEntry(K key, V value) {
            this.key = key;
            this.value = value;
        }

        @Override
        public K getKey() {
            return key;
        }

        @Override
        public V getValue() {
            return value;
        }

        @Override
        public V setValue(V value) {
            this.value = value;
            return value;
        }

        @Override
        public String toString() {
            return "MapEntry{" +
                    "key=" + key +
                    ", value=" + value +
                    '}';
        }
    }
}

        使用SimpleHashMap替换HashMap

import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<Person, Profession> map = new SimpleHashMap<>();
        map.put(new Person(1, "ZanSan"), new Profession("Teacher"));
        map.put(new Person(2, "LiSi"), new Profession("Scientist"));
        Person wangWuInMap = new Person(3, "WangWu");
        System.out.printf("WangWuInMao, hashCode: %d \n", wangWuInMap.hashCode());
        map.put(wangWuInMap, new Profession("Java Developer"));
        System.out.println("map entrys: ");
        map.entrySet()
                .stream()
                .peek(System.out::println)
                .count(); //调用终结操作 stream才会真正执行
        System.out.println("find WangWu: ");
        Person findWangWu = new Person(3, "WangWu");
        System.out.printf("findWangWu, hashCode: %d \n", findWangWu.hashCode());
        Profession wangWu = map.get(findWangWu);
        System.out.println("find Result: " + wangWu);
    }
}

/**
 * Output:
 * WangWuInMao, hashCode: -1711247431
 * map entrys:
 * Person{name='LiSi'}=Profession{name='Scientist'}
 * Person{name='ZanSan'}=Profession{name='Teacher'}
 * Person{name='WangWu'}=Profession{name='Java Developer'}
 * find WangWu:
 * findWangWu, hashCode: -1711247431
 * find Result: Profession{name='Java Developer'}
 */

  • 14
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jiabao998

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值