web-dev-qa-db-ja.com

HashMapを配列としてキーとして機能させるにはどうすればよいですか?

ブール配列を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");

}

}

配列atには同じ要素が含まれていますが、HashMapはtに対して何も返しません。

どうすれば機能しますか?

21
gaganbm

この方法ではできません。 hashCode()メソッドは参照を使用するtから継承されるため、aObjectの両方のJava.lang.Array.hashCode()値は異なります。ハッシュコードを計算します(デフォルトの実装)。したがって、配列のハッシュコードは参照に依存します。つまり、taのハッシュコード値は異なります。さらに、equalsは参照にも基づいているため、2つの配列に対しては機能しません。

これを行う唯一の方法は、boolean配列を内部メンバーとして保持するカスタムクラスを作成することです。次に、equalshashCodeをオーバーライドして、同じ値の配列を含むインスタンスが等しくなり、ハッシュコードも同じになるようにします。

より簡単なオプションは、_List<Boolean>_をキーとして使用することです。 ドキュメント によると、ListhashCode()実装は次のように定義されます。

_int hashCode = 1;
Iterator<E> i = list.iterator();
while (i.hasNext()) {
    E obj = i.next();
    hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
}
_

ご覧のとおり、これは参照ではなくリスト内の値に依存しているため、これでうまくいくはずです。

25
Vivin Paliath

2つの異なる配列は同じ要素があってもequalsを比較しないため、これを配列で行うことはできません。

コンテナクラスからマップする必要があります。例:ArrayList<Boolean>(または単にList<Boolean>。おそらくBitSetがさらに適切でしょう。

10
zch

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

参照: 1つのフィールドからのJavaハッシュコード

4
Genzer

配列を含むクラスを作成できます。値に基づいて、そのクラスの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);
2
Jeroen Vuurens
boolean[] t;
t = a;

これを指定すると、boolean[] t = {false, false};ではなく、目的の出力が得られます。

これは、Mapreferencekeyとして格納するためです。あなたの場合、tは同じ値を持っていますが、 aと同じ参照。

したがって、t=aを指定すると機能します。

これと非常に似ています:-

String a = "ab";
String b = new String("ab");

System.out.println(a==b); // This will give false.

abはどちらも同じ値を保持しますが、参照が異なります。したがって、==を使用して参照を比較しようとすると、falseが得られます。

ただし、a = b;を指定してreferenceを比較すると、trueが得られます。

1
SudoRahul

問題

  1. 他の人が言ったように、Java配列は.hashcode().equals()をObjectから継承し、address配列またはオブジェクトのcontentsを完全に無視します。これを修正する唯一の方法は、オブジェクトの内容に基づいてこれらのメソッドを実装するオブジェクトで配列をラップすることですこれは、Joshua Blochが項目25:「配列よりもリストを優先する」と書いた理由の1つです。Javaは、これを行うクラスをいくつか提供しています。または、Arrays.hashCode()Arrays.equals()には、これらのメソッドの正しい効率的な実装が含まれています。残念ながら、これらはデフォルトの実装ではありません!

  2. 実用的な場合はいつでも、ハッシュベースのコレクションのキーには、大幅に変更できない(または不変の)クラスを使用してください。配列(またはその他の可変オブジェクト)をハッシュテーブルにキーとして格納した後で変更すると、そのハッシュテーブルでの将来の.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);
}
_
  1. ArrayListは、その内容に基づいて.equals()および.hashCode()を(正確かつ効率的に)実装するため、すべてのbList(false, false)は同じハッシュコードを持ち、他のすべてと等しくなります。 bList(false, false)

  2. 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をテストしました。

1
GlenPeterson

おそらく、Arrayのreturnのequals()メソッドの動作が予想と異なるためです。独自の収集を実装し、equals()およびhashCode()をオーバーライドすることを検討する必要があります。

1
FazoM

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})));
}
1

外部ハッシュと比較戦略(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());
1
cquezel