ブール配列をHashMapのキーとして使用しています。しかし問題は、要素は同じですが、別の配列がキーとして渡されるとHashMapがキーを取得できないことです。 (それらは異なるオブジェクトなので)。
配列をキーとして使用するにはどうすればよいですか?ここにコードがあります:
public class main {
public static HashMap<boolean[], Integer> h;
public static void main(String[] args){
boolean[] a = {false, false};
h = new HashMap<boolean[], Integer>();
h.put(a, 1);
if(h.containsKey(a)) System.out.println("Found a");
boolean[] t = {false, false};
if(h.containsKey(t)) System.out.println("Found t");
else System.out.println("Couldn't find t");
}
}
配列a
とt
には同じ要素が含まれていますが、HashMapはt
に対して何も返しません。
どうすれば機能しますか?
この方法ではできません。 hashCode()
メソッドは参照を使用するt
から継承されるため、a
とObject
の両方のJava.lang.Array.hashCode()
値は異なります。ハッシュコードを計算します(デフォルトの実装)。したがって、配列のハッシュコードは参照に依存します。つまり、t
とa
のハッシュコード値は異なります。さらに、equals
は参照にも基づいているため、2つの配列に対しては機能しません。
これを行う唯一の方法は、boolean
配列を内部メンバーとして保持するカスタムクラスを作成することです。次に、equals
とhashCode
をオーバーライドして、同じ値の配列を含むインスタンスが等しくなり、ハッシュコードも同じになるようにします。
より簡単なオプションは、_List<Boolean>
_をキーとして使用することです。 ドキュメント によると、List
のhashCode()
実装は次のように定義されます。
_int hashCode = 1;
Iterator<E> i = list.iterator();
while (i.hasNext()) {
E obj = i.next();
hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
}
_
ご覧のとおり、これは参照ではなくリスト内の値に依存しているため、これでうまくいくはずです。
2つの異なる配列は同じ要素があってもequals
を比較しないため、これを配列で行うことはできません。
コンテナクラスからマップする必要があります。例:ArrayList<Boolean>
(または単にList<Boolean>
。おそらくBitSet
がさらに適切でしょう。
Map
の実装は、キーのequals
およびhashCode
メソッドに依存しています。 Java=の配列は、Object
から直接拡張されたもので、equals
のみを比較するhashCode
のデフォルトのObject
およびidentity
を使用します。
私があなたなら、クラスKey
を作成します
class Key {
private final boolean flag1;
private final boolean flag2;
public Key(boolean flag1, boolean flag2) {
this.flag1 = flag1;
this.flag2 = flag2;
}
@Override
public boolean equals(Object object) {
if (!(object instanceof Key)) {
return false;
}
Key otherKey = (Key) object;
return this.flag1 == otherKey.flag1 && this.flag2 == otherKey.flag2;
}
@Override
public int hashCode() {
int result = 17; // any prime number
result = 31 * result + Boolean.valueOf(this.flag1).hashCode();
result = 31 * result + Boolean.valueOf(this.flag2).hashCode();
return result;
}
}
その後、Map
でキーを使用できます。
Map<Key, Integer> map = new HashMap<>();
Key firstKey = new Key(false, false);
map.put(firstKey, 1);
Key secondKey = new Key(false, false) // same key, different instance
int result = map.get(secondKey); // --> result will be 1
配列を含むクラスを作成できます。値に基づいて、そのクラスのhashCode()メソッドとequals()メソッドを実装します。
public class boolarray {
boolean array[];
public boolarray( boolean b[] ) {
array = b;
}
public int hashCode() {
int hash = 0;
for (int i = 0; i < array.length; i++)
if (array[i])
hash += Math.pow(2, i);
return hash;
}
public boolean equals( Object b ) {
if (!(b instanceof boolarray))
return false;
if ( array.length != ((boolarray)b).array.length )
return false;
for (int i = 0; i < array.length; i++ )
if (array[i] != ((boolarray)b).array[i])
return false;
return true;
}
}
その後、以下を使用できます。
boolarray a = new boolarray( new boolean[]{ true, true } );
boolarray b = new boolarray( new boolean[]{ true, true } );
HashMap<boolarray, Integer> map = new HashMap<boolarray, Integer>();
map.put(a, 2);
int c = map.get(b);
System.out.println(c);
boolean[] t;
t = a;
これを指定すると、boolean[] t = {false, false};
ではなく、目的の出力が得られます。
これは、Map
がreference
をkey
として格納するためです。あなたの場合、t
は同じ値を持っていますが、 a
と同じ参照。
したがって、t=a
を指定すると機能します。
これと非常に似ています:-
String a = "ab";
String b = new String("ab");
System.out.println(a==b); // This will give false.
a
とb
はどちらも同じ値を保持しますが、参照が異なります。したがって、==
を使用して参照を比較しようとすると、false
が得られます。
ただし、a = b;
を指定してreference
を比較すると、true
が得られます。
問題
他の人が言ったように、Java配列は.hashcode()
と.equals()
をObjectから継承し、address配列またはオブジェクトのcontentsを完全に無視します。これを修正する唯一の方法は、オブジェクトの内容に基づいてこれらのメソッドを実装するオブジェクトで配列をラップすることですこれは、Joshua Blochが項目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'sImmutableList.of()
、または自分の Paguro'svec()
、または他のライブラリを使用しますこれらのような事前にテストされたメソッドを提供します(さらに、不変/変更不可能なコレクションなど)。
劣った解決策
これは2017年の私の元の答えでした。誰かが面白かったのでここに残しますが、JavaにはすでにArrayListとCollections.unmodifiableList()が含まれているので回避策です)問題:.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)));
_
私はJava 8を使用しましたが、Java 7にはこれに必要なすべてが揃っています。 TestUtils を使用してhashCodeおよびequalsをテストしました。
おそらく、Arrayのreturnのequals()メソッドの動作が予想と異なるためです。独自の収集を実装し、equals()およびhashCode()をオーバーライドすることを検討する必要があります。
Mapはequals()
を使用して、キーが同じかどうかをテストします。
Object
のそのメソッドのデフォルトの実装は、==
、つまり参照の等価性をテストします。したがって、2つの配列は同じ配列ではないため、equals
は常にfalseを返します。
2つの配列でマップがArrays.equals
を呼び出して、等しいかどうかを確認する必要があります。
Arrays.equals
を使用する配列ラッパークラスを作成すると、期待どおりに動作します。
public static final class ArrayHolder<T> {
private final T[] t;
public ArrayHolder(T[] t) {
this.t = t;
}
@Override
public int hashCode() {
int hash = 7;
hash = 23 * hash + Arrays.hashCode(this.t);
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final ArrayHolder<T> other = (ArrayHolder<T>) obj;
if (!Arrays.equals(this.t, other.t)) {
return false;
}
return true;
}
}
public static void main(String[] args) {
final Map<ArrayHolder<Boolean>, Integer> myMap = new HashMap<>();
myMap.put(new ArrayHolder<>(new Boolean[]{true, true}), 7);
System.out.println(myMap.get(new ArrayHolder<>(new Boolean[]{true, true})));
}
外部ハッシュと比較戦略(trove)を受け入れるライブラリを使用できます。
class MyHashingStrategy implements HashingStrategy<boolean[]> {
@Override
public int computeHashCode(boolean[] pTableau) {
return Arrays.hashCode(pTableau);
}
@Override
public boolean equals(boolean[] o1, boolean[] o2) {
return Arrays.equals(o1, o2);
}
}
Map<boolean[], T> map = new TCustomHashMap<boolean[],T>(new MyHashingStrategy());