Reputation: 528
In Java you can override the equals and hash method of the HashMap's key object to determine how the hash code is generated and when two key objects should be considered equal.
Is there any Map implementation that allows to define the hash and equals on in the Map class by overriding its hash method (and determining key equality by an equals(key1,key2) method that can be overridden)?
Use case:
Ĺets assume we have a Objects of class GeoData, containing the fields: country, region, city. We want access two Maps: map x stores the number of inhabitants of each region and map y the number of inhabitants of each city.
To get both informations for a GeoData object we first need to extract country and region from that object, then create a new Object of class X that defines hash and equals considering country and region and use it as key for map x. Additionally we need to do the same with country, region and city. Create a new Object of class Y and use that get the value from map y.
Wouldn't it be easier if we extend a Map implementation for each of these maps x and y and override a hash(GeoData key) and equals(GeoData key1,GeoData key2) method, which would also avoid to create new key objects for every access?
hash(GeoData key) could then either use country and region in map x, or country,region and city in map y for hash code calculation.
UPDATE:
Correctly marked as duplicate. This answer suggesting apache commons-collections AbstractHashMap is what I was looking for.
Upvotes: 4
Views: 9454
Reputation: 12440
Create a wrapper / decorator class that takes the key object in constructor argument and computes its hashCode
and equals
the way you need. Then use this wrapper class as the key instead of the original key.
Upvotes: 0
Reputation: 5578
You could also create a decorator for a map. It doesn't avoid creating a new class for a key, but it hides it so the map is of type Map<GeoData, Integer>
. Of course if you do things like keySet()
, city will have to be always null since this information is lost (it is not part of the key).
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);
}
}
Upvotes: 2
Reputation: 393781
If I understand you correctly, you want to use the same key type (GeoData
) with different equality criteria in different Map
s.
You can do that if instead of HashMap
you use a TreeMap
and pass a different Comparator<GeoData>
to each TreeMap
constructor (one will compare two GeoData
instances by comparing country,region and city and the other will only compare the country and region).
Upvotes: 3