CodeGym /Java Blog /यादृच्छिक /समान आणि हॅशकोड पद्धती: सर्वोत्तम पद्धती
John Squirrels
पातळी 41
San Francisco

समान आणि हॅशकोड पद्धती: सर्वोत्तम पद्धती

यादृच्छिक या ग्रुपमध्ये प्रकाशित केले
हाय! आज आपण जावामधील दोन महत्त्वाच्या पद्धतींबद्दल बोलू: 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() पद्धत

तुम्हाला आठवत असेल की आम्ही ही पद्धत सुरवातीपासून तयार करत नाही, उलट आम्ही ती ओव्हरराइड करतो: पद्धत 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)खोटे परत करणे आवश्यक आहे
    , हा केवळ काही "उपयुक्त शिफारशींचा" संच नाही, तर ओरॅकल दस्तऐवजीकरणात नमूद केलेला एक कठोर करार आहे.

hashCode() पद्धत

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

    अन्यथा, आमच्या पद्धती निरर्थक ठरतील. आम्ही वर नमूद केल्याप्रमाणे, hashCode()कार्यप्रदर्शन सुधारण्यासाठी प्रथम तपासणी केली पाहिजे. जर हॅश कोड वेगळे असतील, तर चेक खोटा परत येईल, जरी आम्ही पद्धत कशी परिभाषित केली आहे त्यानुसार ऑब्जेक्ट्स प्रत्यक्षात समान आहेत equals().

  2. पद्धत hashCode()एकाच ऑब्जेक्टवर अनेक वेळा कॉल केल्यास, प्रत्येक वेळी ती समान संख्या परत करणे आवश्यक आहे.

  3. नियम 1 विरुद्ध दिशेने कार्य करत नाही. दोन भिन्न वस्तूंमध्ये समान हॅश कोड असू शकतो.

तिसरा नियम थोडा गोंधळात टाकणारा आहे. हे कसे असू शकते? स्पष्टीकरण अगदी सोपे आहे. पद्धत hashCode()एक परत करते int. An intही 32-बिट संख्या आहे. यात मूल्यांची मर्यादित श्रेणी आहे: -2,147,483,648 ते +2,147,483,647 पर्यंत. दुसऱ्या शब्दांत, 4 अब्जाहून अधिक संभाव्य मूल्ये आहेत 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;
}
स्टॅकओव्हरफ्लोवरील या पोस्टमध्ये , तसेच जोशुआ ब्लॉचच्या Effective Java या पुस्तकात तुम्ही या यंत्रणेच्या सर्व गुंतागुंतीबद्दल अधिक वाचू शकता . शेवटी, आणखी एक महत्त्वाचा मुद्दा जो नमूद करण्यासारखा आहे. प्रत्येक वेळी आम्ही 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