diff --git a/src/main/java/org/apache/commons/math3/util/Pair.java b/src/main/java/org/apache/commons/math3/util/Pair.java
index cc5a66cff..4b29f156b 100644
--- a/src/main/java/org/apache/commons/math3/util/Pair.java
+++ b/src/main/java/org/apache/commons/math3/util/Pair.java
@@ -18,7 +18,10 @@ package org.apache.commons.math3.util;
/**
* Generic pair.
- * Immutable class.
+ *
+ * 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 Key type.
* @param Value type.
@@ -31,6 +34,28 @@ public class Pair {
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 {
* @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 {
* @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 {
*/
@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;
}
}
diff --git a/src/test/java/org/apache/commons/math3/util/PairTest.java b/src/test/java/org/apache/commons/math3/util/PairTest.java
index 136359cb9..b9c92ae16 100644
--- a/src/test/java/org/apache/commons/math3/util/PairTest.java
+++ b/src/test/java/org/apache/commons/math3/util/PairTest.java
@@ -46,4 +46,69 @@ public class PairTest {
Pair p3 = new Pair(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 p1 = new Pair(m1, m1);
+ final Pair p2 = new Pair(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 p3 = new Pair(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 p4 = new Pair(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;
+ }
+ }
}