Allow caching of the hash code computation. User is expected to
not change the pair contents if he requested the hash code to be
cached.


git-svn-id: https://svn.apache.org/repos/asf/commons/proper/math/trunk@1336458 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Gilles Sadowski 2012-05-10 00:14:08 +00:00
parent ec4edd9a20
commit cb164171e6
2 changed files with 121 additions and 7 deletions

View File

@ -18,7 +18,10 @@ package org.apache.commons.math3.util;
/**
* Generic pair.
* Immutable class.
* <br/>
* Although the instances of this class are immutable, it is impossible
* to ensure that the references passed to the constructor will not be
* modified by the caller.
*
* @param <K> Key type.
* @param <V> Value type.
@ -31,6 +34,28 @@ public class Pair<K, V> {
private final K key;
/** Value. */
private final V value;
/** Whether the pair contents can be assumed to be immutable. */
private final boolean isImmutable;
/** Cached has code. */
private final int cachedHashCode;
/**
* Create an entry representing a mapping from the specified key to the
* specified value.
* If the pair can be assumed to be immutable, the hash code will be
* cached.
*
* @param k Key.
* @param v Value.
* @param assumeImmutable Whether the pair contents can be assumed to
* be immutable.
*/
public Pair(K k, V v, boolean assumeImmutable) {
key = k;
value = v;
isImmutable = assumeImmutable;
cachedHashCode = computeHashCode();
}
/**
* Create an entry representing a mapping from the specified key to the
@ -40,8 +65,20 @@ public class Pair<K, V> {
* @param v Value.
*/
public Pair(K k, V v) {
key = k;
value = v;
this(k, v, false);
}
/**
* Create an entry representing the same mapping as the specified entry.
* If the pair can be assumed to be immutable, the hash code will be
* cached.
*
* @param entry Entry to copy.
* @param assumeImmutable Whether the pair contents can be assumed to
* be immutable.
*/
public Pair(Pair<? extends K, ? extends V> entry, boolean assumeImmutable) {
this(entry.getKey(), entry.getValue(), assumeImmutable);
}
/**
@ -50,8 +87,7 @@ public class Pair<K, V> {
* @param entry Entry to copy.
*/
public Pair(Pair<? extends K, ? extends V> entry) {
key = entry.getKey();
value = entry.getValue();
this(entry, false);
}
/**
@ -104,7 +140,20 @@ public class Pair<K, V> {
*/
@Override
public int hashCode() {
return (key == null ? 0 : key.hashCode()) ^
(value == null ? 0 : value.hashCode());
return isImmutable ? cachedHashCode : computeHashCode();
}
/**
* Compute a hash code.
*
* @return the hash code value.
*/
private final int computeHashCode() {
int result = key == null ? 0 : key.hashCode();
final int h = value == null ? 0 : value.hashCode();
result = 37 * result + h ^ (h >>> 16);
return result;
}
}

View File

@ -46,4 +46,69 @@ public class PairTest {
Pair<Integer, Float> p3 = new Pair<Integer, Float>(new Integer(1), new Float(2));
Assert.assertFalse(p1.equals(p3));
}
@Test
public void testHashCode() {
final MyInteger m1 = new MyInteger(1);
final MyInteger m2 = new MyInteger(1);
final Pair<MyInteger, MyInteger> p1 = new Pair<MyInteger, MyInteger>(m1, m1);
final Pair<MyInteger, MyInteger> p2 = new Pair<MyInteger, MyInteger>(m2, m2);
// Same contents, same hash code.
Assert.assertTrue(p1.hashCode() == p2.hashCode());
// Different contents, different hash codes.
m2.set(2);
Assert.assertFalse(p1.hashCode() == p2.hashCode());
// Test cache.
final MyInteger m3 = new MyInteger(1);
final Pair<MyInteger, MyInteger> p3 = new Pair<MyInteger, MyInteger>(m3, m3, true);
final int hC3 = p3.hashCode();
// Contents change will not affect the hash code because it is cached.
m3.set(3);
Assert.assertTrue(hC3 == p3.hashCode());
final Pair<MyInteger, MyInteger> p4 = new Pair<MyInteger, MyInteger>(p3, false);
// p3 and p4 do not have the same hash code because p4 was contructed after m3
// was changed.
Assert.assertFalse(p4.hashCode() == p3.hashCode());
final int hC4 = p4.hashCode();
// Contents change will affect the hash code because it is not cached.
m3.set(4);
Assert.assertFalse(hC4 == p4.hashCode());
}
/**
* A mutable integer.
*/
private static class MyInteger {
private int i;
public MyInteger(int i) {
this.i = i;
}
public void set(int i) {
this.i = i;
}
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (!(o instanceof MyInteger)) {
return false;
} else {
return i == ((MyInteger) o).i;
}
}
@Override
public int hashCode() {
return i;
}
}
}