java HashMap的自定义hashcode/equals操作

vaqhlq81  于 2023-04-04  发布在  Java
关注(0)|答案(3)|浏览(122)

是否有一个HashMap类(或Map接口)的实现,允许我使用交替的hashcode和equals操作...类似于使用Collections.sort(list,comparator)中的Comparator以多种方式对相同类型的集合进行排序。
如果可能的话,我想避免创建一个提供所需hashcode和equals操作的密钥 Package 器。
在我的情况下,其中一个场景,为什么我需要这样的东西:
在我的Web应用程序中,对于每个请求,我加载位置/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个Map需要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 }
rggaifut

rggaifut1#

GNU Trove允许您为TCustomHashMap提供特定的TObjectHashingStrategy和您自己的hash和equals函数。

tp5buhyn

tp5buhyn2#

最初的Java API将equals/hashCode行为限制为类型(类),因为它们当时没有lambda表达式。
为了重用现有的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();
    }
}

或 Package :

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

并为key编写helper构造函数:

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

dfuffjeb3#

有很多非并发的第三方哈希Map,其中一些支持自定义equals和hashCode,就像Mikhail的回答中提到的那样。

这里有一个技巧,允许你使用标准的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

相关问题