web-dev-qa-db-ja.com

Java HashMapは機能しますが、containsKeyは機能しません

HashMapでキーを見つけようとしています。 'get'を使用して選択したキーを出力できますが、ifステートメントで 'containsKey'を使用すると、キーが見つかりません。

キーがマップに存在することは知っていますが、falseを返し続けます。アイデアはありますか?

私のコード:

public static boolean checkLowerStructuralSupport(Location location) {

    boolean hasSupport = false;

    Location supportingLocation = new Location(location.getX(), location.getY(), location.getZ() - 1);

    System.out.println(_levels.get(supportingLocation.getZ()).getLevelSites2().get(supportingLocation)); //works

    if (_levels.get(supportingLocation.getZ()).getLevelSites2().containsKey(supportingLocation)) {
        hasSupport = true;
    } else {
        hasSupport = false;
    }

    return hasSupport;
}

Locationクラスのコードは次のとおりです。

public class Location {

    protected int _x;
    protected int _y;
    protected int _z;

    public Location(int xAxis, int yAxis, int zAxis) {
        this._x = xAxis;
        this._y = yAxis;
        this._z = zAxis;
    }

    public void equals() {
        //not implemented yet
    }

    public void HashCode() {
        //not implemented yet
    }

    public String toString() {
        String locationString = Integer.toString(_x) + Integer.toString(_y) + Integer.toString(_z);
        return locationString;
    }

    public void setX(int XAxis) {
        this._x = XAxis;
    }

    public int getX() {
        return this._x;
    }

    public void setY(int YAxis) {
        this._y = YAxis;
    }

    public int getY() {
        return this._y;
    }

    public void setZ(int ZAxis) {
        this._z = ZAxis;
    }

    public int getZ() {
        return this._z;
    }

}
14
burntsugar

LocationクラスがそのhashCode()およびequals(Object)メソッドを適切に実装していることを確認する必要があります( documentation )。つまり、2つのLocationオブジェクトが実質的に等しい場合、それらは共通のハッシュコードを共有し、それらのequalsメソッドはtrueを返す必要があります。

23
Adam Paynter

ここで説明するように、equals(Object)メソッドをオーバーライドする必要があります。

Get(Object)が機能している理由は、HashMapがLocationクラスのハッシュを計算し、hascodeが指すオブジェクトを返すためです。

containsKey(Object)はハッシュキーを計算し、ハッシュが指しているオブジェクトを取得します。 HashMapのオブジェクトは、入力したオブジェクトと比較されます。これらの比較には、equalsメソッドが使用されます。彼がequalsメソッドをオーバーライドしない場合、オブジェクトが同じインスタンスを参照すると、trueが返されます。

HashMapから

/** 
 * Check for equality of non-null reference x and possibly-null y. 
 */
static boolean eq(Object x, Object y) {
    return x == y || x.equals(y);
}

オブジェクトから

public boolean equals(Object obj) {
    return (this == obj);
    }

等しいのjavadocから

クラスObjectのequalsメソッドは、オブジェクトに対して最も識別可能な同等の関係を実装します。つまり、null以外の参照値xおよびyの場合、このメソッドは、xとyが同じオブジェクトを参照している場合にのみtrueを返します(x == yの値はtrueです)。

hashCodeメソッドの一般的なコントラクトを維持するために、このメソッドがオーバーライドされるたびに、hashCodeメソッドをオーバーライドする必要があることに注意してください。これは、等しいオブジェクトには等しいハッシュコードが必要であると述べています。

5
Markus Lausberg

これを引き起こすと私が考えることができる唯一のことは、supportingLocationの状態がget(...)呼び出しとcontainsKey(...)の間で何らかの形で変化している場合です。

投稿したコードスニペットが問題の原因となっている正確なコードであるとすると、これが発生する可能性があるのは、Location#getZ(...)Location#hashCode()、またはLocation#equals(Object)のいずれかが状態を変更した場合のみです。 of Location(またはLocationコンストラクター、またはこれらのメソッドの1つは、Locationインスタンスの状態をランダムに変更するスレッドを開始しますが、それを除外できると思います)。

上記のメソッドのいずれもsupportingLocationインスタンスの状態を変更していないことを確認できますか?私はLocationクラス自体に精通していませんが、そのようなクラスは理想的には不変であると思い切って推測します。

編集:明確にするために、Location#getZ()などが場所を変更していないと言うとき、私が意味するのは:

Location x = new Location(1,2,3);
Location y = new Location(1,2,3);

boolean eq1 = x.equals(y);
int hash1 = x.hashCode();
x.getZ(); // this should *not* mutate the state of x
boolean eq2 = x.equals(y);
int hash2 = x.hashCode();

結局、eq1はeq1と等しく、hash1はhash2と等しくなければなりません。そうでない場合、getZ()はxの状態を変更し(または等しい、またはhashCode、さらに悪いことに、これらのメソッドは完全にオフになります)、観察した動作になります。

2
Jack Leow

Locationクラスで、hashCodeおよびequalsメソッドをオーバーライドしていることを確認してください。

もしそうなら、あなたはそれらを投稿できますか?

2

containsKeyは、メソッドequalsを使用して、パラメーターをキーセットのエントリと比較します。したがって、Locationクラスには適切なequalsメソッドが必要です。 Java.lang.Objectのデフォルトのequalsメソッドは、両方のオブジェクトが同じオブジェクトである場合にのみtrueを返します。この場合、比較する必要があり、カスタムのequalsメソッドが必要な2つの異なるインスタンスがある可能性があります。

2
froderik

get()containsKey()の両方がLocationクラスのhashCode()メソッドを使用しています。ハッシュ衝突がない限り、equals()メソッドは呼び出されません。 (したがって、HashMapのget()はすべての状況でequals()を使用するわけではありません。)

Locationクラスについて、たまたま独自のバージョンのhashCode()を実装しましたか? hashCode()メソッドは慎重に実装する必要があります。 Joshua Blochは、本のすべての詳細を説明しましたEffective Java、その一部はオンラインです...これらのサンプルの章へのリンクを見つけます。 有効Javaサンプルチャプター 。チャプター3が必要です。

質問へのコメントで尋ねたように、あなたの_levels変数はどこから来たのですか?そのメソッド内で宣言されているのがわかりません。名前(アンダースコアプレフィックス、他の言語からその規則をインポートしていますか?)は、このメソッドの外部で「存続」していることを示しています。おそらく他のコードが実行中にそれを変更していますか?解決したらお知らせください。サスペンスが私を殺している。

1
dustmachine

HashMap実装のソースコードでピークを迎えます。 getとcontainsKeyはどちらも、キーオブジェクトのhasCode()メソッドとequals()メソッドを使用します。

唯一の本当の違いは、指摘されたように、それは些細なnullチェックであり、比較にあります。

取得する:

((k = e.key) == key || key.equals(k))

containsKey:

((k = e.key) == key || (key != null && key.equals(k)))

ここで、eはHashMapのタイプEntryです。

したがって、hashCode()やequals()の強力な実装がない場合は、問題が発生します。さらに、キーが変更された場合(クラスフィールドをfinalと宣言しなかったようです)、問題が発生する可能性があります。

次の例を見てください。

public class HashMapTest {
    static class KeyCheck {
        int value;
        public KeyCheck(int value) { this.value = value; }
        public void setValue(int value) { this.value = value; }
        @Override public int hashCode() { return value; }
        @Override public boolean equals(Object o) {
            return ((KeyCheck)o).value == this.value;
        }
    }

    public static void main(String args[]) {
        HashMap<KeyCheck, String> map = new HashMap<KeyCheck, String>();
        KeyCheck k1 = new KeyCheck(5);
        KeyCheck k2 = new KeyCheck(5);

        map.put(k1, "Success");

        System.out.println("Key: " + k1 + " Get: " + map.get(k1) +
                           " Contains: " + map.containsKey(k1));
        System.out.println("Key: " + k2 + " Get: " + map.get(k2) +
                           " Contains: " + map.containsKey(k2));

        k1.setValue(10);

        System.out.println("Key: " + k1 + " Get: " + map.get(k1) +
                           " Contains: " + map.containsKey(k1));
        System.out.println("Key: " + k2 + " Get: " + map.get(k2) +
                           " Contains: " + map.containsKey(k2));
    }
}

これは印刷されます:

キー:HashMapTest $ KeyCheck @ 5取得:成功内容:true
キー:HashMapTest $ KeyCheck @ 5取得:成功内容:true
キー:HashMapTest $ KeyCheck @ a取得:null含まれるもの:false
キー:HashMapTest $ KeyCheck @ 5取得:null含まれるもの:false

ご覧のとおり、この場合、可変性によりhashCode()が変更され、すべてが台無しになりました。

1
Aaron K

問題を回避するには、equals()メソッドとhashCode()メソッドに一貫性があり、要件に準拠している必要があります(他の場所で説明されています)。

さらに、hashCode()はnot可変メンバーに依存する必要があります。そうしないと、計算されたハッシュコードが変更される可能性があり、これはHashMapの内部動作に影響します。これにより、Hash*コレクションからコンテンツを取得できないことが明らかになります。

1
Brian Agnew

ハッシュコードが必要な場合もあれば、必要ない場合もあると思います。このようにして、購入したいときにハッシュコードのチェックをオフにして、すべてのオブジェクトのハッシュコードを0に変更できると思います。

public class sample(){
    @JsonIgnore
    private int hashCode = super.hashCode();

    public void setHashCode(int hashCode){
        this.hashCode = hashCode;
    }    

    @Override
    public int hashCode(){
        return this.hashCode;
    }    

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final ReflectObject other = (ReflectObject) obj;
        if (this.hashCode != other.hashCode) {
            return false;
        }
        return true;
    }
}
0