2016-02-02 9 views
8

私はJava 8以降、HashMapに十分なハッシュ・コリジョンがあり、キーにComparableが実装されていれば、それはuse a balanced tree instead of a linked list for the binになります。しかし、私が見ることができるから、のインターフェイスdoes not requireはと一致しています。(これは強くお勧めしますが)compareTo()です。キーがequalsと矛盾する方法でComparableを実装している場合、Java 8のHashMapが誤動作するバグですか?

私は何かを見逃しましたか?キーが準拠しているが、推奨されていないComparableの実装を持つ場合、新しい実装でHashMapMapインターフェイスの要件に違反するように思われます。

次のJUnitテストは、OpenJDKの8u72上で、この動作を公開:

import static org.junit.Assert.*; 

import java.util.HashSet; 
import java.util.Set; 

import org.junit.Test; 

class Foo 
     implements Comparable<Foo> // Comment this out to fix the test case 
{ 
    private final int bar; 
    private final int baz; 

    Foo(int bar, int baz) { 
     this.bar = bar; 
     this.baz = baz; 
    } 

    public boolean equals(Object obj) { 
     // Note that this ignores 'baz' 
     return obj instanceof Foo && bar == ((Foo) obj).bar; 
    } 

    public int hashCode() { 
     return 0; 
    } 

    public int compareTo(Foo o) { 
     // Inconsistent with equals(), but seems to obey the requirements of 
     // Comparable<Foo> 
     return Integer.compare(baz, o.baz); 
    } 
} 

public class FooTest { 
    @Test 
    public void test() { 
     Set<Foo> set = new HashSet<>(); 
     for (int i = 0; i < 128; ++i) { 
      set.add(new Foo(i, 0)); 
     } 

     // This fails if Foo implements Comparable<Foo> 
     assertTrue(set.contains(new Foo(64, 1))); 
    } 
} 
+4

新しい実装の既知の期待された動作であるという点ではバグではないと思われます... 'TreeMap'は常に' equals'ではなくorder比較を使用しているので、同じ問題があります。 –

+1

@JonSkeet真実ですが、そのため私は 'HashMap'を使っています:)。具体的には、キーが推奨されない 'Comparable'実装を持っていることを知っていたので、意図的にJava 7で' HashMap'( 'TreeMap'ではなく)を使用していたコードについて心配しています。 Java 8にアップグレードすると、非常に微妙なバグが発生する可能性があります。 –

+0

[java.math.BigDecimal](https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html#compareTo-java.math.BigDecimal-)クラスは、次の例です。 JDKに含まれているequalsと矛盾します。 – jmehrens

答えて

5

の実装のバグは、それがどこかバグではありませんIMOは、コードが実装者の期待通りに動作している点で異なりますが、これは珍しいComparable実装の既知の結果です。 Comparable documentationから:

強く、自然順序付けがequalsと一致していることを(必須ではありません)をお勧めします。これは、明示的コンパレータを持たないソートされたセット(およびソートされたマップ)が、自然順序がequalsと矛盾する要素(またはキー)とともに使用された場合、「妙に」振る舞います。特に、そのようなソートされたセット(またはソートされたマップ)は、equalsメソッドによって定義されるセット(またはマップ)の一般規約に違反します。

これは通常のソートセットまたはマップではありませんが、この問題とは明確な関係があります。

私はそれが可能な問題だと同意し、は本当にという微妙なものです。特にシンプルな状況では再現しにくいです。私はあなたのクラスがequalsと矛盾する方法でComparableを実装しているという事実に非常に注意を払うためにドキュメントを更新し、特にこれを潜在的な問題として参照します。

+1

IMOでは、要素を格納するために自然順序付けを使用する必要のあるデータ構造であれば、ドキュメントをアップグレードする必要があります。 –

+1

@AlexisC .:合意。 –

+0

私はTavianがこの部分のドキュメントを見逃したと思います。彼の全体的な質問は、 'compareTo'メソッドのドキュメンテーションが' Comparable'のクラスドキュメントよりも曖昧で、より曖昧であるからです。 – DavidS

1

いいえ、それは文書化実装の制約である、とComparable.

+1

どの制約が厳密に違反されていますか?私は 'Comparable'のJavaDoc全体を読んでいますが、何が分かりませんか? –

+2

確かに:「必ずしも必要ではありませんが、自然順序が等価と一致することを強くお勧めします。」 –

4

まずは、どのような一貫性equals間とcompareToを思い出してみましょう意味:

Comparable

クラスCのための自然な順序は場合にだけe1.compareTo(e2) == 0に等しいと一貫性があると言われていますe1およびe2C)のすべてに対してe1.equals(e2)と同じブール値を持ちます。

だから、自然順序付けがequalsと一貫されていないため1つのシナリオはe1.compareTo(e2)がゼロe1.equals(e2)にもかかわらず、戻りfalseを返すことがあります順序です。大文字と小文字を区別する等価性と組み合わされた、大文字と小文字を区別しない並べ替えが例ですもう1つの例は、new BigDecimal("1.0").compareTo(BigDecimal.ONE)がゼロを返すが、new BigDecimal("1.0").equals(BigDecimal.ONE)falseを返す自然順序付けを持つBigDecimalです。

新しいHashMapの実装は、のケースを処理することに注意してください。自然順序付けは候補を見つけるのに役立ちますが、メソッドがtrueを返す場合、候補は同等と見なされます。これは、同じハッシュコードを持つ多数のキーを持っていて、自然順序付けによって等しいが等しいが等価ではない場合、古い実装のようにこれらのキーの間で線形検索を行うことを意味します。

対照的に、まったく異なるケースでのあなたの例です。 compareToの実装では、テストでtrueが返されますが、2つのオブジェクトが等しくないことがわかります。これは決してBigDecimalや他の実用的な自然順序付けの例では起こりません。

あなたのケースは現在の実装ではサポートされていませんが、お気づきのように、オブジェクトのハッシュコードが同じでも壊れてしまいます。私はこのシナリオが実用的な関連性を持っているのか疑問です。私がこれまでに見たすべての例は、新しいHashMapの実装について学んだの後にちょうどで構成されています。

関連する問題