Làm cách nào để có được ID duy nhất của một đối tượng ghi đè hashCode ()?


231

Khi một lớp trong Java không ghi đè hashCode () , việc in một thể hiện của lớp này sẽ mang lại một số duy nhất đẹp.

Javadoc của Object nói về hashCode () :

Nhiều như là thực tế hợp lý, phương thức hashCode được xác định bởi lớp Object sẽ trả về các số nguyên riêng biệt cho các đối tượng riêng biệt.

Nhưng khi lớp ghi đè hashCode () , làm thế nào tôi có thể nhận được số duy nhất của nó?


33
Chủ yếu là vì lý do 'gỡ lỗi';) Để có thể nói: Ah, cùng một đối tượng!
ivan_ivanovich_ivanoff

5
Với mục đích này, System.identityHashcode () có khả năng được sử dụng. Tôi sẽ không dựa vào nó để thực hiện chức năng mã, tuy nhiên. Nếu bạn muốn xác định các đối tượng duy nhất, bạn có thể sử dụng AspectJ và mã dệt trong một id duy nhất cho mỗi đối tượng được tạo. Tuy nhiên, nhiều công việc hơn
Brian Agnew

9
Chỉ cần lưu ý rằng hashCode KHÔNG được đảm bảo là duy nhất. Ngay cả khi execaiton sử dụng địa chỉ bộ nhớ làm hashCode mặc định. Tại sao nó không phải là duy nhất? Bởi vì các đối tượng được thu gom rác và bộ nhớ được sử dụng lại.
Igor Krivokon

8
Nếu bạn muốn quyết định, nếu hai đối tượng là cùng sử dụng == thay vì hashCode (). Cái sau không được đảm bảo là duy nhất, ngay cả trong việc thực hiện ban đầu.
Mnementh

6
Không có câu trả lời nào trả lời được câu hỏi thực sự bởi vì họ gặp rắc rối khi thảo luận về hashCode (), điều này là ngẫu nhiên ở đây. Nếu tôi nhìn vào các biến tham chiếu trong Eclipse, nó cho tôi thấy một "id = xxx" bất biến duy nhất. Làm thế nào để chúng ta có được giá trị đó theo chương trình mà không phải sử dụng trình tạo id riêng? Tôi muốn truy cập vào giá trị đó cho mục đích gỡ lỗi (ghi nhật ký) để xác định các trường hợp khác nhau của các đối tượng. Có ai biết làm thế nào để có được bàn tay của bạn về giá trị đó?
Chris Westin

Câu trả lời:


346

System.identityHashCode (yourObject) sẽ cung cấp mã băm 'gốc' của yourObject dưới dạng một số nguyên. Tính độc đáo không nhất thiết phải được đảm bảo. Việc triển khai Sun JVM sẽ cung cấp cho bạn một giá trị liên quan đến địa chỉ bộ nhớ ban đầu cho đối tượng này, nhưng đó là một chi tiết triển khai và bạn không nên dựa vào nó.

EDIT: Trả lời sửa đổi sau bình luận của Tom bên dưới re. địa chỉ bộ nhớ và các đối tượng chuyển động.


Hãy để tôi đoán: nó không phải là duy nhất, khi bạn có nhiều hơn 2 ** 32 đối tượng trong cùng một JVM? ;) Bạn có thể chỉ cho tôi một nơi nào đó, nơi nó được mô tả không duy nhất không? Thanx!
ivan_ivanovich_ivanoff

9
Không quan trọng có bao nhiêu đối tượng, hoặc có bao nhiêu bộ nhớ. Không cần hashCode () hay nhận dạngHashCode () để tạo một số duy nhất.
Alan Moore

12
Brian: Đây không phải là vị trí bộ nhớ thực tế, bạn tình cờ nhận được một phiên bản được làm lại của một địa chỉ khi lần đầu tiên được tính toán. Trong một đối tượng VM hiện đại sẽ di chuyển trong bộ nhớ.
Tom Hawtin - tackline

2
Vì vậy, nếu một đối tượng được tạo tại địa chỉ bộ nhớ 0x2000, sau đó được VM di chuyển, thì một đối tượng khác được tạo ở 0x2000, liệu chúng có giống nhau System.identityHashCode()không?
Chuộc tội có giới hạn

14
Tính duy nhất không được đảm bảo ở tất cả ... đối với việc triển khai JVM thực tế. Tính duy nhất được đảm bảo yêu cầu không có sự di chuyển / nén bởi GC hoặc cấu trúc dữ liệu lớn và đắt tiền để quản lý các giá trị băm của các đối tượng sống.
Stephen C

28

Javadoc cho Object chỉ định rằng

Điều này thường được thực hiện bằng cách chuyển đổi địa chỉ bên trong của đối tượng thành số nguyên, nhưng kỹ thuật lập trình JavaTM này không bắt buộc.

Nếu một lớp ghi đè hashCode, điều đó có nghĩa là nó muốn tạo một id cụ thể, điều này (người ta có thể hy vọng) có hành vi đúng.

Bạn có thể sử dụng System.identityHashCode để lấy id đó cho bất kỳ lớp nào.


6

Có lẽ giải pháp nhanh chóng, bẩn này sẽ làm việc?

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

    public int hashCode() {
        return uid;
    }
}

Điều này cũng đưa ra số lượng thể hiện của một lớp được khởi tạo.


4
Điều này giả định rằng bạn có quyền truy cập vào mã nguồn của lớp
pottaisco

Nếu bạn không thể truy cập mã nguồn, chỉ cần mở rộng từ nó và sử dụng lớp mở rộng. Đơn giản là giải pháp nhanh chóng, dễ dàng và bẩn nhưng nó hoạt động.
John Pang

1
nó không luôn luôn hoạt động. Lớp học có thể là cuối cùng. Tôi nghĩ System.identityHashCodelà một giải pháp tốt hơn
pottaisco

2
Đối với an toàn luồng, người ta có thể sử dụng AtomicLongnhư trong câu trả lời này .
Evgeni Sergeev

Nếu lớp được tải bởi một trình nạp lớp khác, nó sẽ có các biến tĩnh UNIQUE_ID khác nhau, tôi có đúng không?
cupiqi09

6

hashCode()phương thức không phải là để cung cấp một định danh duy nhất cho một đối tượng. Nó thay vì tiêu hóa trạng thái của đối tượng (tức là các giá trị của các trường thành viên) thành một số nguyên duy nhất. Giá trị này chủ yếu được sử dụng bởi một số cấu trúc dữ liệu dựa trên hàm băm như bản đồ và bộ để lưu trữ và truy xuất các đối tượng một cách hiệu quả.

Nếu bạn cần một mã định danh cho các đối tượng của mình, tôi khuyên bạn nên thêm phương thức của riêng mình thay vì ghi đè hashCode. Với mục đích này, bạn có thể tạo một giao diện cơ sở (hoặc một lớp trừu tượng) như bên dưới.

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

Ví dụ sử dụng:

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

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

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

4

Nếu đó là một lớp mà bạn có thể sửa đổi, bạn có thể khai báo một biến lớp static java.util.concurrent.atomic.AtomicInteger nextInstanceId. (Bạn sẽ phải cung cấp cho nó một giá trị ban đầu theo cách rõ ràng.) Sau đó khai báo một biến thể hiện int instanceId = nextInstanceId.getAndIncrement().


2

Tôi đã đưa ra giải pháp này hoạt động trong trường hợp của tôi khi tôi có các đối tượng được tạo trên nhiều luồng và được tuần tự hóa:

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

Nếu bạn đang xem xét các hashcodeloại Java khi bạn thực hiện một .toString()đối tượng, mã bên dưới là:

Integer.toHexString(hashCode())

0

Chỉ để tăng các câu trả lời khác từ một góc độ khác.

Nếu bạn muốn sử dụng lại (các) mã băm từ 'ở trên' và lấy ra các mã mới bằng trạng thái bất biến của lớp bạn, thì một cuộc gọi đến siêu sẽ hoạt động. Mặc dù điều này có thể / có thể không xếp tầng tới Object (tức là một số tổ tiên có thể không gọi siêu), nó sẽ cho phép bạn lấy mã băm bằng cách sử dụng lại.

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

0

Có một sự khác biệt giữa trả về hashCode () và idHashCode (). Có thể đối với hai đối tượng không bằng nhau (được thử nghiệm với ==) o1, o2 hashCode () có thể giống nhau. Xem ví dụ dưới đây như thế nào là đúng.

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

Tôi đã có cùng một vấn đề và không hài lòng với bất kỳ câu trả lời nào cho đến nay vì không ai trong số họ đảm bảo ID duy nhất.

Tôi cũng muốn in ID đối tượng để gỡ lỗi có mục đích. Tôi biết rằng phải có một số cách để làm điều đó, bởi vì trong trình gỡ lỗi Eclipse, nó chỉ định các ID duy nhất cho mỗi đối tượng.

Tôi đã đưa ra một giải pháp dựa trên thực tế là toán tử "==" cho các đối tượng chỉ trả về true nếu hai đối tượng thực sự là cùng một thể hiện.

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

Tôi tin rằng điều này sẽ đảm bảo ID duy nhất trong suốt vòng đời của chương trình. Tuy nhiên, lưu ý rằng có lẽ bạn không muốn sử dụng điều này trong một ứng dụng sản xuất vì nó duy trì các tham chiếu đến tất cả các đối tượng mà bạn tạo ID. Điều này có nghĩa là bất kỳ đối tượng nào bạn tạo ID sẽ không bao giờ được thu gom rác.

Vì tôi đang sử dụng điều này cho mục đích gỡ lỗi, tôi không quá quan tâm đến việc bộ nhớ được giải phóng.

Bạn có thể sửa đổi điều này để cho phép xóa Đối tượng hoặc xóa các đối tượng riêng lẻ nếu giải phóng bộ nhớ là một vấn đề đáng lo ngại.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.