2016-07-15 10 views
17

मुझे लगता है कि मानक जावा हैश मैप में डुप्लिकेट कुंजी मिल रही है। "डुप्लिकेट" से, मेरा मतलब है कि चाबियाँ उनके equals() विधि के बराबर होती हैं।मुझे जावा हैश मैप में डुप्लिकेट कुंजी क्यों मिल रही हैं?

import java.util.Map; 
import java.util.HashMap; 

public class User { 
    private String userId; 
    public User(String userId) { 
     this.userId = userId; 
    } 
    public boolean equals(User other) { 
     return userId.equals(other.getUserId()); 
    } 
    public int hashCode() { 
     return userId.hashCode(); 
    } 
    public String toString() { 
     return userId; 
    } 

    public static void main(String[] args) { 
     User arvo1 = new User("Arvo-Part"); 
     User arvo2 = new User("Arvo-Part"); 
     Map<User,Integer> map = new HashMap<User,Integer>(); 
     map.put(arvo1,1); 
     map.put(arvo2,2); 

     System.out.println("arvo1.equals(arvo2): " + arvo1.equals(arvo2)); 
     System.out.println("map: " + map.toString()); 
     System.out.println("arvo1 hash: " + arvo1.hashCode()); 
     System.out.println("arvo2 hash: " + arvo2.hashCode()); 
     System.out.println("map.get(arvo1): " + map.get(arvo1)); 
     System.out.println("map.get(arvo2): " + map.get(arvo2)); 
     System.out.println("map.get(arvo2): " + map.get(arvo2)); 
     System.out.println("map.get(arvo1): " + map.get(arvo1)); 
    } 
} 

यहाँ और जिसके परिणामस्वरूप उत्पादन होता है:: यहाँ समस्याग्रस्त कोड है

arvo1.equals(arvo2): true 
map: {Arvo-Part=1, Arvo-Part=2} 
arvo1 hash: 164585782 
arvo2 hash: 164585782 
map.get(arvo1): 1 
map.get(arvo2): 2 
map.get(arvo2): 2 
map.get(arvo1): 1 

आप देख सकते हैं, दो User वस्तुओं पर equals() विधि true लौटा रहा है और उनके हैश कोड एक ही हैं , फिर भी वे map में एक अलग key बनाते हैं। इसके अलावा, map पिछले चार get() कॉल में दो User कुंजी के बीच अंतर करना जारी रखता है।

यह सीधे विपरीत है documentation:

अधिक औपचारिक रूप से, इस नक्शे को एक मूल्य v ऐसा है कि (कुंजी == बातिल कश्मीर == बातिल के लिए एक महत्वपूर्ण कश्मीर से एक मानचित्रण शामिल है: key.equals (के)), तो यह विधि v देता है; अन्यथा यह शून्य हो जाता है। (इस तरह के एक मैपिंग में हो सकता है।)

क्या यह एक बग है? क्या मुझसे कोई चूक हो रही है? मैं जावा संस्करण 1.8.0_92 चला रहा हूं, जिसे मैंने होमब्रू के माध्यम से स्थापित किया है।

संपादित करें: यह प्रश्न इस other question का डुप्लिकेट के रूप में चिह्नित किया गया है, लेकिन जबकि अन्य प्रश्न मान लिया गया त्रुटि hashCode() के साथ निहित है मैं इस सवाल छोड़ देंगे के रूप में है, क्योंकि यह equals() के साथ एक प्रतीयमान विसंगति पहचान करता है,। उम्मीद है कि इस प्रश्न की उपस्थिति इस मुद्दे को अधिक आसानी से खोजने योग्य बना देगी।

+13

@ Override' अपने 'equals' को जोड़ने का प्रयास करें' और 'हैशकोड' विधियां (हमेशा एक सर्वोत्तम अभ्यास) और देखें कि क्या आपको कोई उपयोगी जानकारी मिलती है या नहीं। – chrylis

+2

भविष्य में इस तरह के टाइपो, या गलतियों को अनुमति देने के लिए, हमेशा अपने आईडीई को आपके लिए विधियां उत्पन्न करने दें। फिर उन्हें अपनी तरह दिखने के लिए उन्हें ट्विक करें। इसने '@ ओवरराइड' एनोटेशन के साथ सही तरीके बनाए होंगे। – Magnilex

उत्तर

28

मुद्दा अपने equals() विधि में निहित है। Object.equals() का हस्ताक्षर equals(OBJECT) है, लेकिन आपके मामले में यह equals(USER) है, इसलिए ये दो पूरी तरह से अलग-अलग विधियां हैं और हैशैप Object पैरामीटर के साथ कॉल कर रहा है। आप यह सत्यापित कर सकते हैं कि आपके बराबर पर @Override एनोटेशन डालने से - यह एक कंपाइलर त्रुटि उत्पन्न करेगा।

के बराबर होती है विधि होना चाहिए:

@Override 
    public boolean equals(Object other) { 
    if(other instanceof User){ 
     User user = (User) other; 
     return userId.equals(user.userId); 
    } 

    return false; 
} 

एक सबसे अच्छा अभ्यास तुम हमेशा @Override तरीकों आप ओवरराइड पर रखना चाहिए के रूप में - यह आप मुसीबत का एक बहुत कुछ बचा सकता है।

+0

मेरे लिए दिलचस्प सवाल: क्या गतिशील टाइपिंग कॉल के लिए सबसे अच्छा मिलान नहीं करना चाहिए? ऑब्जेक्ट ओ = नया उपयोगकर्ता(); -> o.equals (o) 'रनटाइम-प्रकारों के लिए सर्वोत्तम मिलान में भेजा जा सकता है। - लेकिन जावा सही ओवरलोड को खोजने के लिए स्थैतिक संकलन-समय टाइपिंग का उपयोग करता है, गतिशील रनटाइम-पैरामीटर नहीं - आप इसे पूर्णता के लिए अपने उत्तर में शामिल कर सकते हैं! – Falco

+0

इसे सर्वश्रेष्ठ 'संकलन समय' प्रकार पर भेजा जाएगा। जावा में डायनामिक टाइपिंग –

+1

हम्म नहीं है, आप याद कर रहे हैं कि हैमैप को हमेशा 'बराबर (ऑब्जेक्ट)' कॉल करने के लिए संकलित किया गया है, इसलिए यदि आपके पास 'बराबर (उपयोगकर्ता) है तो यह कभी भी इसे कॉल नहीं करेगा। लेकिन अगर आप अपने कोड में 'उपयोगकर्ता उपयोगकर्ता = नया उपयोगकर्ता() करते हैं; user.equals (नया उपयोगकर्ता()) ', 'बराबर (उपयोगकर्ता)' कहा जाएगा। लेकिन अगर आप अपने उदाहरण के रूप में करते हैं 'ऑब्जेक्ट उपयोगकर्ता = नया उपयोगकर्ता(); user.equals (नया उपयोगकर्ता()) '' बराबर (ऑब्जेक्ट) 'कहा जाएगा। मुझे उम्मीद है कि इसे –

13

आपका बराबरी विधि equals ओवरराइड नहीं करता, और Map में प्रकार कार्यावधि में मिट जाता है, ताकि वास्तविक बराबर होती विधि कहा जाता equals(Object) है। आपका बराबरी अधिक इस तरह दिखना चाहिए:

@Override 
public boolean equals(Object other) { 
    if (!(other instanceof User)) 
     return false; 
    User u = (User)other; 
    return userId.equals(u.userId); 
} 
3

ठीक है, तो सबसे पहले, कोड संकलित नहीं होता है। इस विधि लापता:

other.getUserId() 

लेकिन उस के अलावा, आप @Override equals विधि के लिए, ग्रहण की तरह आईडीई भी btw equals और hashCode पैदा करने में मदद कर सकते की आवश्यकता होगी।

@Override 
public boolean equals(Object obj) 
{ 
    if(this == obj) 
    return true; 
    if(obj == null) 
    return false; 
    if(getClass() != obj.getClass()) 
    return false; 
    User other = (User) obj; 
    if(userId == null) 
    { 
    if(other.userId != null) 
     return false; 
    } 
    else if(!userId.equals(other.userId)) 
    return false; 
    return true; 
} 
2

रूप Chrylis का सुझाव दिया, दोनों hashCode करने के लिए @Override और जोड़कर equals आप एक संकलन त्रुटि क्योंकि equals विधि के हस्ताक्षर public boolean equals(Object other) है, मिल जाएगा ताकि आप वास्तव में डिफ़ॉल्ट अधिभावी नहीं कर रहे हैं (वस्तु वर्ग से) विधि बराबर है। यह स्थिति की ओर जाता है कि दोनों उपयोगकर्ता hashMap (हैशकोड ओवरराइडन के अंदर एक ही बाल्टी में समाप्त होते हैं और दोनों उपयोगकर्ताओं के पास एक ही हैश कोड होता है), लेकिन जब समानता के लिए चेक किया जाता है तो वे डिफ़ॉल्ट बराबर विधि के रूप में अलग होते हैं जिसका अर्थ है कि स्मृति पते की तुलना की जाती है।

अपेक्षित परिणाम प्राप्त करने के लिए निम्नलिखित के साथ equals विधि बदलें:

@Override 
public boolean equals(Object other) { 
    return getUserId().equals(((User)other).getUserId()); 
} 
+2

'बराबर' फेंकना नहीं चाहिए (यदि आपका संस्करण मेल नहीं खाता है तो आपका संस्करण होगा)। – Hulk

3

अन्य लोगों की तरह जवाब आप equals विधि हस्ताक्षर के साथ एक समस्या थी। जावा के अनुसार सबसे अच्छा अभ्यास के बराबर आप को लागू करना चाहिए निम्नलिखित की तरह बराबर है:

@Override 
    public boolean equals(Object o) { 
    if (this == o) return true; 
    if (o == null || getClass() != o.getClass()) return false; 

    User user = (User) o; 

    return userId.equals(user.userId); 
    } 

यही बात hashCode() विधि के लिए लागू होता है।

map: {Arvo-Part=2} 

इसका कारण यह है है: देखने Overriding equals() and hashCode() method in Java

दूसरी समस्या

आप डुप्लिकेट की जरूरत नहीं है अब और अब, लेकिन आप एक नई समस्या है, तो अपने HashMap केवल एक ही तत्व शामिल User ऑब्जेक्ट्स एक ही स्ट्रिंग (JVM String Interning) का संदर्भ दे रहे हैं, और HashMap परिप्रेक्ष्य से आपकी दो वस्तुएं समान हैं, क्योंकि दोनों ऑब्जेक्ट्स eq हैं हैशकोड में uivalent और तरीकों के बराबर है। इसलिए जब आप अपना दूसरा ऑब्जेक्ट HashMap पर जोड़ते हैं तो आप अपना पहला ओवरराइड करते हैं। इस समस्या से बचने के लिए, आप प्रत्येक उपयोगकर्ता

अपने उपयोगकर्ताओं पर एक साधारण प्रदर्शन के लिए एक अद्वितीय ID का उपयोग सुनिश्चित करें:

enter image description here

संबंधित मुद्दे