فيما يتعلق بالأسئلة الواردة في هذه السلسلة، يجب على كل من يدرس لغة Java أن يفهمها. بالطبع، لا يهم إذا كنت تتعلم جافا من أجل المتعة فقط. إذا كنت تعتقد أنك تجاوزت مستوى المبتدئين ولكنك لا تفهم هذه المشكلات جيدًا، فيرجى إضافة نفسك إلى فريق المبتدئين.
السؤال 1: ماذا أقول؟
سلسلة s = "مرحبا بالعالم!";
لقد فعل الكثير من الأشخاص هذا، ولكن ما الذي نعلنه بالضبط؟ الجواب عادة هو: سلسلة نصية تحتوي على "Hello World!". غالبًا ما تكون مثل هذه الإجابات الغامضة مصدرًا لمفاهيم غير واضحة. إذا كنت ستعطي إجابة دقيقة، فمن المحتمل أن نصف الأشخاص سيجيبون عليها بشكل خاطئ.
يعلن هذا البيان عن مرجع إلى كائن، يسمى "s"، والذي يمكنه الإشارة إلى أي كائن من النوع String. حاليًا، يشير إلى كائن نوع السلسلة "Hello World!". وهذا ما حدث بالفعل. لم نعلن عن كائن سلسلة، لقد أعلنا للتو عن متغير مرجعي يمكنه الإشارة فقط إلى كائن السلسلة. لذلك، إذا قمت بتشغيل جملة أخرى بعد العبارة الآن:
سلسلة سلسلة = الصورة؛
لقد أعلنا عن مرجع آخر يمكن أن يشير فقط إلى كائن السلسلة، المسمى سلسلة. لم يتم إنشاء كائن ثانٍ. لا تزال السلسلة تشير إلى الكائن الأصلي، أي أنها تشير إلى نفس الكائن مثل s.
السؤال 2: ما الفرق بين طريقة "==" وطريقة التساوي؟
يتم استخدام عامل التشغيل == خصيصًا لمقارنة قيم المتغيرات من أجل المساواة. أسهل شيء يمكن فهمه هو:
انسخ رمز الكود كما يلي:
كثافة العمليات = 10؛
كثافة العمليات ب = 10؛
إذن a==b سيكون صحيحا.
ولكن ما يصعب فهمه هو:
انسخ رمز الكود كما يلي:
سلسلة أ=سلسلة جديدة("foo");
String b=new String("foo");
ثم سيعود a==b خطأ.
وفقًا للمنشور السابق، فإن متغيرات الكائن هي في الواقع مراجع، وتشير قيمها إلى عنوان الذاكرة حيث يوجد الكائن، وليس الكائن نفسه. يستخدم كل من a وb عامل التشغيل الجديد، مما يعني أنه سيتم إنشاء سلسلتين بالمحتوى "foo" في الذاكرة نظرًا لوجود "اثنين"، فإنهما يقعان بشكل طبيعي في عناوين ذاكرة مختلفة. إن قيم a و b هي في الواقع قيم عنوانين مختلفين للذاكرة، لذا باستخدام عامل التشغيل "=="، ستكون النتيجة خاطئة. صحيح أن الكائنات المشار إليها بواسطة a وb تحتوي على المحتوى "foo" ويجب أن تكون "متساوية"، لكن عامل التشغيل == لا يتضمن مقارنة محتويات الكائن.
إن مقارنة محتويات الكائن هو بالضبط ما تفعله طريقة التساوي.
ألق نظرة على كيفية تنفيذ طريقة يساوي لكائن الكائن:
انسخ رمز الكود كما يلي:
منطقية يساوي (كائن س) {
إرجاع هذا==س؛
}
تستخدم كائنات الكائن عامل التشغيل == افتراضيًا. لذا، إذا لم يتجاوز فصلك الذي أنشأته بنفسك طريقة يساوي، فسيحصل فصلك على نفس النتيجة باستخدام يساوي واستخدام ==. ويمكن أيضًا ملاحظة أن طريقة يساوي الكائن لا تحقق الهدف الذي يجب أن تحققه طريقة يساوي: مقارنة ما إذا كانت محتويات كائنين متساوية. نظرًا لأن الإجابة يجب أن يتم تحديدها بواسطة منشئ الفصل، فإن الكائن يترك هذه المهمة لمنشئ الفصل.
ألق نظرة على الطبقة المتطرفة:
انسخ رمز الكود كما يلي:
وحش الطبقة{
محتوى السلسلة الخاصة؛
...
منطقية يساوي (كائن آخر) {إرجاع صحيح؛}
}
لقد تجاوزت طريقة التساوي. يؤدي هذا التنفيذ إلى إرجاع المقارنات بين مثيلات Monster دائمًا إلى الحقيقة بغض النظر عن محتوياتها.
لذلك، عند استخدام طريقة يساوي لتحديد ما إذا كانت محتويات كائن ما متساوية، من فضلك لا تعتبر ذلك أمرا مفروغا منه. لأنك ربما تعتقد أنهما متساويان، لكن مؤلف هذا الفصل لا يعتقد ذلك، ويتحكم هو في تنفيذ طريقة التساوي في الفصل. إذا كنت بحاجة إلى استخدام طريقة يساوي، أو استخدام أي مجموعة تعتمد على رمز التجزئة (HashSet، HashMap، HashTable)، يرجى التحقق من مستند جافا لتأكيد كيفية تنفيذ منطق يساوي لهذه الفئة.
السؤال 3: هل تغيرت السلسلة؟
لا. نظرًا لأن السلسلة مصممة لتكون فئة غير قابلة للتغيير، فإن جميع كائناتها هي كائنات غير قابلة للتغيير. يرجى إلقاء نظرة على الكود التالي:
انسخ رمز الكود كما يلي:
سلسلة ق = "مرحبا"؛
ق = ق + "العالم!";
هل تغير الهدف الذي أشار إليه s؟ يمكن استخلاص هذا الاستنتاج بسهولة من الاستنتاج الوارد في المقالة الأولى من هذه السلسلة. دعونا نرى ما حدث. في هذا الكود، أشار s في الأصل إلى كائن سلسلة يحتوي على المحتوى "Hello"، ثم أجرينا عملية + على s. هل تغير الكائن المشار إليه بـ s؟ الجواب هو لا. في هذا الوقت، لم يعد s يشير إلى الكائن الأصلي، ولكن إلى كائن سلسلة آخر بالمحتوى "Hello World!". لا يزال الكائن الأصلي موجودًا في الذاكرة، لكن المتغير المرجعي s لم يعد يشير إليه.
من خلال الشرح أعلاه، يمكننا بسهولة استخلاص نتيجة أخرى إذا تم تعديل السلاسل بشكل متكرر بطرق مختلفة، أو تم تعديلها بشكل غير متوقع، فإن استخدام السلسلة لتمثيل سلسلة سيؤدي إلى زيادة عبء الذاكرة. نظرًا لأنه لا يمكن تغيير كائن السلسلة بعد إنشائه، فإن كائن السلسلة مطلوب لتمثيل كل سلسلة مختلفة. في هذا الوقت، يجب أن تفكر في استخدام فئة StringBuffer، التي تسمح بالتعديل، بدلاً من إنشاء كائن جديد لكل سلسلة مختلفة. علاوة على ذلك، فإن تغيير السياسة بين هذين النوعين بسيط للغاية.
في الوقت نفسه، يمكننا أيضًا معرفة أنه إذا كنت تريد استخدام سلسلة بنفس المحتوى، فلن تحتاج إلى إنشاء سلسلة جديدة في كل مرة. على سبيل المثال، إذا أردنا تهيئة متغير مرجعي لسلسلة يسمى s في المُنشئ وتعيينه إلى القيمة الأولية، فيجب علينا القيام بذلك:
انسخ رمز الكود كما يلي:
عرض الطبقة العامة {
سلسلة خاصة؛
…
العرض العام {
s = "القيمة الأولية";
}
…
}
بدلاً من s = new String("القيمة الأولية");
سيستدعي الأخير المُنشئ في كل مرة لإنشاء كائن جديد، ذو أداء منخفض واستهلاك كبير للذاكرة، ولا معنى له نظرًا لأنه لا يمكن تغيير كائن السلسلة، يمكن استخدام كائن سلسلة واحد فقط لتمثيل سلسلة بنفس المحتوى . بمعنى آخر، إذا قمت باستدعاء المنشئ أعلاه عدة مرات لإنشاء أهداف متعددة، فستشير جميع سمات نوع السلسلة الخاصة بها إلى نفس الهدف.
يعتمد الاستنتاج أعلاه أيضًا على حقيقة أنه بالنسبة لثوابت السلسلة، إذا كانت المحتويات هي نفسها، يعتقد تدريب Java في قوانغتشو أنها تمثل نفس كائن السلسلة. سيؤدي استدعاء المُنشئ باستخدام الكلمة الأساسية new دائمًا إلى إنشاء هدف جديد، بغض النظر عما إذا كان المحتوى هو نفسه أم لا.
أما لماذا يجب وصف فئة String بأنها فئة غير قابلة للتغيير، فسيتم تحديدها حسب الغرض منها. في الواقع، ليست السلسلة فحسب، بل أيضًا العديد من الفئات في مكتبة فئة Java القياسية غير قابلة للتغيير. عند تطوير النظام، نحتاج أحيانًا إلى وصف الفئات غير القابلة للتغيير لتمرير مجموعة من القيم المترابطة، وهو أيضًا مظهر من مظاهر التفكير الموجه نحو الأهداف. تتمتع الفئات غير القابلة للتغيير ببعض المزايا، على سبيل المثال، نظرًا لأن الغرض منها هو للقراءة فقط، فلن تكون هناك مشاكل في الوصول المتزامن عبر سلاسل رسائل متعددة. بالطبع، هناك أيضًا بعض أوجه القصور، على سبيل المثال، كل موقف مختلف يحتاج إلى كائن لتمثيله، مما قد يسبب مشاكل وظيفية. لذلك، توفر مكتبة فئة Java القياسية أيضًا إصدارًا متغيرًا، وهو StringBuffer.