I don't know how constrained you are regarding memory usage, but if you can use a LinkedHashMap
instead of a HashMap
(LinkedHashMap
uses extra references to keep insertion order), then you could take advantage of its removeEldestEntry
method:
public class HackedMap<K, V> extends LinkedHashMap<K, V> {
K lastKey;
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
lastKey = eldest.getKey();
return false;
}
K getLastKey() {
return lastKey;
}
}
I think the code is self-explanatory. We are keeping a reference to the original key, which we grab from the removeEldestEntry
method's argument.
As to the removeEldestEntry
method's return value, it is false
, so that we don't allow the eldest entry to be removed (after all, we don't want the map to work as a cache).
Now, with a common insertion-order LinkedHashMap
, the removeEldestEntry
method is automatically called by put
and putAll
(from removeEldestEntry
method docs):
This method is invoked by put and putAll after inserting a new entry into the map.
So all we need to do now is to implement your getExistingKey
method in such a way that it calls put
without modifying the map, which you can do as follows:
<K, V> K getExistingKey(HackedMap<K, V> map, K k) {
if (k == null) return null;
V v = map.get(k);
if (v == null) return null;
map.put(k, v);
return map.getLastKey();
}
This works because, when the map already contains an entry mapped to a given key, the put
method replaces the value without touching the key.
I'm not sure about the null checks I've done, maybe you need to improve that. And of course this HackedMap
doesn't support concurrent access, but HashMap
and LinkedHashMap
don't do either.
You can safely use a HackedMap
instead of a HashMap
. This is the test code:
Key k1 = new Key(10, "KEY 1");
Key k2 = new Key(10, "KEY 2");
Key k3 = new Key(10, "KEY 3");
HackedMap<Key, String> myMap = new HackedMap<>();
System.out.println(k1.equals(k2)); // true
System.out.println(k1.equals(k3)); // true
System.out.println(k2.equals(k3)); // true
System.out.println(k1.hashCode() == k2.hashCode()); // true
System.out.println(k1.hashCode() == k3.hashCode()); // true
System.out.println(k2.hashCode() == k3.hashCode()); // true
System.out.println(k1 == k2); // false
System.out.println(k1 == k3); // false
System.out.println(k2 == k3); // false
myMap.put(k1, "k1 value");
System.out.println(myMap); // {Key{k=10, d='KEY 1'}=k1 value}
myMap.put(k3, "k3 value"); // Key k1 (the one with its field d='KEY 1') remains in
// the map but value is now 'k3 value' instead of 'k1 value'
System.out.println(myMap); // {Key{k=10, d='KEY 1'}=k3 value}
Key existingKey = getExistingKey(myMap, k2);
System.out.println(existingKey == k1); // true
System.out.println(existingKey == k2); // false
System.out.println(existingKey == k3); // false
// Just to be sure
System.out.println(myMap); // {Key{k=10, d='KEY 1'}=k3 value}
Here's the Key
class I've used:
public class Key {
private final int k;
private final String d;
Key(int k, String d) {
this.k = k;
this.d = d;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Key key1 = (Key) o;
return k == key1.k;
}
@Override
public int hashCode() {
return Objects.hash(k);
}
@Override
public String toString() {
return "Key{" +
"k=" + k +
", d='" + d + '\'' +
'}';
}
}