允许覆盖hash和equals方法的HashMap实现

时间:2017-01-04 10:25:36

标签: java hashmap

在Java中,您可以覆盖HashMap的密钥对象的equals和hash方法,以确定如何生成哈希代码以及何时应将两个关键对象视为相等。

是否有任何Map实现允许通过覆盖其哈希方法(并通过可被覆盖的equals(key1,key2)方法确定键相等性)来定义Map类中的哈希值和等号值?

使用案例

我们假设我们有一个GeoData类的对象,包含字段:country,region,city。我们想要访问两个地图:地图 x 存储每个地区的居民数量并映射 y 数量每个城市的居民。

要获取GeoData对象的两个信息,我们首先需要从该对象中提取国家和地区,然后创建一个新的类X,它定义哈希和等于考虑国家和地区,并将其用作地图的键 X 即可。此外,我们需要对国家,地区和城市做同样的事情。创建一个类Y的新对象,并使用它从地图 y 中获取值。

如果我们为每个地图 x y 并覆盖哈希(GeoData键)和等于(GeoData key1,GeoData key2)方法,这也可以避免为每次访问创建新的密钥对象?

然后,

哈希(GeoData键)可以使用地图 x 中的国家和地区,或地图 y中的国家/地区,城市 用于哈希码计算。

更新

正确标记为重复。 This回答暗示apache commons-collections AbstractHashMap就是我想要的。

3 个答案:

答案 0 :(得分:3)

如果我理解正确,您希望在不同import UIKit class CustomAccessoryView: UIView { @IBAction func clickLoveButton(_ sender: UIButton) { print("Love button clicked") } } s中使用具有不同相等标准的相同密钥类型(GeoData)。

如果您使用Map代替HashMap并将TreeMap传递给每个Comparator<GeoData>构造函数,则可以执行此操作(一个将比较两个TreeMap比较国家,地区和城市的实例,另一个只比较国家和地区)。

答案 1 :(得分:2)

您还可以为地图创建装饰器。它不会避免为键创建新类,但它隐藏了它,因此映射的类型为Map<GeoData, Integer>。当然,如果您执行keySet()之类的操作,则city必须始终为null,因为此信息会丢失(它不是密钥的一部分)。

public class Country {}
public class Region {}
public class City {}

public class GeoData {

    public final Country country;
    public final Region region;
    public final City city;

    public GeoData(Country country, Region region, City city) {
        this.country = country;
        this.region = region;
        this.city = city;
    }
}

public class InhabitantsInRegion  implements Map<GeoData, Integer> {

    private static class InnerKey {

        private final Country country;
        private final Region region;

        private InnerKey(Country country, Region region) {
            this.country = country;
            this.region = region;
        }

        // hashcode & equals
    }

    private final Map<InnerKey, Integer> map = new HashMap<>();

    @Override
    public int size() {
        return map.size();
    }

    @Override
    public boolean isEmpty() {
        return map.isEmpty();
    }

    @Override
    public boolean containsKey(Object key) {
        return key instanceof GeoData && map.containsKey(newInnerKey((GeoData) key));
    }

    @Override
    public boolean containsValue(Object value) {
        return map.containsValue(value);
    }

    @Override
    public Integer get(Object key) {
        if (key instanceof GeoData) {
            return map.get(newInnerKey((GeoData) key));
        }
        return null;
    }

    @Override
    public Integer put(GeoData key, Integer value) {
        return map.put(new InnerKey(key.country, key.region), value);
    }

    @Override
    public void putAll(Map<? extends GeoData, ? extends Integer> m) {
        m.entrySet().forEach(entry -> put(entry.getKey(), entry.getValue()));
    }

    @Override
    public Integer remove(Object key) {
        if (key instanceof GeoData) {
            return map.remove(newInnerKey((GeoData) key));
        }
        return null;
    }

    @Override
    public void clear() {
        map.clear();
    }

    @Override
    public Set<GeoData> keySet() {
        return map.keySet().stream()
                .map(InhabitantsInRegion::newGeoDataKey)
                .collect(toSet());
    }

    @Override
    public Collection<Integer> values() {
        return map.values();
    }

    @Override
    public Set<Entry<GeoData, Integer>> entrySet() {
        return map.entrySet().stream()
                .map(this::newEntry)
                .collect(toSet());
    }



    private Entry<GeoData, Integer> newEntry(Entry<InnerKey, Integer> entry) {
        return new Entry<GeoData, Integer>() {
            @Override
            public GeoData getKey() {
                return newGeoDataKey(entry.getKey());
            }

            @Override
            public Integer getValue() {
                return entry.getValue();
            }

            @Override
            public Integer setValue(Integer value) {
                return map.put(entry.getKey(), value);
            }
        };
    }

    private static InnerKey newInnerKey(GeoData geoDataKey) {
        return new InnerKey(geoDataKey.country, geoDataKey.region);
    }

    private static GeoData newGeoDataKey(InnerKey innerKey) {
        return new GeoData(innerKey.country, innerKey.region, null);
    }

}

答案 2 :(得分:1)

创建一个包装器/装饰器类,它接受构造函数参数中的键对象,并以您需要的方式计算其hashCodeequals。然后使用此包装类作为键而不是原始键。