CodeGym /Java Blog /अनियमित /बराबर और हैशकोड विधियाँ: सर्वोत्तम अभ्यास
John Squirrels
स्तर 41
San Francisco

बराबर और हैशकोड विधियाँ: सर्वोत्तम अभ्यास

अनियमित ग्रुप में प्रकाशित
नमस्ते! आज हम Java में दो महत्वपूर्ण मेथड्स के बारे में बात करेंगे: equals()और hashCode(). यह पहली बार नहीं है जब हम उनसे मिले हैं: CodeGym कोर्स एक छोटे पाठ के साथ शुरू होता है equals()— इसे पढ़ें यदि आप इसे भूल गए हैं या इसे पहले नहीं देखा है... बराबर और हैशकोड विधियाँ: सर्वोत्तम अभ्यास - 1आज के पाठ में, हम इसके बारे में बात करेंगे इन अवधारणाओं को विस्तार से और मेरा विश्वास करो, हमारे पास बात करने के लिए कुछ है! लेकिन इससे पहले कि हम नए पर आगे बढ़ें, जो हमने पहले ही कवर कर लिया है उसे ताज़ा करें :) जैसा कि आपको याद है, आमतौर पर ऑपरेटर का उपयोग करके दो वस्तुओं की तुलना करना एक बुरा विचार है, ==क्योंकि ==संदर्भों की तुलना करता है। हाल ही के पाठ से कारों के साथ हमारा उदाहरण यहां दिया गया है:

public class Car {

   String model;
   int maxSpeed;

   public static void main(String[] args) {

       Car car1 = new Car();
       car1.model = "Ferrari";
       car1.maxSpeed = 300;

       Car car2 = new Car();
       car2.model = "Ferrari";
       car2.maxSpeed = 300;

       System.out.println(car1 == car2);
   }
}
कंसोल आउटपुट:

false
ऐसा लगता है कि हमने दो समान Carवस्तुएँ बनाई हैं: दो कार वस्तुओं के संबंधित क्षेत्रों के मान समान हैं, लेकिन तुलना का परिणाम अभी भी गलत है। हम पहले से ही कारण जानते हैं: car1और car2संदर्भ अलग-अलग स्मृति पतों को इंगित करते हैं, इसलिए वे समान नहीं हैं। लेकिन हम अभी भी दो वस्तुओं की तुलना करना चाहते हैं, दो संदर्भों की नहीं। वस्तुओं की तुलना करने का सबसे अच्छा उपाय equals()तरीका है।

बराबर () विधि

आपको याद हो सकता है कि हम इस विधि को खरोंच से नहीं बनाते हैं, बल्कि हम इसे ओवरराइड करते हैं: विधि को कक्षा equals()में परिभाषित किया गया है । Objectउस ने कहा, अपने सामान्य रूप में, इसका बहुत कम उपयोग है:

public boolean equals(Object obj) {
   return (this == obj);
}
इस प्रकार कक्षा equals()में विधि को परिभाषित किया गया है । Objectयह एक बार फिर संदर्भों की तुलना है। उन्होंने ऐसा क्यों बनाया? खैर, भाषा के निर्माता कैसे जानते हैं कि आपके कार्यक्रम में कौन सी वस्तुएं समान मानी जाती हैं और कौन सी नहीं हैं? :) यह विधि का मुख्य बिंदु है equals()- एक वर्ग का निर्माता वह है जो यह निर्धारित करता है कि वर्ग की वस्तुओं की समानता की जाँच करते समय किन विशेषताओं का उपयोग किया जाता है। फिर आप equals()अपनी कक्षा में विधि को ओवरराइड करते हैं। यदि आप "किस विशेषताओं को निर्धारित करते हैं" का अर्थ पूरी तरह से नहीं समझते हैं, तो आइए एक उदाहरण पर विचार करें। यहाँ एक आदमी का प्रतिनिधित्व करने वाला एक साधारण वर्ग है: Man.

public class Man {

   private String noseSize;
   private String eyesColor;
   private String haircut;
   private boolean scars;
   private int dnaCode;

public Man(String noseSize, String eyesColor, String haircut, boolean scars, int dnaCode) {
   this.noseSize = noseSize;
   this.eyesColor = eyesColor;
   this.haircut = haircut;
   this.scars = scars;
   this.dnaCode = dnaCode;
}

   // Getters, setters, etc.
}
मान लीजिए कि हम एक प्रोग्राम लिख रहे हैं जिसे यह निर्धारित करने की आवश्यकता है कि क्या दो लोग समान जुड़वाँ हैं या केवल हमशक्ल हैं। हमारे पास पांच विशेषताएं हैं: नाक का आकार, आंखों का रंग, बालों की शैली, निशान की उपस्थिति, और डीएनए परीक्षण के परिणाम (सरलता के लिए, हम इसे पूर्णांक कोड के रूप में दर्शाते हैं)। आपको क्या लगता है कि इनमें से कौन सी विशेषताएं हमारे कार्यक्रम को एक जैसे जुड़वा बच्चों की पहचान करने में मदद करेंगी? बराबर और हैशकोड विधियाँ: सर्वोत्तम अभ्यास - 2बेशक, केवल एक डीएनए परीक्षण गारंटी प्रदान कर सकता है। दो लोगों की आंखों का रंग एक जैसा हो सकता है, बाल कटवा सकते हैं, नाक, और यहां तक ​​कि निशान भी हो सकते हैं - दुनिया में बहुत सारे लोग हैं, और यह गारंटी देना असंभव है कि वहां कोई हमशक्ल नहीं हैं। लेकिन हमें एक विश्वसनीय तंत्र की आवश्यकता है: केवल डीएनए परीक्षण के परिणाम ही हमें एक सटीक निष्कर्ष निकालने देंगे। equals()हमारी पद्धति के लिए इसका क्या अर्थ है ? हमें इसे में ओवरराइड करने की आवश्यकता हैManकक्षा, हमारे कार्यक्रम की आवश्यकताओं को ध्यान में रखते हुए। विधि को int dnaCodeदो वस्तुओं के क्षेत्र की तुलना करनी चाहिए। यदि वे समान हैं, तो वस्तुएँ समान हैं।

@Override
public boolean equals(Object o) {
   Man man = (Man) o;
   return dnaCode == man.dnaCode;
}
क्या यह वास्तव में इतना आसान है? ज़रूरी नहीं। हमने कुछ अनदेखा किया। हमारी वस्तुओं के लिए, हमने केवल एक क्षेत्र की पहचान की है जो वस्तु समानता स्थापित करने के लिए प्रासंगिक है: dnaCode. अब कल्पना कीजिए कि हमारे पास 1 नहीं, बल्कि 50 प्रासंगिक क्षेत्र हैं। और यदि दो वस्तुओं के सभी 50 क्षेत्र समान हैं, तो वस्तुएँ समान हैं। ऐसा परिदृश्य भी संभव है। मुख्य समस्या यह है कि 50 क्षेत्रों की तुलना करके समानता स्थापित करना एक समय लेने वाली और संसाधन-गहन प्रक्रिया है। अब कल्पना करें कि हमारी कक्षा के अतिरिक्त Man, हमारे पास एक Womanवर्ग है जिसमें ठीक उसी क्षेत्र हैं जो मौजूद हैं Man। यदि कोई अन्य प्रोग्रामर हमारी कक्षाओं का उपयोग करता है, तो वह आसानी से इस तरह का कोड लिख सकता है:

public static void main(String[] args) {
  
   Man man = new Man(........); // A bunch of parameters in the constructor

   Woman woman = new Woman(.........); // The same bunch of parameters.

   System.out.println(man.equals(woman));
}
इस मामले में, फ़ील्ड मानों की जाँच करना व्यर्थ है: हम आसानी से देख सकते हैं कि हमारे पास दो अलग-अलग वर्गों की वस्तुएँ हैं, इसलिए उनके बराबर होने का कोई तरीका नहीं है! equals()इसका मतलब है कि हमें तुलना की गई वस्तुओं के वर्गों की तुलना करते हुए विधि में एक चेक जोड़ना चाहिए । यह अच्छा है कि हमने इसके बारे में सोचा!

@Override
public boolean equals(Object o) {
   if (getClass() != o.getClass()) return false;
   Man man = (Man) o;
   return dnaCode == man.dnaCode;
}
लेकिन शायद हम कुछ और भूल गए हैं? हम्म... कम से कम, हमें यह जांचना चाहिए कि हम किसी वस्तु की तुलना उसी से नहीं कर रहे हैं! यदि संदर्भ A और B एक ही स्मृति पते की ओर इशारा करते हैं, तो वे एक ही वस्तु हैं, और हमें समय बर्बाद करने और 50 क्षेत्रों की तुलना करने की आवश्यकता नहीं है।

@Override
public boolean equals(Object o) {
   if (this == o) return true;
   if (getClass() != o.getClass()) return false;
   Man man = (Man) o;
   return dnaCode == man.dnaCode;
}
इसके लिए चेक जोड़ने में भी कोई दिक्कत नहीं होती है null: कोई वस्तु बराबर नहीं हो सकती है null। इसलिए, यदि विधि पैरामीटर शून्य है, तो अतिरिक्त जांच का कोई मतलब नहीं है। इस सब को ध्यान में रखते हुए, कक्षा equals()के लिए हमारा तरीका Manइस तरह दिखता है:

@Override
public boolean equals(Object o) {
   if (this == o) return true;
   if (o == null || getClass() != o.getClass()) return false;
   Man man = (Man) o;
   return dnaCode == man.dnaCode;
}
हम ऊपर उल्लिखित सभी प्रारंभिक जांच करते हैं। दिन के अंत में, यदि:
  • हम एक ही वर्ग की दो वस्तुओं की तुलना कर रहे हैं
  • और तुलना की गई वस्तुएँ एक ही वस्तु नहीं हैं
  • और पारित वस्तु नहीं हैnull
...फिर हम प्रासंगिक विशेषताओं की तुलना करने के लिए आगे बढ़ते हैं। हमारे लिए, इसका अर्थ है dnaCodeदो वस्तुओं का क्षेत्र। विधि को ओवरराइड करते समय equals(), इन आवश्यकताओं का पालन करना सुनिश्चित करें:
  1. रिफ्लेक्सिविटी।

    जब equals()विधि का उपयोग किसी वस्तु की स्वयं से तुलना करने के लिए किया जाता है, तो उसे सत्य लौटना चाहिए।
    हम पहले ही इस आवश्यकता का अनुपालन कर चुके हैं। हमारी पद्धति में शामिल हैं:

    
    if (this == o) return true;
    

  2. समरूपता।

    अगर a.equals(b) == true, तो b.equals(a)वापस जाना चाहिए true
    हमारी पद्धति इस आवश्यकता को भी पूरा करती है।

  3. सकारात्मकता।

    यदि दो वस्तुएँ किसी तीसरी वस्तु के बराबर हैं, तो वे एक दूसरे के बराबर होनी चाहिए।
    अगर a.equals(b) == trueऔर a.equals(c) == true, तो b.equals(c)भी सच होना चाहिए।

  4. अटलता।

    का नतीजा equals()तभी बदलना चाहिए जब शामिल फ़ील्ड बदल दिए जाएं। यदि दो वस्तुओं का डेटा नहीं बदलता है, तो परिणाम equals()हमेशा समान होना चाहिए।

  5. के साथ असमानता null

    किसी भी वस्तु के लिए, a.equals(null)गलत लौटना चाहिए
    यह केवल कुछ "उपयोगी अनुशंसाओं" का एक सेट नहीं है, बल्कि एक सख्त अनुबंध है , जो Oracle प्रलेखन में निर्धारित है

हैशकोड () विधि

अब बात करते हैं hashCode()विधि की। यह क्यों आवश्यक है? ठीक उसी उद्देश्य के लिए - वस्तुओं की तुलना करना। लेकिन हमारे पास पहले से ही है equals()! दूसरा तरीका क्यों? उत्तर सरल है: प्रदर्शन में सुधार करना। एक हैश फ़ंक्शन, hashCode()विधि का उपयोग करके जावा में दर्शाया गया है, किसी ऑब्जेक्ट के लिए एक निश्चित-लंबाई संख्यात्मक मान देता है। जावा में, विधि किसी वस्तु के लिए hashCode()32-बिट संख्या () लौटाती है । intविधि का उपयोग करके दो वस्तुओं की तुलना करने की तुलना में दो संख्याओं की तुलना करना बहुत तेज है equals(), खासकर यदि वह विधि कई क्षेत्रों पर विचार करती है। यदि हमारा प्रोग्राम वस्तुओं की तुलना करता है, तो हैश कोड का उपयोग करना बहुत सरल है। विधि के आधार पर वस्तुएं समान होने पर ही hashCode()तुलना आगे बढ़ती हैequals()तरीका। वैसे, हैश-आधारित डेटा संरचनाएं इस तरह काम करती हैं, उदाहरण के लिए, परिचित HashMap! विधि hashCode(), equals()विधि की तरह, डेवलपर द्वारा ओवरराइड की जाती है। और ठीक उसी तरह equals(), hashCode()विधि की आधिकारिक आवश्यकताएं Oracle दस्तावेज़ीकरण में लिखी गई हैं:
  1. यदि दो वस्तुएँ समान हैं (अर्थात equals()विधि सत्य है), तो उनके पास समान हैश कोड होना चाहिए।

    अन्यथा, हमारे तरीके अर्थहीन होंगे। जैसा कि हमने ऊपर उल्लेख किया है, hashCode()प्रदर्शन को बेहतर बनाने के लिए पहले जांच की जानी चाहिए। यदि हैश कोड अलग थे, तो चेक गलत वापस आ जाएगा, भले ही ऑब्जेक्ट वास्तव में समान हैं कि हमने equals()विधि को कैसे परिभाषित किया है।

  2. यदि hashCode()विधि को एक ही वस्तु पर कई बार कहा जाता है, तो उसे हर बार एक ही संख्या वापस करनी चाहिए।

  3. नियम 1 विपरीत दिशा में काम नहीं करता। दो अलग-अलग वस्तुओं में एक ही हैश कोड हो सकता है।

तीसरा नियम थोड़ा भ्रमित करने वाला है। यह कैसे हो सकता है? व्याख्या काफी सरल है। विधि hashCode()एक देता है int। An intएक 32-बिट संख्या है। इसमें मूल्यों की एक सीमित सीमा है: -2,147,483,648 से +2,147,483,647 तक। दूसरे शब्दों में, एक int. अब कल्पना कीजिए कि आप पृथ्वी पर रहने वाले सभी लोगों के बारे में डेटा स्टोर करने के लिए एक प्रोग्राम बना रहे हैं। प्रत्येक व्यक्ति अपनी Personवस्तु (वर्ग के समान Man) के अनुरूप होगा। ग्रह पर ~ 7.5 बिलियन लोग रहते हैं। दूसरे शब्दों में, कोई फर्क नहीं पड़ता कि हम कनवर्ट करने के लिए कितना चालाक एल्गोरिथम लिखते हैंPersonएक int के लिए ऑब्जेक्ट, हमारे पास पर्याप्त संभावित संख्याएँ नहीं हैं। हमारे पास केवल 4.5 बिलियन संभावित इंट वैल्यू हैं, लेकिन इससे कहीं अधिक लोग हैं। इसका मतलब यह है कि हम कितनी भी कोशिश कर लें, कुछ अलग लोगों के पास एक ही हैश कोड होगा। जब ऐसा होता है (हैश कोड दो अलग-अलग वस्तुओं के लिए मेल खाते हैं) तो हम इसे टक्कर कहते हैं। विधि को ओवरराइड करते समय hashCode(), प्रोग्रामर के उद्देश्यों में से एक टक्करों की संभावित संख्या को कम करना है। इन सभी नियमों को ध्यान में रखते हुए, यह hashCode()विधि कक्षा में कैसी दिखेगी Person? इस कदर:

@Override
public int hashCode() {
   return dnaCode;
}
हैरान? :) यदि आप आवश्यकताओं को देखते हैं, तो आप देखेंगे कि हम उन सभी का अनुपालन करते हैं। जिन वस्तुओं के लिए हमारा equals()तरीका सही है, वे भी बराबर होंगी hashCode()। यदि हमारी दो Personवस्तुएँ समान हैं equals(अर्थात उनके पास समान हैं dnaCode), तो हमारी विधि समान संख्या लौटाती है। आइए एक और कठिन उदाहरण पर विचार करें। मान लीजिए कि हमारे कार्यक्रम को कार संग्राहकों के लिए लक्ज़री कारों का चयन करना चाहिए। संग्रह करना कई विशिष्टताओं के साथ एक जटिल शौक हो सकता है। एक विशेष 1963 कार की कीमत 1964 की कार से 100 गुना अधिक हो सकती है। 1970 की लाल रंग की कार की कीमत उसी वर्ष उसी ब्रांड की नीली कार से 100 गुना अधिक हो सकती है। बराबर और हैशकोड विधियाँ: सर्वोत्तम अभ्यास - 4हमारे पिछले उदाहरण में, वर्ग के साथ Person, हमने अधिकांश क्षेत्रों (यानी मानवीय विशेषताओं) को नगण्य के रूप में त्याग दिया और केवल उपयोग कियाdnaCodeतुलना में क्षेत्र। अब हम एक बहुत ही विशेष स्वभाव के दायरे में काम कर रहे हैं, जिसमें कोई महत्वहीन विवरण नहीं है! यहाँ हमारी LuxuryAutoकक्षा है:

public class LuxuryAuto {

   private String model;
   private int manufactureYear;
   private int dollarPrice;

   public LuxuryAuto(String model, int manufactureYear, int dollarPrice) {
       this.model = model;
       this.manufactureYear = manufactureYear;
       this.dollarPrice = dollarPrice;
   }

   // ...getters, setters, etc.
}
अब हमें अपनी तुलना में सभी क्षेत्रों पर विचार करना चाहिए। किसी भी गलती से ग्राहक को सैकड़ों हजारों डॉलर खर्च हो सकते हैं, इसलिए बेहतर होगा कि अत्यधिक सुरक्षित रहें:

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

   LuxuryAuto that = (LuxuryAuto) o;

   if (manufactureYear != that.manufactureYear) return false;
   if (dollarPrice != that.dollarPrice) return false;
   return model.equals(that.model);
}
अपनी equals()पद्धति में, हम उन सभी जाँचों को नहीं भूले हैं जिनके बारे में हमने पहले बात की थी। लेकिन अब हम अपनी वस्तुओं के तीनों क्षेत्रों में से प्रत्येक की तुलना करते हैं। इस कार्यक्रम के लिए हमें पूर्ण समानता अर्थात प्रत्येक क्षेत्र की समानता की आवश्यकता है। किस बारे में hashCode?

@Override
public int hashCode() {
   int result = model == null ? 0 : model.hashCode();
   result = result + manufactureYear;
   result = result + dollarPrice;
   return result;
}
modelहमारी कक्षा का क्षेत्र एक स्ट्रिंग है । यह सुविधाजनक है, क्योंकि वर्ग पहले से ही विधि को Stringओवरराइड करता है । hashCode()हम modelफ़ील्ड के हैश कोड की गणना करते हैं और फिर उसमें अन्य दो संख्यात्मक फ़ील्ड का योग जोड़ते हैं। जावा डेवलपर्स के पास टकराव की संख्या को कम करने के लिए उपयोग की जाने वाली एक सरल चाल है: हैश कोड की गणना करते समय, मध्यवर्ती परिणाम को विषम अभाज्य से गुणा करें। सबसे अधिक उपयोग की जाने वाली संख्या 29 या 31 है। हम अभी गणितीय सूक्ष्मताओं में नहीं जाएंगे, लेकिन भविष्य में याद रखें कि मध्यवर्ती परिणामों को पर्याप्त रूप से बड़ी विषम संख्या से गुणा करने से हैश फ़ंक्शन के परिणामों को "फैलाने" में मदद मिलती है और, परिणामस्वरूप, समान हैश कोड वाली वस्तुओं की संख्या कम करें। LuxuryAuto में हमारे hashCode()तरीके के लिए, यह ऐसा दिखाई देगा:

@Override
public int hashCode() {
   int result = model == null ? 0 : model.hashCode();
   result = 31 * result + manufactureYear;
   result = 31 * result + dollarPrice;
   return result;
}
आप इस तंत्र की सभी पेचीदगियों के बारे में इस पोस्ट में StackOverflow पर , साथ ही जोशुआ बलोच द्वारा प्रभावी जावा पुस्तक में पढ़ सकते हैं। अंत में, एक और महत्वपूर्ण बात जो ध्यान देने योग्य है। हर बार जब हम equals()और hashCode()विधि को ओवररोड करते हैं, तो हमने कुछ उदाहरण फ़ील्ड का चयन किया है जिन्हें इन विधियों में ध्यान में रखा जाता है। ये विधियाँ समान क्षेत्रों पर विचार करती हैं। equals()लेकिन क्या हम और में विभिन्न क्षेत्रों पर विचार कर सकते हैं hashCode()? तकनीकी रूप से, हम कर सकते हैं। लेकिन यह एक बुरा विचार है, और यहाँ क्यों है:

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

   LuxuryAuto that = (LuxuryAuto) o;

   if (manufactureYear != that.manufactureYear) return false;
   return dollarPrice == that.dollarPrice;
}

@Override
public int hashCode() {
   int result = model == null ? 0 : model.hashCode();
   result = 31 * result + manufactureYear;
   result = 31 * result + dollarPrice;
   return result;
}
यहाँ हमारे equals()और कक्षा hashCode()के तरीके हैं LuxuryAuto। विधि hashCode()अपरिवर्तित रही, लेकिन हमने विधि modelसे फ़ील्ड हटा दी। equals()मॉडल अब उपयोग की जाने वाली विशेषता नहीं है जब equals()विधि दो वस्तुओं की तुलना करती है। लेकिन हैश कोड की गणना करते समय, उस क्षेत्र को अभी भी ध्यान में रखा जाता है। परिणामस्वरूप हमें क्या मिलता है? चलो दो कार बनाते हैं और पता लगाते हैं!

public class Main {

   public static void main(String[] args) {

       LuxuryAuto ferrariGTO = new LuxuryAuto("Ferrari 250 GTO", 1963, 70000000);
       LuxuryAuto ferrariSpider = new LuxuryAuto("Ferrari 335 S Spider Scaglietti", 1963, 70000000);

       System.out.println("Are these two objects equal to each other?");
       System.out.println(ferrariGTO.equals(ferrariSpider));

       System.out.println("What are their hash codes?");
       System.out.println(ferrariGTO.hashCode());
       System.out.println(ferrariSpider.hashCode());
   }
}

Are these two objects equal to each other? 
true 
What are their hash codes? 
-1372326051 
1668702472
गलती! equals()और विधियों के लिए विभिन्न क्षेत्रों का उपयोग करके hashCode(), हमने उन अनुबंधों का उल्लंघन किया जो उनके लिए स्थापित किए गए हैं! विधि के अनुसार समान दो वस्तुओं में equals()समान हैश कोड होना चाहिए। हमें उनके लिए अलग-अलग मूल्य मिले। इस तरह की त्रुटियां बिल्कुल अविश्वसनीय परिणाम दे सकती हैं, खासकर जब हैश का उपयोग करने वाले संग्रह के साथ काम करते हैं। परिणामस्वरूप, जब आप equals()और को ओवरराइड करते हैं hashCode(), तो आपको उन्हीं क्षेत्रों पर विचार करना चाहिए। यह पाठ काफ़ी लंबा था, लेकिन आज आपने बहुत कुछ सीखा! :) अब कार्यों को हल करने के लिए वापस आने का समय आ गया है!
टिप्पणियां
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION