是否有 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 }
GNU Trove 允许您为特定的 TObjectHashingStrategy 提供您自己的散列和等于 TCustomHashMap 的函数。
初始 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);
}
}
有相当多的非并发第三方哈希映射,其中一些支持自定义 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