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 entry, boolean assumeImmutable) { + this(entry.getKey(), entry.getValue(), assumeImmutable); } /** @@ -50,8 +87,7 @@ public class Pair { * @param entry Entry to copy. */ public Pair(Pair 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; + } + } }