วิธีรับ ID เฉพาะของวัตถุที่แทนที่ hashCode ()


231

เมื่อคลาสใน Java ไม่ได้แทนที่hashCode ()การพิมพ์อินสแตนซ์ของคลาสนี้จะให้หมายเลขที่ไม่ซ้ำใคร

Javadoc of Object พูดเกี่ยวกับhashCode () :

เท่าที่เป็นจริงในทางปฏิบัติวิธีการ hashCode ที่กำหนดโดยวัตถุคลาสจะส่งกลับจำนวนเต็มที่แตกต่างกันสำหรับวัตถุที่แตกต่างกัน

แต่เมื่อคลาสแทนที่hashCode ()ฉันจะรับหมายเลขที่ไม่ซ้ำได้อย่างไร


33
ส่วนใหญ่ด้วยเหตุผล 'แก้จุดบกพร่อง';) ที่จะสามารถพูดได้: อาวัตถุเดียวกัน!
ivan_ivanovich_ivanoff

5
เพื่อจุดประสงค์นี้ System.identityHashcode () มีแนวโน้มที่จะมีการใช้งานบางอย่าง ฉันจะไม่พึ่งพามันสำหรับการใช้งานฟังก์ชั่นรหัสอย่างไรก็ตาม หากคุณต้องการระบุวัตถุที่ไม่ซ้ำกันคุณสามารถใช้ AspectJ และการสานรหัสใน id เฉพาะต่อวัตถุที่สร้างขึ้น แม้ว่าจะทำงานได้มากขึ้น
Brian Agnew

9
เพียงจำไว้ว่า hashCode ไม่รับประกันว่าจะไม่ซ้ำกัน แม้ว่า implementaiton ใช้ที่อยู่หน่วยความจำเป็นค่าเริ่มต้น hashCode ทำไมมันไม่เหมือนกัน เนื่องจากวัตถุได้รับการรวบรวมขยะและหน่วยความจำจะถูกนำมาใช้ซ้ำ
Igor Krivokon

8
หากคุณต้องการตัดสินใจว่าจะใช้วัตถุสองอย่างเดียวกัน == แทนที่จะเป็น hashCode () หลังไม่รับประกันว่าจะไม่ซ้ำกันแม้ในการดำเนินการเดิม
Mnementh

6
ไม่มีคำตอบที่ตอบคำถามจริงเพราะพวกเขายุ่งเหยิงในการพูดคุย hashCode () ซึ่งเป็นเรื่องบังเอิญที่นี่ ถ้าฉันดูตัวแปรอ้างอิงใน Eclipse มันจะแสดง "id = xxx" ที่ไม่เปลี่ยนรูปได้ เราจะได้รับคุณค่านั้นโดยทางโปรแกรมโดยไม่ต้องใช้ตัวสร้างรหัสของเราเองได้อย่างไร ฉันต้องการเข้าถึงค่านั้นเพื่อวัตถุประสงค์ในการดีบัก (การบันทึก) เพื่อระบุอินสแตนซ์ที่แตกต่างกันของวัตถุ ไม่มีใครรู้วิธีที่จะจัดการกับคุณค่านั้นหรือไม่?
Chris Westin

คำตอบ:


346

System.identityHashCode (yourObject)จะให้รหัสแฮช 'ดั้งเดิม' ของ yourObject เป็นจำนวนเต็ม ไม่รับประกันความเป็นเอกลักษณ์ การใช้งาน Sun JVM จะให้ค่าที่เกี่ยวข้องกับที่อยู่หน่วยความจำดั้งเดิมสำหรับวัตถุนี้ แต่นั่นเป็นรายละเอียดการใช้งานและคุณไม่ควรเชื่อถือ

แก้ไข: คำตอบแก้ไขตามความคิดเห็นของทอมด้านล่างอีกครั้ง ที่อยู่หน่วยความจำและวัตถุที่เคลื่อนที่


ให้ฉันเดาว่ามันไม่ซ้ำกันเมื่อคุณมีมากกว่า 2 ** 32 วัตถุใน JVM เดียวกัน ;) คุณสามารถชี้ให้ฉันไปยังสถานที่ที่มีการอธิบายที่ไม่ซ้ำกันได้หรือไม่? Thanx!
ivan_ivanovich_ivanoff

9
ไม่สำคัญว่ามีวัตถุกี่ชิ้นหรือมีหน่วยความจำเท่าใด ไม่จำเป็นต้องใช้ hashCode () และ identityHashCode () ในการสร้างหมายเลขเฉพาะ
Alan Moore

12
ไบรอัน: มันไม่ใช่ตำแหน่งหน่วยความจำจริงคุณได้รับที่อยู่รุ่นที่ถูก rehashed เมื่อคำนวณครั้งแรก ในวัตถุ VM ที่ทันสมัยจะย้ายเกี่ยวกับในหน่วยความจำ
Tom Hawtin - tackline

2
ดังนั้นหากวัตถุถูกสร้างขึ้นที่ที่อยู่หน่วยความจำ 0x2000 จะถูกย้ายโดย VM ดังนั้นวัตถุอื่นจะถูกสร้างขึ้นที่ 0x2000 พวกเขาจะเหมือนกันSystem.identityHashCode()หรือไม่
การชดเชย จำกัด

14
ความเป็นเอกลักษณ์ไม่ได้รับประกันเลย ... สำหรับการนำ JVM ไปใช้งานจริง รับประกันความเป็นเอกลักษณ์ไม่จำเป็นต้องมีการย้าย / การบีบอัดโดย GC หรือโครงสร้างข้อมูลขนาดใหญ่และราคาแพงสำหรับการจัดการค่าแฮชโค้ดของวัตถุสด
สตีเฟ่นซี

28

javadoc สำหรับ Object ระบุว่า

โดยทั่วไปแล้วจะถูกนำมาใช้โดยการแปลงที่อยู่ภายในของวัตถุให้เป็นจำนวนเต็ม แต่ไม่จำเป็นต้องใช้เทคนิคการติดตั้งนี้ในภาษาการเขียนโปรแกรม JavaTM

หากคลาสแทนที่ hashCode ก็หมายความว่ามันต้องการสร้างรหัสเฉพาะซึ่งจะ (หนึ่งสามารถหวัง) มีพฤติกรรมที่เหมาะสม

คุณสามารถใช้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

2
เพื่อความปลอดภัยของเธรดหนึ่งสามารถใช้AtomicLongตามคำตอบนี้
Evgeni Sergeev

หากคลาสถูกโหลดโดย classloader อื่นมันจะมีตัวแปรสแตติก 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

หากคุณกำลังมองหาhashcodeประเภท Java เมื่อคุณทำ.toString()กับวัตถุรหัสพื้นฐานคือ:

Integer.toHexString(hashCode())

0

เพียงเพื่อเพิ่มคำตอบอื่น ๆ จากมุมที่แตกต่าง

หากคุณต้องการนำรหัสแฮชโค้ดกลับมาใช้ใหม่จาก 'ด้านบน' และรับค่าใหม่โดยใช้สถานะที่เปลี่ยนไม่ได้ในชั้นเรียนของคุณการเรียกร้องให้ super จะทำงานได้ ในขณะที่สิ่งนี้อาจ / อาจไม่เรียงซ้อนไปจนถึงวัตถุ (เช่นบรรพบุรุษบางคนอาจไม่เรียกว่า super) แต่มันจะช่วยให้คุณได้รับ hashcodes โดยการนำกลับมาใช้ใหม่

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

0

มีความแตกต่างระหว่างผลตอบแทน hashCode () และ identityHashCode () เป็นไปได้ว่าวัตถุสองตัวที่ไม่เท่ากัน (ทดสอบด้วย ==) 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

ฉันมีปัญหาเดียวกันและไม่พอใจกับคำตอบใด ๆ จนถึงขณะนี้ยังไม่มีใครรับประกันรหัสที่ไม่ซ้ำกัน

ฉันก็อยากจะพิมพ์รหัสวัตถุสำหรับการแก้ไขจุดบกพร่อง ฉันรู้ว่าต้องมีวิธีในการทำเช่นนี้เพราะในดีบักเกอร์ Eclipse จะระบุรหัสเฉพาะสำหรับแต่ละวัตถุ

ฉันคิดวิธีแก้ไขตามความจริงที่ว่าตัวดำเนินการ "==" สำหรับวัตถุจะส่งกลับค่าจริงหากวัตถุสองรายการนั้นเป็นอินสแตนซ์เดียวกันจริง ๆ

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.