Пользовательский хэш-код / равнозначная операция для HashMap
Существует ли реализация класса HashMap (или интерфейса Map), которая позволит мне использовать альтернативный хэш-код и операции равенства... По аналогии с тем, как коллекции одного и того же типа могут быть отсортированы несколькими способами с помощью Comparator в Collections.sort (список, компаратор).
Я хотел бы по возможности избежать создания оболочки ключей, обеспечивающей желаемый хэш-код и операции равенства.
В моем случае один из сценариев, почему мне нужно что-то вроде этого:
В моем веб-приложении для каждого запроса я загружаю Местоположение / Интернет-провайдера и другие данные. В разных частях кода (на уровне сервисов и репозитория) я "минимизировал" кеши, соответствующие его требованиям.
Вот упрощенный пример кода:
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 различных метода хэш-кода и метода равно.
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 }
3 ответа
GNU Trove позволяет вам предоставлять конкретную TObjectHashingStrategy с вашим собственным хешем и равнозначными функциями для TCustomHashMap.
Начальный Java API ограничен equals
/ hashCode
поведение типа (класса), потому что в те дни у них не было лямбда-выражений.
Чтобы повторно использовать существующие 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);
}
}
Существует довольно много непараллельных сторонних хэш-карт, некоторые из которых поддерживают пользовательские равенства и хеш-код, как тот, который упомянут в ответе Михаила. Однако я не знаю ни одной параллельной реализации.
Вот что-то вроде хака, который позволяет вам использовать стандартный ConcurrentHashMap с собственными реализациями Equals/HashCode. Он использует локальную переменную потока для передачи того, какие ключи реализации Equals/hashCode следует использовать в любой заданный момент времени.
Сначала декоратор, добавляющий функциональность любому
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
}
Вот пример того, как использовать одни и те же экземпляры ключевых объектов а) со значением по умолчанию равного/хэш-кода и б) с пользовательским:
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