問題
他の人が言っているように、Java配列はObjectを継承.hashcode()
し、Objectから継承します。これは、配列またはオブジェクトのアドレス.equals()
のハッシュを使用し、その内容を完全に無視します。これを修正する唯一の方法は、配列の内容に基づいてこれらのメソッドを実装するオブジェクトで配列をラップすることです。これが、JoshuaBlochが項目25「配列よりもリストを優先する」を書いた理由の1つです。Javaには、これを行ういくつかのクラスが用意されています。または、を使用して独自のクラスを作成できます。これらのクラスには、これらのメソッドの正確で効率的な実装が含まれています。残念ながら、これらはデフォルトの実装ではありません。Arrays.hashCode()
Arrays.equals()
実用的な場合は常に、ハッシュベースのコレクションのキーに非常に変更不可能な(または不変の)クラスを使用してください。配列(または他の可変オブジェクト)をキーとしてハッシュテーブルに格納した後で変更すると、ほぼ確実に将来失敗する.get()
か.contains()
、そのハッシュテーブルでテストされます。変更可能なハッシュマップキーは危険な方法ですか?も参照してください。
特定のソリューション
// Also works with primitive: (boolean... items)
public static List<Boolean> bList(Boolean... items) {
List<Boolean> mutableList = new ArrayList<>();
for (Boolean item : items) {
mutableList.add(item);
}
return Collections.unmodifiableList(mutableList);
}
ArrayListは、その内容に基づいて(正しく効率的に)を実装するため、すべて.equals()
がと同じハッシュコードを持ち、他のすべてと等しくなります。.hashCode()
bList(false, false)
bList(false, false)
それを包むことはCollections.unmodifiableList()
変更を防ぎます。
bList()を使用するように例を変更するには、いくつかの宣言と型アノテーションを変更するだけです。それはあなたのオリジナルと同じくらい明確で、ほぼ同じくらい短いです:
public class main {
public static HashMap<List<Boolean>, Integer> h;
public static void main(String[] args){
List<Boolean> a = bList(false, false);
h = new HashMap<>();
h.put(a, 1);
if(h.containsKey(a)) System.out.println("Found a");
List<Boolean> t = bList(false, false);
if(h.containsKey(t)) System.out.println("Found t");
else System.out.println("Couldn't find t");
}
}
一般的なソリューション
public <T> List<T> bList(T... items) {
List<T> mutableList = new ArrayList<>();
for (T item : items) {
mutableList.add(item);
}
return Collections.unmodifiableList(mutableList);
}
上記のソリューションの残りの部分は変更されていませんが、これはJavaの組み込み型推論を利用して、任意のプリミティブまたはオブジェクトで機能します(ただし、不変クラスでのみ使用することをお勧めします)。
ライブラリソリューション
の代わりにbList()
、Google Guava ImmutableList.of()
、または私自身のPaguro vec()
、またはこれらのような事前にテストされたメソッド(および不変/変更不可能なコレクションなど)を提供する他のライブラリを使用してください。
劣った解決策
これは2017年の私の最初の答えでした。誰かが面白いと思ったのでここに残しておきますが、Javaには問題を回避するArrayListとCollections.unmodizableList()がすでに含まれているため、二流だと思います。.equals()メソッドと.hashCode()メソッドを使用して独自のコレクションラッパーを作成することは、組み込みのものを使用するよりも作業が多く、エラーが発生しやすく、検証が難しいため、読みにくくなります。
これは、あらゆるタイプの配列で機能するはずです。
class ArrayHolder<T> {
private final T[] array;
@SafeVarargs
ArrayHolder(T... ts) { array = ts; }
@Override public int hashCode() { return Arrays.hashCode(array); }
@Override public boolean equals(Object other) {
if (array == other) { return true; }
if (! (other instanceof ArrayHolder) ) {
return false;
}
//noinspection unchecked
return Arrays.equals(array, ((ArrayHolder) other).array);
}
}
ArrayHolderを使用するように変換された特定の例を次に示します。
// boolean[] a = {false, false};
ArrayHolder<Boolean> a = new ArrayHolder<>(false, false);
// h = new HashMap<boolean[], Integer>();
Map<ArrayHolder<Boolean>, Integer> h = new HashMap<>();
h.put(a, 1);
// if(h.containsKey(a)) System.out.println("Found a");
assertTrue(h.containsKey(a));
// boolean[] t = {false, false};
ArrayHolder<Boolean> t = new ArrayHolder<>(false, false);
// if(h.containsKey(t)) System.out.println("Found t");
assertTrue(h.containsKey(t));
assertFalse(h.containsKey(new ArrayHolder<>(true, false)));
私はJava8を使用しましたが、Java7にはこれに必要なものがすべて揃っていると思います。TestUtilsを使用してhashCodeとequalsをテストしました。