web-dev-qa-db-ja.com

hashCode()をオーバーライドするオブジェクトの一意のIDを取得する方法は?

JavaのクラスがhashCode()をオーバーライドしない場合、このクラスのインスタンスを印刷すると、Niceの一意の番号が与えられます。

ObjectのJavadocはhashCode()について述べています:

合理的に実用的である限り、クラスObjectによって定義されたhashCodeメソッドは、個別のオブジェクトに対して個別の整数を返します。

しかし、クラスがhashCode()をオーバーライドする場合、一意の番号を取得するにはどうすればよいですか?

215

System.identityHashCode(yourObject) は、yourObjectの「元の」ハッシュコードを整数として提供します。一意性は必ずしも保証されません。 Sun JVM実装は、このオブジェクトの元のメモリアドレスに関連する値を提供しますが、これは実装の詳細であり、それに依存するべきではありません。

編集:以下のトムのコメントに従って修正された回答。メモリアドレスと移動オブジェクト。

321
Brian Agnew

Objectのjavadocは、

これは通常、オブジェクトの内部アドレスを整数に変換することで実装されますが、この実装手法はJavaTMプログラミング言語では必要ありません。

クラスがhashCodeをオーバーライドする場合、それは特定のIDを生成したいということを意味します。これは(期待できる)正しい振る舞いをします。

System.identityHashCode を使用して、任意のクラスのIDを取得できます。

28
Valentin Rocher

たぶん、この迅速で汚い解決策はうまくいくでしょうか?

public class A {
    static int UNIQUE_ID = 0;
    int uid = ++UNIQUE_ID;

    public int hashCode() {
        return uid;
    }
}

これは、初期化されているクラスのインスタンスの数も示します。

9
John Pang

hashCode()メソッドは、オブジェクトに一意の識別子を提供するためのものではありません。オブジェクトの状態(つまり、メンバーフィールドの値)を単一の整数にダイジェストします。この値は、マップやセットなどのハッシュベースのデータ構造で主に使用され、オブジェクトを効果的に保存および取得します。

オブジェクトの識別子が必要な場合は、hashCodeをオーバーライドする代わりに、独自のメソッドを追加することをお勧めします。この目的のために、以下のような基本インターフェース(または抽象クラス)を作成できます。

public interface IdentifiedObject<I> {
    I getId();
}

使用例:

public class User implements IdentifiedObject<Integer> {
    private Integer studentId;

    public User(Integer studentId) {
        this.studentId = studentId;
    }

    @Override
    public Integer getId() {
        return studentId;
    }
}

ID生成については、 私のブログ投稿 で確認できます。一意のIDを生成するいくつかの方法を説明しようとしました。

7
ovunccetin

変更可能なクラスの場合、クラス変数static Java.util.concurrent.atomic.AtomicInteger nextInstanceIdを宣言できます。 (明らかな方法で初期値を与える必要があります。)次に、インスタンス変数int instanceId = nextInstanceId.getAndIncrement()を宣言します。

3
Aaron Mansheim

複数のスレッドで作成されたオブジェクトがあり、シリアル化可能である場合に機能するこのソリューションを思い付きました:

public abstract class ObjBase implements Serializable
    private static final long serialVersionUID = 1L;
    private static final AtomicLong atomicRefId = new AtomicLong();

    // transient field is not serialized
    private transient long refId;

    // default constructor will be called on base class even during deserialization
    public ObjBase() {
       refId = atomicRefId.incrementAndGet()
    }

    public long getRefId() {
        return refId;
    }
}
1
Howard Swope
// looking for that last hex?
org.joda.DateTime@57110da6

オブジェクトで.toString()を実行するときにhashcode Java型を調べる場合、基になるコードは次のとおりです。

Integer.toHexString(hashCode())
1
Frankie

HashCode()とidentityHashCode()の戻り値には違いがあります。 2つの等しくない(==でテストされた)オブジェクトo1、o2 hashCode()が同じになる可能性があります。以下の例をご覧ください。

class SeeDifferences
{
    public static void main(String[] args)
    {
        String s1 = "stackoverflow";
        String s2 = new String("stackoverflow");
        String s3 = "stackoverflow";
        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());
        System.out.println(s3.hashCode());
        System.out.println(System.identityHashCode(s1));
        System.out.println(System.identityHashCode(s2));
        System.out.println(System.identityHashCode(s3));
        if (s1 == s2)
        {
            System.out.println("s1 and s2 equal");
        } 
        else
        {
            System.out.println("s1 and s2 not equal");
        }
        if (s1 == s3)
        {
            System.out.println("s1 and s3 equal");
        }
        else
        {
            System.out.println("s1 and s3 not equal");
        }
    }
}
0
display_name

別の角度から他の答えを増やすためだけに。

「上記」のハッシュコードを再利用し、クラスの不変状態を使用して新しいハッシュコードを導出したい場合、スーパーへの呼び出しが機能します。これはObjectに至るまでカスケードされる場合とされない場合があります(つまり、一部の祖先はsuperを呼び出さない場合があります)が、再利用によりハッシュコードを導出することができます。

@Override
public int hashCode() {
    int ancestorHash = super.hashCode();
    // now derive new hash from ancestorHash plus immutable instance vars (id fields)
}
0
Glen Best

私は同じ問題を抱えており、一意のIDを保証するものがないため、これまでのところどの回答にも満足していませんでした。

私も目的のデバッグのためにオブジェクトIDを出力したかった。 Eclipseデバッガーでは、各オブジェクトに一意のIDを指定するため、何らかの方法が必要だと思いました。

オブジェクトの「==」演算子は、2つのオブジェクトが実際に同じインスタンスである場合にのみtrueを返すという事実に基づいたソリューションを思い付きました。

import Java.util.HashMap;
import Java.util.Map;

/**
 *  Utility for assigning a unique ID to objects and fetching objects given
 *  a specified ID
 */
public class ObjectIDBank {

    /**Singleton instance*/
    private static ObjectIDBank instance;

    /**Counting value to ensure unique incrementing IDs*/
    private long nextId = 1;

    /** Map from ObjectEntry to the objects corresponding ID*/
    private Map<ObjectEntry, Long> ids = new HashMap<ObjectEntry, Long>();

    /** Map from assigned IDs to their corresponding objects */
    private Map<Long, Object> objects = new HashMap<Long, Object>();

    /**Private constructor to ensure it is only instantiated by the singleton pattern*/
    private ObjectIDBank(){}

    /**Fetches the singleton instance of ObjectIDBank */
    public static ObjectIDBank instance() {
        if(instance == null)
            instance = new ObjectIDBank();

        return instance;
    }

    /** Fetches a unique ID for the specified object. If this method is called multiple
     * times with the same object, it is guaranteed to return the same value. It is also guaranteed
     * to never return the same value for different object instances (until we run out of IDs that can
     * be represented by a long of course)
     * @param obj The object instance for which we want to fetch an ID
     * @return Non zero unique ID or 0 if obj == null
     */
    public long getId(Object obj) {

        if(obj == null)
            return 0;

        ObjectEntry objEntry = new ObjectEntry(obj);

        if(!ids.containsKey(objEntry)) {
            ids.put(objEntry, nextId);
            objects.put(nextId++, obj);
        }

        return ids.get(objEntry);
    }

    /**
     * Fetches the object that has been assigned the specified ID, or null if no object is
     * assigned the given id
     * @param id Id of the object
     * @return The corresponding object or null
     */
    public Object getObject(long id) {
        return objects.get(id);
    }


    /**
     * Wrapper around an Object used as the key for the ids map. The wrapper is needed to
     * ensure that the equals method only returns true if the two objects are the same instance
     * and to ensure that the hash code is always the same for the same instance.
     */
    private class ObjectEntry {
        private Object obj;

        /** Instantiates an ObjectEntry wrapper around the specified object*/
        public ObjectEntry(Object obj) {
            this.obj = obj;
        }


        /** Returns true if and only if the objects contained in this wrapper and the other
         * wrapper are the exact same object (same instance, not just equivalent)*/
        @Override
        public boolean equals(Object other) {
            return obj == ((ObjectEntry)other).obj;
        }


        /**
         * Returns the contained object's identityHashCode. Note that identityHashCode values
         * are not guaranteed to be unique from object to object, but the hash code is guaranteed to
         * not change over time for a given instance of an Object.
         */
        @Override
        public int hashCode() {
            return System.identityHashCode(obj);
        }
    }
}

これにより、プログラムの全期間を通じて一意のIDが保証されるはずです。ただし、IDを生成するすべてのオブジェクトへの参照を保持しているため、本番アプリケーションではおそらくこれを使用しないことに注意してください。つまり、IDを作成するオブジェクトはガベージコレクションされません。

デバッグ目的でこれを使用しているので、解放されるメモリについてはあまり気にしません。

これを変更して、メモリの解放が懸念される場合は、オブジェクトをクリアしたり、個々のオブジェクトを削除したりできます。

0
NateW