Create a unique key for Map from multiple Enum types

1.6k views Asked by At

I have a requirement where multiple enums will need to find a value in the i.e. Map<Key,Value> (combination of enums will return a unique value). I think there are many options like having a wrapper Object say Key which will act as a Key. Also, we can use Guava Table if keys are limited to two (not sure).

Wanted to check for below approach where two enums will map to a unique computed value, need suggestions for understanding -
i) If this approach is fine?
ii) If Yes, is it scalable? i.e. can it be easily made generic to support 'n' enums as in toKey(Enum ...enums)

Below is snippet for two enums -

static Integer toKey(Status s, SubStatus ss) {
   return Integer.valueOf(s.ordinal() * SubStatus.values().length + ss.ordinal());
}

And

Status { NONE, PASS, WARN, REJECT }
SubStatus { NONE, SOFT_REJECT, HARD_REJECT }

Integer key1 = toKey(Status.REJECT, SubStatus.HARD_REJECT)
Integer key2 = toKey(Status.WARN, SubStatus.NONE)

then key1 != key2 

Thanks!

2

There are 2 answers

0
Nico Weisenauer On

You can try this code to generate a hash code which serves as a key:

static int toKey(Status s, SubStatus ss) {
  final int prime = 31;
  int result = 1;
  result = prime * result + ((s == null) ? 0 : s.hashCode());
  result = prime * result + ((ss == null) ? 0 : ss.hashCode());
  return result;
}

It should be fairly robust against collisions due to the prime number and it is extendable to support n Enums.

1
Tagir Valeev On

If you want a general solution you may introduce an array wrapper like this:

import java.util.Arrays;
import java.util.Objects;

public final class MultiKey {
    private final Object[] elements;

    public MultiKey(Object... elements) {
        this.elements = Objects.requireNonNull(elements);
    }

    @Override
    public int hashCode() {
        return Arrays.hashCode(elements);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null || getClass() != obj.getClass())
            return false;
        return Arrays.equals(elements, ((MultiKey) obj).elements);
    }

    @Override
    public String toString() {
        return Arrays.toString(elements);
    }
}

It can be used as HashMap key:

Map<MultiKey, String> testMap = new HashMap<>();
testMap.put(new MultiKey(Status.NONE), "none");
testMap.put(new MultiKey(Status.REJECT, SubStatus.SOFT_REJECT), "soft-reject");
testMap.put(new MultiKey(Status.WARN, SubStatus.SOFT_REJECT), "warn");
System.out.println(
    testMap.get(new MultiKey(Status.REJECT, SubStatus.SOFT_REJECT))); // prints "soft-reject"

Note that it can combine not enums, but any types with properly defined hashCode and equals as well. However you should maintain the same argument order in MultiKey constructor.