Bagaimana cara mendapatkan ID unik dari objek yang mengabaikan kode hash ()?

231

Ketika sebuah kelas di Java tidak menimpa kode hash () , mencetak sebuah instance dari kelas ini memberikan angka unik yang bagus.

Javadoc of Object mengatakan tentang kode hash () :

Sebanyak yang praktis, metode kode hash yang didefinisikan oleh Object class mengembalikan integer yang berbeda untuk objek yang berbeda.

Tetapi ketika kelas menimpa kode hash () , bagaimana saya bisa mendapatkan nomor uniknya?

ivan_ivanovich_ivanoff
sumber
33
Sebagian besar karena alasan 'debugging';) Untuk dapat mengatakan: Ah, objek yang sama!
ivan_ivanovich_ivanoff
5
Untuk tujuan ini, System.identityHashcode () kemungkinan digunakan. Saya tidak akan bergantung padanya untuk mengimplementasikan fungsi kode. Jika Anda ingin mengidentifikasi objek secara unik, Anda bisa menggunakan AspectJ dan menenun kode dalam id unik per objek yang dibuat. Lebih banyak pekerjaan,
317 Brian Agnew
9
Perlu diingat bahwa kode hasT TIDAK dijamin unik. Bahkan jika implementaiton menggunakan alamat memori sebagai kode hash default. Kenapa tidak unik? Karena benda dikumpulkan sampah, dan memori digunakan kembali.
Igor Krivokon
8
Jika Anda ingin memutuskan, apakah dua objek adalah penggunaan yang sama == bukan hashCode (). Yang terakhir tidak dijamin unik, bahkan dalam implementasi aslinya.
Mnementh
6
Tak satu pun dari jawaban menjawab pertanyaan nyata karena mereka terlibat dalam membahas kode hash (), yang kebetulan di sini. Jika saya melihat variabel referensi di Eclipse, ini menunjukkan kepada saya "id = xxx" yang unik dan tidak berubah. Bagaimana kita mencapai nilai itu secara terprogram tanpa harus menggunakan generator id kita sendiri? Saya ingin akses ke nilai itu untuk keperluan debugging (logging) untuk mengidentifikasi contoh objek yang berbeda. Adakah yang tahu cara mendapatkan nilai itu?
Chris Westin

Jawaban:

346

System.identityHashCode (yourObject) akan memberikan kode hash 'asli' dari yourObject sebagai integer. Keunikan belum tentu dijamin. Implementasi Sun JVM akan memberi Anda nilai yang terkait dengan alamat memori asli untuk objek ini, tapi itu detail implementasi dan Anda tidak harus bergantung padanya.

EDIT: Jawaban diubah mengikuti komentar Tom di bawah ini. alamat memori dan objek bergerak.

Brian Agnew
sumber
Biar saya tebak: itu tidak unik, ketika Anda memiliki lebih dari 2 ** 32 objek di JVM yang sama? ;) Dapatkah Anda mengarahkan saya ke suatu tempat, di mana keunikan itu digambarkan? Terima kasih!
ivan_ivanovich_ivanoff
9
Tidak masalah berapa banyak objek yang ada, atau berapa banyak memori yang ada. HashCode () maupun identityHashCode () tidak diperlukan untuk menghasilkan nomor unik.
Alan Moore
12
Brian: Ini bukan lokasi memori yang sebenarnya, Anda mendapatkan versi yang diulang dari alamat saat pertama kali dihitung. Dalam sebuah benda VM modern akan bergerak dalam memori.
Tom Hawtin - tackline
2
Jadi jika suatu objek dibuat pada alamat memori 0x2000, kemudian dipindahkan oleh VM, maka objek lain dibuat pada 0x2000, akankah mereka memiliki yang sama System.identityHashCode()?
Penebusan Terbatas
14
Keunikan tidak dijamin sama sekali ... untuk implementasi JVM yang praktis. Keunikan yang dijamin tidak memerlukan relokasi / pemadatan oleh GC, atau struktur data yang besar dan mahal untuk mengelola nilai kode hash objek hidup.
Stephen C
28

Javadoc untuk Object menentukan itu

Ini biasanya diterapkan dengan mengubah alamat internal objek menjadi integer, tetapi teknik implementasi ini tidak diperlukan oleh bahasa pemrograman JavaTM.

Jika sebuah kelas menimpa kode hash, itu berarti ia ingin menghasilkan id tertentu, yang (dapat berharap) memiliki perilaku yang benar.

Anda bisa menggunakan System.identityHashCode untuk mendapatkan id itu untuk kelas apa pun.

Valentin Rocher
sumber
7

hashCode()metode bukan untuk menyediakan pengidentifikasi unik untuk objek. Ini agak mencerna keadaan objek (yaitu nilai-nilai bidang anggota) ke integer tunggal. Nilai ini sebagian besar digunakan oleh beberapa struktur data berbasis hash seperti peta dan set untuk secara efektif menyimpan dan mengambil objek.

Jika Anda membutuhkan pengenal untuk objek Anda, saya sarankan Anda untuk menambahkan metode Anda sendiri daripada mengganti hashCode. Untuk tujuan ini, Anda dapat membuat antarmuka dasar (atau kelas abstrak) seperti di bawah ini.

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

Contoh penggunaan:

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

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

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

Mungkin solusi cepat dan kotor ini akan berhasil?

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

    public int hashCode() {
        return uid;
    }
}

Ini juga memberikan jumlah instance kelas yang diinisialisasi.

John Pang
sumber
4
Ini mengasumsikan bahwa Anda memiliki akses ke kode sumber kelas
pablisco
Jika Anda tidak dapat mengakses kode sumber, cukup rentangkan dan gunakan kelas extended. Solusi yang cepat, mudah, dan kotor tetapi berhasil.
John Pang
1
itu tidak selalu berhasil. Kelasnya bisa final. Saya pikir System.identityHashCodeini solusi yang lebih baik
pablisco
2
Untuk keamanan utas, seseorang dapat menggunakan AtomicLongseperti pada jawaban ini .
Evgeni Sergeev
Jika kelas dimuat oleh classloader yang berbeda, itu akan memiliki variabel statis UNIQUE_ID yang berbeda, apakah saya benar?
cupiqi09
4

Jika kelas yang bisa Anda modifikasi, Anda bisa mendeklarasikan variabel kelas static java.util.concurrent.atomic.AtomicInteger nextInstanceId. (Anda harus memberikannya nilai awal dengan cara yang jelas.) Kemudian deklarasikan variabel instan int instanceId = nextInstanceId.getAndIncrement().

Aaron Mansheim
sumber
2

Saya datang dengan solusi ini yang berfungsi dalam kasus saya di mana saya memiliki objek yang dibuat pada banyak utas dan serializable:

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;
    }
}
Howard Swope
sumber
2
// looking for that last hex?
org.joda.DateTime@57110da6

Jika Anda melihat ke hashcodetipe Java ketika Anda melakukan .toString()pada suatu objek kode yang mendasarinya adalah ini:

Integer.toHexString(hashCode())
Frankie
sumber
0

Hanya untuk menambah jawaban lain dari sudut yang berbeda.

Jika Anda ingin menggunakan kembali kode hash dari 'di atas' dan mendapatkan yang baru menggunakan status immutatable kelas Anda, maka panggilan ke super akan berfungsi. Walaupun ini mungkin / mungkin tidak mengalir hingga Object (yaitu beberapa leluhur mungkin tidak memanggil super), ini akan memungkinkan Anda untuk mendapatkan kode hash dengan menggunakan kembali.

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

Ada perbedaan antara pengembalian hashCode () dan identityHashCode (). Ada kemungkinan bahwa untuk dua objek yang tidak sama (diuji dengan ==) o1, o2 hashCode () dapat sama. Lihat contoh di bawah ini bagaimana ini benar.

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");
        }
    }
}
Pengembang Marius Žilėnas
sumber
0

Saya memiliki masalah yang sama dan tidak puas dengan salah satu jawaban sejauh ini karena tidak satupun dari mereka yang menjamin ID unik.

Saya juga ingin mencetak objek ID untuk tujuan debugging. Saya tahu pasti ada beberapa cara untuk melakukannya, karena di Eclipse debugger, ini menentukan ID unik untuk setiap objek.

Saya datang dengan solusi berdasarkan pada kenyataan bahwa operator "==" untuk objek hanya mengembalikan true jika dua objek sebenarnya adalah contoh yang sama.

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

Saya percaya bahwa ini harus memastikan ID unik sepanjang masa program. Perhatikan, bagaimanapun, bahwa Anda mungkin tidak ingin menggunakannya dalam aplikasi produksi karena ia mempertahankan referensi ke semua objek yang Anda hasilkan ID. Ini berarti bahwa objek apa pun yang Anda buat ID tidak akan pernah menjadi sampah yang dikumpulkan.

Karena saya menggunakan ini untuk keperluan debug, saya tidak terlalu khawatir dengan memori yang dibebaskan.

Anda bisa memodifikasinya untuk memungkinkan membersihkan Obyek atau menghapus objek individual jika membebaskan memori menjadi masalah.

NateW
sumber