أهلاً! سنتحدث اليوم عن موضوع مهم ومثير للاهتمام للغاية وهو مقارنة الكائنات بالكائنات (مقارنة السلاسل والمساواة). إذن في Java، متى بالضبط سيكون الكائن A مساويًا للكائن B ؟ دعونا نحاول كتابة مثال:
يستخدم عامل التشغيل == نفس المنطق تقريبًا عندما نستخدمه لمقارنة كائنين. ولكن ماذا لو كنت بحاجة لبرنامجك لاستخدام منطق مختلف؟ على سبيل المثال، لنفترض أن برنامجك يقوم بتحليل الحمض النووي. فهو يقارن الشفرة الوراثية لشخصين، ويحدد ما إذا كانا توأمان.
يعد تجمع السلسلة منطقة لتخزين كافة قيم السلسلة التي تقوم بإنشائها في برنامجك. لماذا تم إنشاؤها؟ كما قلنا من قبل، تمثل السلاسل نسبة كبيرة من جميع الكائنات. أي برنامج كبير ينشئ الكثير من السلاسل. تم إنشاء تجمع السلاسل لحفظ الذاكرة: يتم وضع السلاسل هناك ثم تشير السلاسل التي تم إنشاؤها لاحقًا إلى نفس منطقة الذاكرة - ليست هناك حاجة لتخصيص ذاكرة إضافية في كل مرة. في كل مرة تكتب فيها String = "......." يتحقق البرنامج مما إذا كانت هناك سلسلة متطابقة في تجمع السلاسل. إذا كان هناك، فلن يتم إنشاء سلسلة جديدة. وسيشير المرجع الجديد إلى نفس العنوان في تجمع السلسلة (حيث توجد السلسلة المتطابقة). لذلك عندما كتبنا
وفي كل مرة تقوم فيها بإنشاء كائن جديد باستخدام new ، يتم تخصيص مساحة جديدة من الذاكرة، حتى لو كان النص الموجود داخل السلسلة الجديدة هو نفسه! يبدو أننا اكتشفنا عامل التشغيل == . ولكن ماذا عن معرفتنا الجديدة، طريقة يساوي () ؟
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);
}
}
إخراج وحدة التحكم: انتظار كاذب ، توقف. لماذا لا تكون هاتان السيارتان متساويتين؟ لقد أعطيناهم نفس الخصائص، لكن نتيجة المقارنة خاطئة. الجواب بسيط. يقوم عامل التشغيل == بمقارنة مراجع الكائنات، وليس خصائص الكائنات. يمكن أن يحتوي كائنان على 500 حقل بقيم متطابقة، لكن المقارنة بينهما ستظل غير صحيحة. بعد كل شيء، تشير المراجع car1 و car2 إلى كائنين مختلفين، أي إلى عنوانين مختلفين. تخيل موقفًا تقارن فيه بين الأشخاص. بالتأكيد، يوجد في مكان ما من العالم شخص يشاركك نفس الاسم، ولون العين، والعمر، والطول، ولون الشعر، وما إلى ذلك. وهذا يجعلك متشابهًا في كثير من النواحي، لكنك مازلت لست توأمان - ومن الواضح أنك لست كذلك نفس الشخص.
public class Man {
int geneticCode;
public static void main(String[] args) {
Man man1 = new Man();
man1.geneticCode = 1111222233;
Man man2 = new Man();
man2.geneticCode = 1111222233;
System.out.println(man1 == man2);
}
}
مخرجات وحدة التحكم: false نحصل على نفس النتيجة المنطقية (لأننا لم نتغير كثيرًا)، لكن هذا المنطق ليس جيدًا الآن! ففي نهاية المطاف، في الحياة الواقعية، يجب أن يمنحنا تحليل الحمض النووي ضمانًا بنسبة 100٪ بأن لدينا توأمان يقفان أمامنا. لكن برنامجنا والمشغل == يخبراننا بالعكس. كيف نغير هذا السلوك ونتأكد من أن البرنامج يخرج النتيجة الصحيحة عندما يتطابق الحمض النووي؟ لدى Java طريقة خاصة لهذا: يساوي () . مثل طريقة toString() ، التي ناقشناها سابقًا، تنتمي يساوي() إلى فئة الكائن - وهي الفئة الأكثر أهمية في Java، وهي الفئة التي تشتق منها جميع الفئات الأخرى. لكن يساوي() لا يغير سلوك برنامجنا من تلقاء نفسه:
public class Man {
String geneticCode;
public static void main(String[] args) {
Man man1 = new Man();
man1.geneticCode = "111122223333";
Man man2 = new Man();
man2.geneticCode = "111122223333";
System.out.println(man1.equals(man2));
}
}
إخراج وحدة التحكم: خطأ نفس النتيجة تمامًا، فلماذا نحتاج إلى هذه الطريقة؟ :/ الأمر كله بسيط. المشكلة هنا هي أننا نستخدم هذه الطريقة حاليًا كما يتم تنفيذها في فئة الكائن . وإذا دخلنا إلى كود فئة الكائن ونظرنا إلى تنفيذ الطريقة، فهذا ما سنراه:
public boolean equals(Object obj) {
return (this == obj);
}
هذا هو السبب وراء عدم تغير سلوك البرنامج! يتم استخدام نفس عامل التشغيل == (الذي يقارن المراجع) داخل طريقة يساوي () لفئة الكائن . لكن الحيلة في هذه الطريقة هي أنه يمكننا تجاوزها. التجاوز يعني كتابة طريقة يساوي () الخاصة بك في فئة Man الخاصة بنا ، مما يمنحها السلوك الذي نحتاجه! في الوقت الحاضر، لا نحب حقيقة أن man1.equals(man2) يعادل بشكل أساسي man1 == man2 . إليك ما سنفعله في هذه الحالة:
public class Man {
int dnaCode;
public boolean equals(Man man) {
return this.dnaCode == man.dnaCode;
}
public static void main(String[] args) {
Man man1 = new Man();
man1.dnaCode = 1111222233;
Man man2 = new Man();
man2.dnaCode = 1111222233;
System.out.println(man1.equals(man2));
}
}
إخراج وحدة التحكم: صحيح الآن حصلنا على نتيجة مختلفة تمامًا! من خلال كتابة طريقة يساوي () الخاصة بنا واستخدامها بدلاً من الطريقة القياسية، فقد أنتجنا السلوك الصحيح: الآن إذا كان لدى شخصين نفس الحمض النووي، فسيبلغ البرنامج "أثبت تحليل الحمض النووي أنهما توأمان" ويعود صحيحًا! من خلال تجاوز طريقة يساوي () في فئاتك، يمكنك بسهولة إنشاء أي منطق مقارنة الكائنات الذي تحتاجه. في الواقع، لقد تطرقنا للتو إلى مقارنة الكائنات. لا يزال أمامنا درس كبير مستقل حول هذا الموضوع
(يمكنك تصفحه الآن إذا كنت مهتمًا).
مقارنة السلاسل في جافا
لماذا نفكر في مقارنات السلسلة بشكل منفصل عن كل شيء آخر؟ الحقيقة هي أن السلاسل هي موضوع بحد ذاتها في البرمجة. أولاً، إذا نظرت إلى جميع برامج Java المكتوبة على الإطلاق، فستجد أن حوالي 25% من الكائنات الموجودة فيها عبارة عن سلاسل. لذلك هذا الموضوع مهم جدا. ثانيًا، تختلف عملية مقارنة السلاسل كثيرًا عن الكائنات الأخرى. خذ مثالاً بسيطًا:public class Main {
public static void main(String[] args) {
String s1 = "CodeGym is the best website for learning Java!";
String s2 = new String("CodeGym is the best website for learning Java!");
System.out.println(s1 == s2);
}
}
إخراج وحدة التحكم: خطأ ولكن لماذا حصلنا على خطأ؟ بعد كل شيء، السلاسل هي نفسها تمامًا، كلمة بكلمة :/ ربما خمنت السبب: لأن عامل التشغيل == يقارن بين المراجع ! من الواضح أن s1 و s2 لهما عناوين مختلفة في الذاكرة. إذا فكرت في ذلك، فلنعد صياغة مثالنا:
public class Main {
public static void main(String[] args) {
String s1 = "CodeGym is the best website for learning Java!";
String s2 = "CodeGym is the best website for learning Java!";
System.out.println(s1 == s2);
}
}
الآن لدينا مرجعان مرة أخرى، لكن النتيجة هي العكس تمامًا: إخراج وحدة التحكم: صحيح مرتبك بلا حول ولا قوة؟ دعونا معرفة ما يحدث. يقوم عامل التشغيل == بالفعل بمقارنة عناوين الذاكرة. هذا صحيح دائمًا ولا داعي للشك فيه. وهذا يعني أنه إذا كانت s1 == s2 ترجع صحيحًا، فإن هاتين السلسلتين لهما نفس العنوان. وبالفعل هذا صحيح! حان الوقت لتعريفك بمنطقة خاصة من الذاكرة لتخزين السلاسل: تجمع السلاسل
String s1 = "CodeGym is the best website for learning Java!";
String s2 = "CodeGym is the best website for learning Java!";
يشير s2 إلى نفس مكان s1 . تقوم العبارة الأولى بإنشاء سلسلة جديدة في تجمع السلاسل. العبارة الثانية تشير ببساطة إلى نفس مساحة الذاكرة مثل s1 . يمكنك إنشاء 500 سلسلة متطابقة أخرى ولن تتغير النتيجة. انتظر دقيقة. إذا كان هذا صحيحا، فلماذا لم يعمل هذا المثال من قبل؟
public class Main {
public static void main(String[] args) {
String s1 = "CodeGym is the best website for learning Java!";
String s2 = new String("CodeGym is the best website for learning Java!");
System.out.println(s1 == s2);
}
}
أعتقد أن حدسك قد أخبرك بالسبب بالفعل =) حاول التخمين قبل مواصلة القراءة. يمكنك أن ترى أنه تم الإعلان عن هاتين السلسلتين بطرق مختلفة. واحد مع المشغل الجديد والآخر بدونه. وهنا يكمن السبب. عند استخدام عامل التشغيل الجديد لإنشاء كائن، فإنه يخصص مساحة جديدة من الذاكرة للكائن بالقوة. والسلسلة التي تم إنشاؤها باستخدام new لا تنتهي في تجمع السلاسل - فهي تصبح كائنًا منفصلاً، حتى لو كان نصها يتطابق تمامًا مع سلسلة في تجمع السلاسل. أي إذا كتبنا الكود التالي:
public class Main {
public static void main(String[] args) {
String s1 = "CodeGym is the best website for learning Java!";
String s2 = "CodeGym is the best website for learning Java!";
String s3 = new String("CodeGym is the best website for learning Java!");
}
}
في الذاكرة يبدو الأمر كما يلي:
public class Main {
public static void main(String[] args) {
String s1 = "CodeGym is the best website for learning Java!";
String s2 = new String("CodeGym is the best website for learning Java!");
System.out.println(s1.equals(s2));
}
}
إخراج وحدة التحكم: صحيح مثير للاهتمام. نحن على يقين من أن s1 و s2 يشيران إلى مناطق مختلفة في الذاكرة. لكن طريقة يساوي () ما زالت تخبرنا أنهما متساويان. لماذا؟ هل تذكر أننا قلنا سابقًا أنه يمكن تجاوز طريقة يساوي () لمقارنة الكائنات بالطريقة التي نريدها؟ هذا بالضبط ما فعلوه مع فئة السلسلة . إنه يتجاوز طريقة يساوي () . وبدلا من مقارنة المراجع، فإنه يقارن تسلسل الأحرف في السلاسل. إذا كان النص هو نفسه، فلا يهم كيفية إنشائه أو مكان تخزينه: سواء في تجمع السلاسل أو في منطقة منفصلة من الذاكرة. وستكون نتيجة المقارنة صحيحة. بالمناسبة، تتيح لك Java إجراء مقارنات سلسلة غير حساسة لحالة الأحرف. عادةً، إذا كانت إحدى السلاسل تحتوي على أحرف كبيرة، فستكون نتيجة المقارنة خاطئة:
public class Main {
public static void main(String[] args) {
String s1 = "CodeGym is the best website for learning Java!";
String s2 = new String("CODEGYM IS THE BEST WEBSITE FOR LEARNING JAVA!");
System.out.println(s1.equals(s2));
}
}
إخراج وحدة التحكم: خطأ بالنسبة للمقارنات غير الحساسة لحالة الأحرف، فإن فئة السلسلة لها أسلوب يساوي IgnoreCase () . يمكنك استخدامه إذا كنت تهتم فقط بمقارنة تسلسل أحرف معينة بدلاً من حالة الأحرف. على سبيل المثال، قد يكون هذا مفيدًا عند مقارنة عنوانين:
public class Main {
public static void main(String[] args) {
String address1 = "2311 Broadway Street, San Francisco";
String address2 = new String("2311 BROADWAY STREET, SAN FRANCISCO");
System.out.println(address1.equalsIgnoreCase(address2));
}
}
في هذه الحالة، من الواضح أننا نتحدث عن نفس العنوان، لذلك فمن المنطقي استخدام طريقة equalsIgnoreCase() .
طريقة String.intern()
تحتوي فئة String على طريقة أكثر صعوبة: intern() ; تعمل طريقة المتدرب () مباشرة مع تجمع السلسلة. إذا قمت باستدعاء طريقة المتدرب () على بعض السلسلة:- يتحقق مما إذا كانت هناك سلسلة مطابقة في تجمع السلسلة
- إذا كان هناك، فإنه يقوم بإرجاع المرجع إلى السلسلة الموجودة في التجمع
- إذا لم يكن الأمر كذلك، فإنه يضيف السلسلة إلى تجمع السلسلة ويعيد مرجعًا إليها.
public class Main {
public static void main(String[] args) {
String s1 = "CodeGym is the best website for learning Java!";
String s2 = new String("CodeGym is the best website for learning Java!");
System.out.println(s1 == s2.intern());
}
}
مخرجات وحدة التحكم: true عندما قارنا هذه السلاسل سابقًا بدون intern() ، كانت النتيجة خاطئة. الآن تتحقق طريقة intern() مما إذا كانت السلسلة "CodeGym هو أفضل موقع لتعلم Java!" موجود في تجمع السلسلة. وبطبيعة الحال، هو: خلقناه مع
String s1 = "CodeGym is the best website for learning Java!";
نتحقق مما إذا كان s1 والمرجع الذي تم إرجاعه بواسطة s2.intern() يشيران إلى نفس منطقة الذاكرة. وبالطبع، يفعلون ذلك :) باختصار، احفظ هذه القاعدة المهمة وطبقها: استخدم دائمًا طريقة يساوي () لمقارنة السلاسل! عند مقارنة السلاسل، نقصد دائمًا مقارنة أحرفها بدلاً من المراجع أو مناطق الذاكرة أو أي شيء آخر. تقوم طريقة يساوي () بما تحتاجه بالضبط. لتعزيز ما تعلمته، نقترح عليك مشاهدة درس فيديو من دورة Java الخاصة بنا
المزيد من القراءة: |
---|
GO TO FULL VERSION