hashCode()をオーバーライドするオブジェクトの一意のIDを取得するにはどうすればよいですか?


231

JavaのクラスがhashCode()をオーバーライドしない場合、このクラスのインスタンスを出力すると、適切な一意の番号が得られます。

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

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

しかし、クラスがhashCode()をオーバーライドするとき、どのようにしてその一意の番号を取得できますか?


33
主に「デバッグ」の理由で;)言えるように:ああ、同じオブジェクト!
ivan_ivanovich_ivanoff 2009年

5
この目的のために、System.identityHashcode()が使用される可能性があります。ただし、コードの機能を実装するためにこれに依存することはありません。オブジェクトを一意に識別したい場合は、作成されたオブジェクトごとに一意のIDでAspectJとコードウィーブを使用できます。しかし、さらなる作業
ブライアンアグニュー

9
hashCodeが一意であることが保証されていないことに注意してください。実装がデフォルトのハッシュコードとしてメモリアドレスを使用する場合でも。なぜユニークではないのですか?オブジェクトはガベージコレクションされ、メモリが再利用されるためです。
イゴールクリヴォコン2009年

8
決定したい場合、2つのオブジェクトが同じであれば、hashCode()の代わりに==を使用します。後者は、元の実装でさえ、一意であることが保証されていません。
Mnementh 2009年

6
ここで偶発的だったhashCode()の議論に巻き込まれるため、どの回答も本当の質問に答えるものではありません。Eclipseで参照変数を見ると、一意の不変の「id = xxx」が表示されます。独自のIDジェネレーターを使用せずに、プログラムでその値を取得するにはどうすればよいですか?オブジェクトの個別のインスタンスを識別するために、デバッグ(ログ)のためにその値にアクセスしたい。誰かがその価値を手に入れる方法を知っていますか?
クリスウェスティン

回答:


346

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

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


推測させてください:同じJVMに2 ** 32を超えるオブジェクトがある場合、それは一意ではありませんか?;)一意性が説明されていない場所を教えてもらえますか?ありがとう!
ivan_ivanovich_ivanoff 2009年

9
オブジェクトの数やメモリの量は関係ありません。一意の番号を生成するために、hashCode()もidentityHashCode()も必要ありません。
アランムーア

12
ブライアン:実際のメモリの場所ではありません。最初に計算されたときに、アドレスの再ハッシュされたバージョンを取得することがあります。最近のVMでは、オブジェクトはメモリ内を動き回ります。
トム・ホーティン-09年

2
したがって、オブジェクトがメモリアドレス0x2000で作成され、VMによって移動され、他のオブジェクトが0x2000で作成された場合、それらは同じSystem.identityHashCode()ですか?
限定的な贖罪2013年

14
一意性はまったく保証されていませ ...実際のJVM実装では。一意性の保証には、GCによる再配置や圧縮が不要か、ライブオブジェクトのハッシュコード値を管理するための大きくて高価なデータ構造が必要です。
スティーブンC

28

Objectのjavadocは、

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

クラスがhashCodeをオーバーライドする場合、それは特定のIDを生成したいことを意味します。

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


7

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

6

たぶん、この迅速で汚れたソリューションはうまくいくでしょうか?

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

    public int hashCode() {
        return uid;
    }
}

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


4
これは、クラスのソースコードにアクセスできることを前提としています
pablisco

ソースコードにアクセスできない場合は、ソースコードから拡張して、拡張クラスを使用してください。簡単、簡単、そしてダーティなソリューションですが、機能します。
John Pang

1
常に機能するとは限りません。クラスは最終的なものになる可能性があります。私System.identityHashCodeはより良い解決策だと思います
pablisco 2015年

2
スレッドセーフでAtomicLongは、この回答のように使用できます。
Evgeni Sergeev 2016年

クラスが異なるクラスローダーによってロードされた場合、異なるUNIQUE_ID静的変数が含まれますが、正しいですか?
cupiqi09

4

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


2

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

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

2
// looking for that last hex?
org.joda.DateTime@57110da6

オブジェクトでhashcodeJavaタイプを調べて.toString()いる場合、基礎となるコードは次のとおりです。

Integer.toHexString(hashCode())

0

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

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

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

0

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

同じ問題があり、一意の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を作成したオブジェクトはガベージコレクションされません。

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.