Stephen Lee
Stephen Lee

Reputation: 23

HashMap getting key given that I already have the key

This sounds like a really weird request but I have a HashMap of a custom class. I've overridden the equals and hashCode methods to only focus on certain fields, so that I can pull a key if it equals a new key with the same certain fields. In that case, I want to replace the other fields with some new values. The structure is like so:

public class ExampleClass() {
   int field1;
   int field2;

   <insert constructor here with field1 and field2>

   @Override
   public boolean equals(Object obj) { // Only return true if field1 is equal
      ...
      return (this.field1 == obj.field1);
   }
}

So I use it like this:

HashMap<ExampleClass, int> hmap = new HashMap<>();
while(true) {
   ...
   ExampleClass oldObject = new ExampleClass(1, 2);
   ExampleClass newObject = new ExampleClass(1, 5);

   hmap.put(oldObject, 10);
   if(hmap.contains(newObject)) {
      // Get field1 of old object and change it
   }
}

This was a bad example but I just want to be able to retrieve the key object of a key-value pair in a HashMap given that I have the key so that I can modify the key. How would I do so?

Edit: My hashcode function.

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + 
            ((this.srcVertex.getVertexData().getID() == null) ? 0 : this.srcVertex.getVertexData().getID().hashCode());
    result = prime * result + 
            ((this.targetVertex.getVertexData().getID() == null) ? 0 : this.targetVertex.getVertexData().getID().hashCode());
    return result;
  }

Upvotes: 0

Views: 59

Answers (3)

kaya3
kaya3

Reputation: 51037

As I understand your question, you have a key object in the HashMap, and you want to use an "equal" key object to retrieve the key rather than the value it's associated with. There is no method on a HashMap to do that, and it somewhat violates the idea of two objects being "equal" if you do actually care which of the two equal objects you get.

I think it would make more sense to do this in a different way:

  • Write a new class ExampleKey with just the fields that you want to use in the equals/hashCode methods for the purposes of the HashMap. This class must override equals and hashCode using those fields, and it should be immutable (the behaviour is undefined if a key's hash can change while it's in the HashMap).
  • Give ExampleClass a getKey() method which returns an ExampleKey object for the current object. It is probably simpler to use composition here, so that ExampleClass doesn't duplicate those fields.
  • Now have two HashMaps: a Map<ExampleKey, Integer> for the actual mapping that you want to store, and a separate Map<ExampleKey, ExampleClass> storing the object which would otherwise have been used as the key in the other HashMap.

Example usage:

Map<ExampleKey, Integer> actualMapping = new HashMap<>();
Map<ExampleKey, ExampleClass> objsUsed = new HashMap<>();
while(true) {
   // ...
   ExampleClass oldObject = new ExampleClass(1, 2);
   ExampleClass newObject = new ExampleClass(1, 5);

   // always update both maps together, to ensure valid state
   actualMapping.put(oldObject.getKey(), 10);
   objsUsed.put(oldObject.getKey(), oldObject);

   // ...

   ExampleClass objUsed = objsUsed.get(newObject.getKey());
   if(objUsed != null) {
        // objUsed == oldObject here
   }
}

If you don't care about the philosophy of what "equal" is supposed to mean, then you can apply this same solution without the ExampleKey class or the getKey method; just use the objects themselves, i.e. objsUsed would be of type Map<ExampleClass, ExampleClass> and it would always map an object to itself. But I think if you do that, readers of your code will be scratching their heads wondering why you are mapping objects to themselves.

Upvotes: 1

Olivier
Olivier

Reputation: 18027

You forgot to define the hashCode() method. Without it, storing an object as a key in a HashMap doesn't work.

UPDATE:

If srcVertex is field1 and targetVertex is field2, then your hashCode() method is incorrect. If equals() compares srcVertex, then hashCode() should use only srcVertex, not targetVertex.

The rule is: if 2 objects are equal, then their hash codes must be equal.

Upvotes: 0

Tom Hawtin - tackline
Tom Hawtin - tackline

Reputation: 147124

Maps in Java should be keyed on values that have equals defined over all their 'essential properties'. I believe most collection libraries work like this, with the only example that springs to mind is that of General Magic's Telescript.

So, have a Map defined on a type of only those properties. The field1 int (Integer) in this case. Put the rest of the information in the map entry value. This may well be a new class.

Map<Integer, ValueClass> map;

where

public final class ValueClass {
    private int someValue;
    private ExampleClass exmaple;
    ...

If you are insistent you want to find the key, which I suggest you don't. There's various ways of doing it, something like:

Optional<ExampleClass> found = map.keySet().stream()
    .firstThat(k -> k.field1() == target);
found.ifPresent(key -> {
   Integer value = hmap.remove(key);
   // update key.
   hmap.put(key, value);
});

Or the old school version (looks better to me, but not so cool):

for (ExampleClass key : map.keySet()) {
    if (key.field1() == target) {
        Integer value = hmap.remove(key);
        // update key.
        hmap.put(key, value);
    }
}

Using an Iterator or possibly over an ihe entry set is better in that it avoids the second of three lookups, but I'll leave that as an exercise.

Upvotes: 0

Related Questions