جافا تشالنجرز # 2: مقارنة السلسلة
كما هو الحال دائمًا ، لدينا الكثير من الوقت المتأخر لبدء الدورة ، لذلك عقدت أمس درسًا ثانيًا بين سلسلة المحادثات الجديدة "Java Developer" . لكن هذا هو الأمر ، أشياء صغيرة في الحياة ، ولكن في الوقت الحالي نواصل نشر سلسلة من مقالات Java Challenger ، والتي تم إعداد ترجمتها لك.
في Java ، تقوم فئة String
بتغليف مصفوفة char
( ملاحظة المترجم - مع java 9 هي بالفعل صفيف byte
، انظر Compact Strings in Java 9 ). بعبارات بسيطة ، فإن String
عبارة عن مجموعة من الأحرف المستخدمة لتأليف الكلمات أو الجمل أو البنيات الأخرى.
التغليف هو أحد أقوى المفاهيم في البرمجة الشيئية. بفضل التغليف ، لا تحتاج إلى معرفة كيفية عمل فئة String
. ما عليك سوى معرفة طرق واجهته.

عندما تنظر إلى فئة String
في Java ، يمكنك أن ترى كيف يتم تغليف مصفوفة الأحرف:
public String(char value[]) { this(value, 0, value.length, null); }
لفهم التغليف بشكل أفضل ، تخيل شيئًا ماديًا: آلة. هل تريد أن تعرف كيف تعمل السيارة تحت غطاء محرك السيارة من أجل قيادتها؟ بالطبع لا ، ولكن يجب أن تعرف ما تفعله واجهات السيارة: دواسة الوقود ، الفرامل وعجلة القيادة. تدعم كل واحدة من هذه الواجهات إجراءات معينة: التسارع ، الكبح ، الاستدارة لليسار ، الاستدارة لليمين. وينطبق الشيء نفسه على البرمجة الشيئية.
كانت المقالة الأولى في سلسلة Java Challengers تدور حول التحميل الزائد للأسلوب ، والذي يستخدم على نطاق واسع في فئة String
. يمكن أن يجعل التحميل الزائد فصولك الدراسية مرنة حقًا:
public String(String original) {} public String(char value[], int offset, int count) {} public String(int[] codePoints, int offset, int count) {} public String(byte bytes[], int offset, int length, String charsetName) {}
بدلاً من محاولة فهم كيفية عمل فئة String
، ستساعدك هذه المقالة على فهم ما تفعله وكيفية استخدامه في التعليمات البرمجية الخاصة بك.
ما هو تجمع سلسلة؟
يمكن القول إن فئة String
هي الفئة الأكثر استخدامًا في Java. إذا أنشأنا كائنًا جديدًا في الذاكرة الديناميكية (كومة الذاكرة) في كل مرة نستخدم فيها String
، فسوف نفقد الكثير من الذاكرة. يحل تجمع السلسلة هذه المشكلة عن طريق تخزين كائن واحد فقط لكل قيمة صف.

خطوط في تجمع الصف
على الرغم من أننا أنشأنا العديد من متغيرات String
بقيم Duke
و Juggy
، يتم إنشاء وتخزين Juggy
فقط في ذاكرة ديناميكية (كومة). انظر المثال التالي رمز لإثبات. (تذكر أنه في Java ، يتم استخدام عامل التشغيل " ==
" لمقارنة كائنين وتحديد ما إذا كان نفس الكائن هو نفسه أم لا.)
String juggy = "Juggy"; String anotherJuggy = "Juggy"; System.out.println(juggy == anotherJuggy);
سيعود هذا الرمز true
لأن متغيري String
يشيران إلى نفس الكائن في تجمع السلسلة. معانيهم هي نفسها.
الاستثناء هو عامل التشغيل new
.
الآن انظر إلى هذا الرمز - يبدو مشابهًا للمثال السابق ، ولكن هناك فرق.
String duke = new String("duke"); String anotherDuke = new String("duke"); System.out.println(duke == anotherDuke);
بناءً على المثال السابق ، قد تعتقد أن هذا الرمز سيعود true
، ولكنه ليس كذلك. تؤدي إضافة عامل تشغيل new
إلى إنشاء كائن String
جديد في الذاكرة. وبالتالي ، سوف تنشئ JVM كائنين مختلفين.
الأساليب الأصلية
الطرق الأصلية في Java هي طرق سيتم تجميعها باستخدام لغة C ، عادةً بهدف إدارة الذاكرة وتحسين الأداء.
تجمعات السلسلة وطريقة intern()
لتخزين السلاسل في التجمع ، يتم استخدام طريقة تسمى String interning.
هذا ما يخبرنا به جافادوك عن طريقة intern()
:
public native String intern();
يتم استخدام الأسلوب intern()
لتخزين السلاسل في تجمع سلسلة. أولاً ، يتحقق من وجود صف موجود بالفعل في التجمع. إذا لم يكن كذلك ، يقوم بإنشاء صف جديد في التجمع. يعتمد منطق تجمع الصفوف على نمط Flyweight .
الآن ، لاحظ ما يحدث عندما نستخدم new
لإنشاء خطين:
String duke = new String("duke"); String duke2 = new String("duke"); System.out.println(duke == duke2);
على عكس المثال السابق مع الكلمة الرئيسية new
، في هذه الحالة ، ستظهر المقارنة true
. وذلك لأن استخدام الطريقة intern()
يضمن أن السلسلة في التجمع.
equals
الأسلوب في فئة String
تُستخدم طريقة equals()
للتحقق مما إذا كانت فئتان متماثلتان أم لا. نظرًا equals()
في فئة Object
، فإن كل فئة Java ترثها. ولكن يجب تجاوز طريقة equals()
حتى تعمل بشكل صحيح. بالطبع ، تجاوز String
equals()
.
الق نظرة:
public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String aString = (String)anObject; if (coder() == aString.coder()) { return isLatin1() ? StringLatin1.equals(value, aString.value) : StringUTF16.equals(value, aString.value); } } return false; }
كما ترى ، تتم مقارنة قيمة فئة String
خلال equals()
، وليس من خلال مرجع كائن. لا يهم إذا كانت الإشارات إلى الكائنات مختلفة ؛ سيتم مقارنة الظروف.
طرق String
الشائعة
هناك شيء آخر تحتاج إلى معرفته قبل حل مشكلة مقارنة السلسلة.
فكر في الطرق الأكثر شيوعًا لفئة String
:
حل مشكلة مقارنة السلسلة
دعونا نتحقق مما تعلمته عن فئة String
خلال حل لغز صغير.
في هذه المهمة ، يمكنك مقارنة عدة أسطر باستخدام المفاهيم التي تعلمتها. بالنظر إلى الرمز أدناه ، هل يمكنك تحديد قيمة كل result
متغيرة؟
public class ComparisonStringChallenge { public static void main(String... doYourBest) { String result = ""; result += " powerfulCode ".trim() == "powerfulCode" ? "0" : "1"; result += "flexibleCode" == "flexibleCode" ? "2" : "3"; result += new String("doYourBest") == new String("doYourBest") ? "4" : "5"; result += new String("noBugsProject") .equals("noBugsProject") ? "6" : "7"; result += new String("breakYourLimits").intern() == new String("breakYourLimits").intern() ? "8" : "9"; System.out.println(result); } }
ماذا ستكون النتيجة؟
- ج: 02468
- ب: 12469
- ج: 12579
- ت: 12568
الجواب الصحيح معطى في نهاية المقال.
ماذا حدث الان؟ فهم سلوك String
في السطر الأول نرى:
result += " powerfulCode ".trim() == "powerfulCode" ? "0" : "1";
في هذه الحالة ، تكون النتيجة false
، لأنه عندما تزيل طريقة trim()
المسافات ، فإنها تنشئ String
جديدة باستخدام عامل التشغيل new
.
التالي نرى:
result += "flexibleCode" == "flexibleCode" ? "2" : "3";
لا يوجد سر هنا ؛ الصفوف هي نفسها في تجمع الصف. هذه المقارنة تعود true
.
ثم لدينا:
result += new String("doYourBest") == new String("doYourBest") ? "4" : "5";
استخدام خيوط new
لإنشاء خطين جديدين ولا يهم إذا كانت قيمهما متساوية أم لا. في هذه الحالة ، ستكون المقارنة false
حتى لو كانت القيم هي نفسها.
التالي:
result += new String("noBugsProject") .equals("noBugsProject") ? "6" : "7";
نظرًا لأننا استخدمنا طريقة equals()
، سيتم مقارنة قيمة السلسلة ، وليس مثيل الكائن.
في هذه الحالة ، لا يهم ما إذا كانت الكائنات مختلفة أم لا ، حيث يتم مقارنة القيمة. والنتيجة true
.
أخيرًا ، لدينا:
result += new String("breakYourLimits").intern() == new String("breakYourLimits").intern() ? "8" : "9";
كما رأيت سابقًا ، تضع طريقة intern()
سلسلة في تجمع سلسلة. يشير كلا الخطين إلى نفس الكائن ، لذلك في هذه الحالة true
.
أخطاء السلسلة الشائعة
قد يكون من الصعب تحديد ما إذا كان خطان يشيران إلى نفس الكائن أم لا ، خاصة عندما تحتوي السطور على نفس القيمة. من المفيد أن نتذكر أن استخدام new
يؤدي دائمًا إلى إنشاء كائن جديد في الذاكرة ، حتى إذا كانت قيم السلسلة هي نفسها.
يمكن أن يكون استخدام أساليب String
لمقارنة مراجع الكائن أمرًا صعبًا. تكمن الخصوصية في أنه إذا قامت الطريقة بتغيير شيء ما في السطر ، فستكون هناك مراجع مختلفة للكائنات.
بعض الأمثلة للمساعدة في توضيح:
System.out.println("duke".trim() == "duke".trim());
ستكون هذه المقارنة صحيحة لأن طريقة trim()
لا تنشئ خطًا جديدًا.
System.out.println(" duke".trim() == "duke".trim());
في هذه الحالة ، تنشئ طريقة trim()
الأولى trim()
خطًا جديدًا ، لأن الطريقة ستؤدي وظيفتها وبالتالي ستكون الروابط مختلفة.
أخيرًا ، عندما يؤدي trim()
وظيفته ، فإنه ينشئ خطًا جديدًا:
ما يجب تذكره عن السلاسل
الصفوف غير قابلة للتغيير ، لذلك لا يمكن تغيير حالة الصف.
لحفظ الذاكرة ، يخزن JVM سلاسل في تجمع سلسلة. عند إنشاء خط جديد ، يتحقق JVM من قيمته ويشير إلى كائن موجود. إذا لم يكن للتجمع صف بهذه القيمة ، فسيقوم JVM بإنشاء صف جديد.
عامل التشغيل " ==
" يقارن مراجع الكائن. يقارن الأسلوب equals()
قيم السلسلة. سيتم تطبيق نفس القاعدة على جميع الكائنات.
عند استخدام عامل التشغيل new
، سيتم إنشاء خط جديد في كومة الذاكرة المؤقتة (ملاحظة المترجم - مكتوب في الأصل أنه موجود في التجمع ، ولكن هذا ليس كذلك ، وذلك بفضل zagayevskiy ) ، حتى إذا كان هناك خط بنفس القيمة.
الجواب
الإجابة على هذه المشكلة هي د. وستكون النتيجة 12568.
يتبع ...