32

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

どうすればそれを機能させることができますか?

4

9 に答える 9

33

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

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

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

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

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

于 2013-03-22T17:07:38.233 に答える
12

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

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

于 2013-03-22T17:08:53.820 に答える
5

Mapequals実装はキーとhashCodeメソッドに依存します。Javaの配列は、から直接拡張されObject、デフォルトを使用equalshashCodeObject比較のみを行います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ハッシュコード

于 2013-03-22T17:22:13.453 に答える
3

問題

  1. 他の人が言っているように、Java配列はObjectを継承.hashcode()し、Objectから継承します。これは、配列またはオブジェクトのアドレス.equals()のハッシュを使用し、その内容を完全に無視します。これを修正する唯一の方法は、配列の内容に基づいてこれらのメソッドを実装するオブジェクトで配列をラップすることです。これが、JoshuaBlochが項目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 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をテストしました。

于 2017-08-19T14:19:55.353 に答える
2

配列を含むクラスを作成できます。値に基づいて、そのクラスの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);
于 2013-03-22T17:23:59.123 に答える
1

マップはequals()、キーが同じであるかどうかをテストするために使用します。

Objectテストでのそのメソッドのデフォルトの実装==、つまり参照の同等性。したがって、2つの配列は同じ配列ではないため、equals常にfalseを返します。

Arrays.equals等しいかどうかを確認するには、2つの配列でマップ呼び出しを行う必要があります。

を使用する配列ラッパークラスを作成する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})));
}
于 2013-03-22T17:12:11.717 に答える
1
boolean[] t;
t = a;

の代わりにこれを指定するとboolean[] t = {false, false};、目的の出力が得られます。

これは、をとしてMap格納するためです。この場合、値は同じですが、と同じ参照を持っていません。referencekeyta

したがって、あなたが与えるとき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

于 2013-03-22T17:04:53.463 に答える
1

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

于 2013-03-22T17:10:54.337 に答える
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());
于 2015-09-13T20:27:01.220 に答える