允许覆盖哈希和相等方法的HashMap实现

4
在Java中,您可以重写HashMap的键对象的equals和hash方法,以确定生成哈希码的方式,以及何时应将两个键对象视为相等。
是否有任何Map实现允许通过覆盖其哈希方法(并通过可以重写的equals(key1,key2)方法确定键相等性),在Map类中定义哈希和相等性?
用例:
假设我们有一个GeoData类的对象,包含字段:country,region,city。我们想要访问两个地图:map x存储每个地区的居民数量,map y存储每个城市的居民数量。
为了获取GeoData对象的这两种信息,我们首先需要从该对象中提取国家和地区,然后创建一个新的X类对象,该对象定义了考虑国家和地区的哈希和相等性,并将其用作map x的键。此外,我们还需要使用国家、地区和城市来创建一个新的Y类对象,并使用它来获取map y的值。

如果我们为这些地图的每一个实现扩展一个Map,同时重写hash(GeoData key)和equals(GeoData key1, GeoData key2)方法,这样是否更容易,也可以避免为每次访问创建新的键对象?

hash(GeoData key)可以使用mapx中的国家和地区,或者使用mapy中的国家、地区和城市进行哈希码计算。

更新:

已正确标记为重复。这个 回答建议使用Apache Commons-collections AbstractHashMap。

3个回答

3
如果我理解您的意思正确,您想在不同的Map中使用相同的键类型(GeoData),但具有不同的相等性标准。
如果您使用TreeMap而不是HashMap,并向每个TreeMap构造函数传递不同的Comparator,则可以实现此目的(一个将通过比较国家,地区和城市来比较两个GeoData实例,另一个仅比较国家和地区)。 请注意保留HTML标记。

2

您也可以为地图创建一个装饰器。它不能避免为键创建一个新类,但是它会隐藏它,因此地图的类型为Map<GeoData,Integer>。当然,如果您执行keySet()等操作,城市将始终为空,因为此信息已丢失(它不是键的一部分)。

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);
    }

}

0
创建一个包装器/装饰器类,它以构造函数参数中的键对象为输入,并计算其hashCodeequals。然后使用这个包装器类作为键,而不是原始键。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接