JavaのクラスがhashCode()をオーバーライドしない場合、このクラスのインスタンスを出力すると、適切な一意の番号が得られます。
ObjectのJavadocはhashCode()について述べています:
合理的に実用的である限り、Objectクラスで定義されたhashCodeメソッドは、個別のオブジェクトに対して個別の整数を返します。
しかし、クラスがhashCode()をオーバーライドするとき、どのようにしてその一意の番号を取得できますか?
JavaのクラスがhashCode()をオーバーライドしない場合、このクラスのインスタンスを出力すると、適切な一意の番号が得られます。
ObjectのJavadocはhashCode()について述べています:
合理的に実用的である限り、Objectクラスで定義されたhashCodeメソッドは、個別のオブジェクトに対して個別の整数を返します。
しかし、クラスがhashCode()をオーバーライドするとき、どのようにしてその一意の番号を取得できますか?
回答:
System.identityHashCode(yourObject)は、yourObjectの「元の」ハッシュコードを整数として提供します。一意性は必ずしも保証されていません。Sun JVM実装は、このオブジェクトの元のメモリアドレスに関連する値を提供しますが、これは実装の詳細であるため、それに依存しないでください。
編集:以下のトムのコメントに従って回答を修正しました。メモリアドレスと移動オブジェクト。
System.identityHashCode()
ですか?
Objectのjavadocは、
これは通常、オブジェクトの内部アドレスを整数に変換することによって実装されますが、この実装手法はJavaTMプログラミング言語では必要ありません。
クラスがhashCodeをオーバーライドする場合、それは特定のIDを生成したいことを意味します。
System.identityHashCodeを使用して、任意のクラスのIDを取得できます。
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;
}
}
たぶん、この迅速で汚れたソリューションはうまくいくでしょうか?
public class A {
static int UNIQUE_ID = 0;
int uid = ++UNIQUE_ID;
public int hashCode() {
return uid;
}
}
これは、初期化されるクラスのインスタンスの数も示します。
System.identityHashCode
はより良い解決策だと思います
AtomicLong
は、この回答のように使用できます。
変更できるクラスの場合は、クラス変数を宣言できますstatic java.util.concurrent.atomic.AtomicInteger nextInstanceId
。(明らかな方法で初期値を指定する必要があります。)次に、インスタンス変数を宣言しますint instanceId = nextInstanceId.getAndIncrement()
。
私は複数のスレッドで作成されたオブジェクトがあり、シリアライズ可能な場合に機能するこのソリューションを思いつきました:
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;
}
}
別の角度から他の答えを増やすためだけに。
「上記」のハッシュコードを再利用し、クラスの不変状態を使用して新しいハッシュコードを導出する場合は、superの呼び出しが機能します。これはObjectまでカスケードする場合とそうでない場合があります(つまり、一部の祖先がsuperを呼び出さない場合があります)が、再利用によりハッシュコードを導出できます。
@Override
public int hashCode() {
int ancestorHash = super.hashCode();
// now derive new hash from ancestorHash plus immutable instance vars (id fields)
}
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");
}
}
}
同じ問題があり、一意の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を作成したオブジェクトはガベージコレクションされません。
これをデバッグの目的で使用しているので、メモリが解放されることにあまり関心がありません。
これを変更して、メモリの解放が問題になる場合は、オブジェクトをクリアしたり、個々のオブジェクトを削除したりできます。