1

Can I get a reference to the actual key in a (Concurrent)HashMap (or HashSet), by searching for a key which equals() it? How?

I'm looking for something like getEntry(K key).getKey() that will give me the same reference every time I access the key after the first insert, so I can use this reference instead of the freshly generated key to save memory.

(Clearly one can dedicate a special HashMap<K,K> just for this purpose, but I actually already have a map and was wondering if I can use its keys for this purpose)

Just Me
  • 413
  • 3
  • 9
  • [this](https://stackoverflow.com/a/18380755/9398584) seems to address a similar question (bottom line: you have to use a HashMap. Why? because) – Just Me Nov 30 '20 at 20:50
  • I do not agree that the answer listed in the justification for closing this question answers it, as it does not provide an efficient way to search for the key. The reference I provided hits closer to home (basically, it suggests the answer is no "you can't"), though it refers to Sets (so maybe there is a way to do this with HashMaps?) and does not mention the application to memory saving. Please reconsider opening this question. – Just Me Nov 30 '20 at 20:57
  • There is no built-in way to do this. The best you can do, as described in the description, is to e.g. have a `Map>`. – Louis Wasserman Nov 30 '20 at 20:58
  • Thanks @LouisWasserman. Maybe you can open the question and provide this as an answer? I don't think it's a duplicate of anything else. – Just Me Nov 30 '20 at 21:00
  • @basil The questions were very clearly the same. What did you feel was missing from the answers? – Sotirios Delimanolis Nov 30 '20 at 21:53
  • @SotiriosDelimanolis The [linked Answer](https://stackoverflow.com/a/18380755/642706) is an excellent explanation as to why `Map` does not provide a method to meet the needs of this Question. But this Question asked for a solution rather than an explanation. I bought the arguments of this Question’s author asking to reopen, while also thinking of a potential solution. If I was too rushed in my judgment, let me know and I’ll vote to close again and delete my Answer. – Basil Bourque Nov 30 '20 at 22:04
  • 1
    @BasilBourque The duplicate I used is [this](https://stackoverflow.com/questions/19921288/get-key-object-out-of-a-hashmap-in-java), not the thing they linked (if that's what you were referring to). One of those answers is essentially the same as the one you've provided here. – Sotirios Delimanolis Nov 30 '20 at 22:07
  • Does this answer your question? [Get key object out of a HashMap in Java](https://stackoverflow.com/questions/19921288/get-key-object-out-of-a-hashmap-in-java) – cigien Dec 01 '20 at 00:24
  • @cigien these questions are indeed quite close - but the truth is I didn't have trouble writing a workaround, similar to those suggested (also in the question itself). Instead I was looking for the elegant (efficient, less thread-contested) "built-in" solution: "this is how you get the key using the existing API". And the answer Andreas gave below addresses it well: you cannot (without brute force search). Haven't seen that elsewhere, except a similar discussion about HashSet's linked to above. – Just Me Dec 01 '20 at 07:51

3 Answers3

2

You cannot retrieve the original key from a HashMap, without just doing a brute-force sequential search like this:

K keyToLookup = ...;

K originalKey = map.keySet().stream().filter(keyToLookup::equals).findAny().orElse(null);

Option 1: Embed key in value

Workaround for a HashMap is of course to have the key object as part of the value:

  • By actually having the key object as part of the value object, which is often inherent, e.g. a map of user name to user object. May require modifying the value object, and may require removing and re-adding the map entry when it is updated to refer to a different value object.

  • In a separate Map<K, K>. Less efficient, since you have to look up twice.

  • By changing the value to a key/value pair, e.g. Map<K, Entry<K, V>>. This is likely the best solution, but does require care to ensure that the Entry's key object is always the original key.


Option 2: Use NavigableMap

If the Map can be changed from a HashMap to be a NavigableMap, e.g. a TreeMap, it supports retrieving the original key object from the map, e.g. using the ceilingEntry(K key)​ method.

The key object must implement Comparable or the TreeMap can use a custom Comparator. In either case, the implementation must be consistent with equals.

Not all key types can define a relative ordering, so it may not be possible to use NavigableMap.

K keyToLookup = ...;

Entry<K,​V> entry = map.ceilingEntry​(keyToLookup);
if (entry != null && entry.getKey().equals(keyToLookup)) {
    K originalKey = entry.getKey();
    V value = entry.getValue();
    // code here
} else {
    // key not found
}
Andreas
  • 154,647
  • 11
  • 152
  • 247
0

It is rather strange to want to obtain the "original" of the key ...usually, polling with newly created keys is "cheap" and they can be garbage collected later. I guess you have a fairly exotic use case.

That said, you can't achieve what you want with a HashMap out of the box. The closest you could do is extend it to expose the getNode(...) method, allowing you to get access to the key.

dagnelies
  • 5,203
  • 5
  • 38
  • 56
-1

You seem to be saying that you want a reference to the object held as a key in a map, to be found using another object that happens to be equal per the Object::equals contract.

While I’ve not yet tried this code, I think you can do this by getting a Set of the map’s keys. Then convert to a List. Find your desired object in that list, retrieving a reference from the list’s element.

Map< Car , Integer > map = … ;
Car car = new Car( … ) ;
map.put( car , 42 ) ;

Set< Car > carSet = map.keySet() ;
List< Car > carList = List.copyOf( carSet ) ;
Car similarCar = … ;
int index = carList.indexOf( similarCar ) ;
Car originalCar = carList.get( index ) ;  

boolean same = ( car == originalCar ) ;

You could also make a stream from the set of keys, searching for a match while having the stream halt on first match.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • This is quite inefficient. If you're going to do a linear scan, you might as well just use an `Iterator` from the `Set`, the extra copy and `indexOf` is unnecessary. – Savior Nov 30 '20 at 22:01
  • I think the point of the question is to do it without using a brute-force sequential search. --- *FYI:* That is a lot of code for something that can be done simpler using `map.keySet().stream().filter(similarCar::equals).findAny().orElse(null)` – Andreas Nov 30 '20 at 22:05
  • @Andreas I had the same idea as your comment but was trying to draw out the steps explicitly in the longer non-stream code. – Basil Bourque Nov 30 '20 at 22:11
  • @Andreas Where does the Question ask about efficiency of the search or avoiding sequential search? The only concern I see about efficiency is the desire to save memory by accessing a reference to the original object used as a key. I suspect some of you are reading more into the Question than is there. – Basil Bourque Nov 30 '20 at 22:15
  • @BasilBourque One of the main reasons for using a map is lookup performance. Since the question is *already* considering a `HashMap`, it's obvious that performance is a concern. --- Also, since the stated reason for the question is memory optimization, the map is expected to grow large (otherwise it wouldn't be much of a concern), so sequential search would become a real performance bottleneck. --- I suspect you are ignoring a key aspect of the problem when you basically just answer: Do a brute-force sequential search. – Andreas Nov 30 '20 at 22:44