HashMap 的自定义哈希码/等于操作

问题描述 投票:0回答:3

是否有 HashMap 类(或 Map 接口)的实现允许我使用备用哈希码和等于操作... 类似于如何使用 Collections.sort(list, comparator) 中的 Comparator 以多种方式对相同类型的集合进行排序。

如果可能的话,我想避免创建一个提供所需哈希码和等于操作的密钥包装器。


就我而言,我需要这样的场景之一:

在我的网络应用程序中,对于每个请求,我加载位置/ISP 和其他数据。 在代码的不同部分(在我的服务和存储库层中),我根据其要求“最小化”了缓存。

这里是一个简化的代码示例:

class GeoIpData{
    private String countryName;
    private String state;
    private String city;
    private String isp;
    @Override
    public int hashCode() {
        //countryName hashCode
        //state hashCode
        //city hashCode
        //isp hashCode
    }
    @Override
    public boolean equals(Object obj) {
        // compare countryName
        // compare state
        // compare city
        // compare isp
    }
}

 Map<GeoIpData,#Type1> fullCache = ... //This cache needs to be unique per countryName,state,city and isp
 Map<GeoIpData,#Type2> countryCache = ... //This cache needs to be unique per countryName
 Map<GeoIpData,#Type2> ispCache = ... //This cache needs to be unique per countryName,isp

要实现这一点,上述 3 个映射需要 3 个不同的 hashcode 和 equals 方法。

fullCache:
hashCode -> GeoIpData.hashCode();
equals   -> GeoIpData.equals(Object obj);

countryCache:
hashCode -> {countryName hashCode }
equals   -> {compare countryName }

ispCache:
hashCode -> {countryName hashCode & isp hashCode }
equals   -> {compare countryName & compare isp hashCode }
java hashmap equals hashcode overriding
3个回答
8
投票

GNU Trove 允许您为特定的 TObjectHashingStrategy 提供您自己的散列和等于 TCustomHashMap 的函数。


1
投票

初始 Java API 限制了

equals
/
hashCode
行为类型(类),因为那时他们没有 lambdas。

为了重用现有的 API / 实现 - 制作临时密钥:

public CountryKey {
    private String country;
    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof CountryKey)) { return false; }
        CountryKey that = (CountryKey) obj;
        return this.country.equals(that.country);
    }
    @Override int hashCode() {
         return country.hashCode();
    }
}

或包装纸:

public CountryKey {
    private GeoIpData holder;
    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof CountryKey)) { return false; }
        CountryKey that = (CountryKey) obj;
        return this.holder.getCountry().equals(that.holder.getCountry());
    }
    @Override int hashCode() {
         return holder.getCountry().hashCode();
    }
}

并为键编写辅助构造函数:

public class GeoIpData {
     public CountryKey buildCountryKey() {
          return new CountryKey(this.country);
     }
}

0
投票

有相当多的非并发第三方哈希映射,其中一些支持自定义 equals 和 hashCode,如 Mikhail 的回答中提到的那个。我知道没有并发实现。

这里有一些 hack,允许您将标准 ConcurrentHashMap 与自定义 equals/hashCode 实现一起使用。它使用线程局部变量来传达在任何给定时间点应该使用哪些 equals/hashCode 实现键。

首先是一个装饰器,用于将功能添加到任何

ConcurrentMap
实现:

public class CustomEqualsAndHashCodeConcurrentMapDecorator<K, V>
        implements ConcurrentMap<K, V> {

    public interface EqualsAndHashCode<X> {
        boolean equals(X x, X y);
        int hashCode(X x);
    }
    private final ConcurrentMap<K, V> decoratee;
    private final EqualsAndHashCode<K> equalsAndHashCode;
    private final Consumer<EqualsAndHashCode<K>> equalsAndHashCodeSetter;

    public CustomEqualsAndHashCodeConcurrentMapDecorator(ConcurrentMap<K, V> decoratee, EqualsAndHashCode<K> equalsAndHashCode, Consumer<EqualsAndHashCode<K>> equalsAndHashCodeSetter) {
        this.decoratee = decoratee;
        this.equalsAndHashCode = equalsAndHashCode;
        this.equalsAndHashCodeSetter = equalsAndHashCodeSetter;
    }

    @Override
    public V get(Object key) {
        equalsAndHashCodeSetter.accept(equalsAndHashCode);
        final V ret = decoratee.get(key);
        equalsAndHashCodeSetter.accept(null);
        return ret;
    }

    public Set<K> keySet() {
// Note that this is potentially dangerous, as the caller will have to make sure
// equalsAndHashCodeSetter.accept() is called before/after calling some of the methods of the returned set
        return decoratee.keySet();
    }

    public Set<Entry<K, V>> entrySet() {
// Note that this is potentially dangerous, as the caller will have to make sure
// equalsAndHashCodeSetter.accept() is called before/after calling some of the methods of the returned set
        return decoratee.entrySet();
    }

    // omitting the other methods, most of which need to be
    // wrapped in equalsAndHashCodeSetter.accept() calls as well
}

这里是一个关于如何使用相同键对象实例的示例 a) 使用默认的 equals/hashCode,以及 b) 使用自定义的:

class CustomEqualsAndHashCodeConcurrentMapDecoratorTest {

    private static abstract class DynamicEqualsAndHashCodeObject<K> {
        @SuppressWarnings("rawtypes")
        private static final ThreadLocal<CustomEqualsAndHashCodeConcurrentMapDecorator.EqualsAndHashCode> equalsAndHashCode = new ThreadLocal<>();

        static void setEqualsAndHashCode(@SuppressWarnings("rawtypes") CustomEqualsAndHashCodeConcurrentMapDecorator.EqualsAndHashCode equalsAndHashCode) {
            DynamicEqualsAndHashCodeObject.equalsAndHashCode.set(equalsAndHashCode);
        }

        private CustomEqualsAndHashCodeConcurrentMapDecorator.EqualsAndHashCode<K> getComparator() {
            //noinspection rawtypes
            final CustomEqualsAndHashCodeConcurrentMapDecorator.EqualsAndHashCode raw = DynamicEqualsAndHashCodeObject.equalsAndHashCode.get();
            //noinspection unchecked
            return raw == null ? null : (CustomEqualsAndHashCodeConcurrentMapDecorator.EqualsAndHashCode<K>) DynamicEqualsAndHashCodeObject.equalsAndHashCode.get();
        }

        @Override
        public boolean equals(Object obj) {
            var equalsAndHashCode = getComparator();
            //noinspection unchecked
            return equalsAndHashCode != null ? equalsAndHashCode.equals(dis(), (K) obj) : super.equals(obj);
        }

        @Override
        public int hashCode() {
            var equalsAndHashCode = getComparator();
            return equalsAndHashCode != null ? equalsAndHashCode.hashCode(dis()) : super.hashCode();
        }

        abstract K dis();
    }

    static class Key extends DynamicEqualsAndHashCodeObject<Key> {
        final String a;
        final String b;

        public Key(String a, String b) {
            this.a = a;
            this.b = b;
        }

        @Override
        public String toString() {
            return a + b;
        }

        @Override
        Key dis() {
            return this;
        }

        static class AEqualsAndHashCode implements CustomEqualsAndHashCodeConcurrentMapDecorator.EqualsAndHashCode<Key> {
            public boolean equals(Key x, Key y) {return Objects.equals(x.a, y.a);}
            public int hashCode(Key key) {return Objects.hashCode(key.a);}
        }
    }

    @Test
    void test() {
        var key11 = new Key("1", "1");
        var key12 = new Key("1", "2");
        var key21 = new Key("2", "1");
        var key22 = new Key("2", "2");
        var mapDefault = new ConcurrentHashMap<Key, String>();
        mapDefault.put(key11, key11.toString());
        mapDefault.put(key12, key12.toString());
        mapDefault.put(key21, key21.toString());
        mapDefault.put(key22, key22.toString());
        mapDefault.forEach((k, v) -> System.out.println("mapDefault: " + k + " -> " + v));
        var mapA = new CustomEqualsAndHashCodeConcurrentMapDecorator<>(new ConcurrentHashMap<Key, String>(), new Key.AEqualsAndHashCode(), Key::setEqualsAndHashCode);
        mapA.put(key11, key11.toString());
        mapA.put(key12, key12.toString());
        mapA.put(key21, key21.toString());
        mapA.put(key22, key22.toString());
        mapA.forEach((k, v) -> System.out.println("mapA: " + k + " -> " + v));
    }
}

输出是这样的:

mapDefault: 12 -> 12
mapDefault: 11 -> 11
mapDefault: 21 -> 21
mapDefault: 22 -> 22
mapA: 11 -> 12
mapA: 21 -> 22
© www.soinside.com 2019 - 2024. All rights reserved.