You should use java.awt.Dimension as your key.
Dimension key = new Dimension(4, 12);
Dimension has a very nice hashCode() method that produces a different hashCode for each pair of positive integers, so that the hashCodes for (4, 12) and (12, 4) are different. So these are fast to instantiate and make very good hashCodes.
I do wish they had made the class immutable, but you can make your own immutable class modeled on Dimension.
Here's a table showing the hashCode for different values of width and height:
0 1 2 3 4 <-- width
+--------------------
0 | 0 2 5 9 14
1 | 1 4 8 13
2 | 3 7 12
3 | 6 11
4 | 10
^
|
height
If you follow the hashCodes in order from 0 to 14, you'll see the pattern.
Here's the code that produces this hashCode:
public int hashCode() {
int sum = width + height;
return sum * (sum + 1)/2 + width;
}
You may recognize the formula for triangular number inside the last line. That's why the first column of the table contains all triangular numbers.
For speed, you should calculate the hashCode in the constructor. So your whole class could look like this:
public class PairHash {
private final int hash;
public PairHash(int a, int b) {
int sum = a+b;
hash = sum * (sum+1)/2 + a;
}
public int hashCode() { return hash; }
}
Of course, if you'll probably need an equals method, but you limit yourself to positive integers that won't overflow, you can add a very fast one:
public class PairHash {
// PAIR_LIMIT is 23170
// Keeping the inputs below this level prevents overflow, and guarantees
// the hash will be unique for each pair of positive integers. This
// lets you use the hashCode in the equals method.
public static final int PAIR_LIMIT = (int) (Math.sqrt(Integer.MAX_VALUE))/2;
private final int hash;
public PairHash(int a, int b) {
assert a >= 0;
assert b >= 0;
assert a < PAIR_LIMIT;
assert b < PAIR_LIMIT;
int sum = a + b;
hash = sum * (sum + 1) / 2 + a;
}
public int hashCode() { return hash; }
public boolean equals(Object other) {
if (other instanceof PairHash){
return hash == ((PairHash) other).hash;
}
return false;
}
}
We restrict this to positive values because negative values will produce some duplicated hash codes. But with this restriction in place, these are the fastest hashCode() and equals() methods that can be written. (Of course, you can write hashCodes just as fast in any immutable class by calculating the hashCode in the constructor.)
If you can't live with those restrictions, you just need to save the parameters.
public class PairHash {
private final int a, b, hash;
public PairHash(int a, int b) {
this.a = a;
this.b = b;
int sum = a+b;
hash = sum * (sum+1)/2 + a;
}
public int hashCode() { return hash; }
public boolean equals(Object other) {
if (other instanceof PairHash) {
PairHash otherPair = (PairHash)other;
return a == otherPair.a && b == otherPair.b;
}
return false;
}
But here's the kicker. You don't need this class at all. Since the formula gives you a unique integer for each pair of numbers, you can just use that Integer as your map key. The Integer class has its own fast equals() and hashCode methods that will work fine. This method will generate the hash key from two short values. The restriction is that your inputs need to be positive short values. This is guaranteed not to overflow, and by casting the intermediate sum to a long, it has a wider range than the previous method: It works with all positive short values.
static int hashKeyFromPair(short a, short b) {
assert a >= 0;
assert b >= 0;
long sum = (long) a + (long) b;
return (int) (sum * (sum + 1) / 2) + a;
}