ما هي الكائنات غير القابلة للتغيير؟
كائن السلسلة غير قابل للتغيير، ولكن هذا يعني أنه لا يمكنك تغيير قيمته عن طريق استدعاء أساليبه العامة.
كما نعلم جميعًا، في Java، فئة السلسلة غير قابلة للتغيير. إذن ما هي الكائنات غير القابلة للتغيير بالضبط؟ يمكنك التفكير في الأمر بهذه الطريقة: إذا لم يتمكن الكائن من تغيير حالته بعد إنشائه، فهو غير قابل للتغيير. لا يمكن تغيير الحالة، مما يعني أنه لا يمكن تغيير متغيرات الأعضاء داخل الكائن، بما في ذلك قيم أنواع البيانات الأساسية، ولا يمكن لمتغيرات الأنواع المرجعية أن تشير إلى كائنات أخرى، ولا يمكن أن تشير حالة الكائنات إلى أنواع مرجعية يتم تغييرها.
التمييز بين الكائنات ومراجع الكائنات
بالنسبة للمبتدئين في Java، هناك دائمًا شكوك حول كون السلسلة كائنًا غير قابل للتغيير. انظر إلى الكود أدناه:
String s = "ABCabc";System.out.println("s = " + s);s = "123456";System.out.println("s = " + s);النتيجة المطبوعة هي:
الصورة = ABCabc الصورة = 123456
قم أولاً بإنشاء كائن سلسلة s، ثم اجعل قيمة s هي "ABCabc"، ثم اجعل قيمة s هي "123456". وكما يتبين من النتائج المطبوعة، فقد تغيرت قيمة s بالفعل. فلماذا لا تزال تقول أن كائنات السلسلة غير قابلة للتغيير؟ في الواقع، هناك سوء فهم هنا: s هو مجرد إشارة إلى كائن سلسلة، وليس الكائن نفسه. الكائن عبارة عن منطقة ذاكرة في الذاكرة، وكلما زاد عدد متغيرات الأعضاء، زادت المساحة التي تشغلها منطقة الذاكرة هذه. المرجع هو مجرد بيانات مكونة من 4 بايت تخزن عنوان الكائن الذي يشير إليه ويمكن الوصول إلى الكائن من خلال هذا العنوان.
بمعنى آخر، s مجرد مرجع يشير إلى كائن معين. عند تنفيذ s=123456، يتم إنشاء كائن جديد "123456"، ويشير المرجع إلى هذا الكائن مرة أخرى ، فإن الكائن الأصلي "ABCabc" لا يزال موجودًا في الذاكرة ولم يتغير. يظهر هيكل الذاكرة في الشكل أدناه:
أحد الاختلافات بين Java وC++ هو أنه في Java من المستحيل تشغيل الكائن نفسه مباشرة، حيث تتم الإشارة إلى جميع الكائنات بواسطة مرجع، ويجب عليك استخدام هذا المرجع للوصول إلى الكائن نفسه، بما في ذلك الحصول على قيمة متغير العضو وتغييره متغير العضو لأساليب كائن الاتصال، وما إلى ذلك. في لغة C++، هناك ثلاثة أشياء: المراجع، والكائنات، والمؤشرات، وكلها يمكنها الوصول إلى الكائنات. في الواقع، المراجع في Java والمؤشرات في C++ متشابهة من الناحية المفاهيمية، فهي قيم عنوان الكائنات المخزنة في الذاكرة، ومع ذلك، تفقد المراجع في Java بعض المرونة، على سبيل المثال، لا يمكن استخدام المراجع في Java مثل الجمع والطرح يتم تنفيذها مثل المؤشرات في C++.
لماذا كائنات السلسلة غير قابلة للتغيير؟
لفهم ثبات السلسلة، قم أولاً بإلقاء نظرة على متغيرات الأعضاء في فئة السلسلة. في JDK1.6، تتضمن متغيرات أعضاء السلسلة ما يلي:
سلسلة الفئة النهائية العامة تنفذ java.io.Serializable, Comparable<String>, CharSequence{ /** يتم استخدام القيمة لتخزين الأحرف */ قيمة char النهائية الخاصة[]; المستخدمة */ الخاص النهائي int Off; /** العدد هو عدد الأحرف في السلسلة */ الخاص Final int count; الافتراضي ل 0في JDK1.7، قامت فئة String ببعض التغييرات، وبشكل أساسي تغيير السلوك عند تنفيذ طريقة السلسلة الفرعية، وهو ما لا علاقة له بموضوع هذه المقالة. لا يوجد سوى متغيرين رئيسيين من فئة String في JDK1.7:
سلسلة الفئة النهائية العامة تنفذ java.io.Serializable, Comparable<String>, CharSequence { /** يتم استخدام القيمة لتخزين الأحرف */ قيمة char النهائية الخاصة[]; تجزئة int الخاصة؛ // الافتراضي هو 0كما يتبين من الكود أعلاه، فإن فئة String في Java هي في الواقع عبارة عن تغليف لمصفوفة أحرف. في JDK6، القيمة عبارة عن مصفوفة مغلفة بسلسلة، والإزاحة هي موضع البداية للسلسلة في مصفوفة القيمة، والعدد هو عدد الأحرف التي تشغلها السلسلة. في JDK7، يوجد متغير قيمة واحد فقط، أي أن جميع الأحرف في القيمة تنتمي إلى كائن السلسلة. لا يؤثر هذا التغيير على مناقشة هذه المقالة. بالإضافة إلى ذلك، يوجد متغير عضو تجزئة، وهو عبارة عن ذاكرة تخزين مؤقت لقيمة التجزئة لكائن السلسلة، كما أن متغير العضو هذا غير ذي صلة بمناقشة هذه المقالة. في Java، تعد المصفوفات أيضًا كائنات (يرجى الرجوع إلى مقالتي السابقة خصائص المصفوفات في Java). لذا فإن القيمة هي مجرد مرجع يشير إلى كائن مصفوفة حقيقي. في الواقع، بعد تنفيذ الكود String s = “ABCabc”;، يجب أن يكون تخطيط الذاكرة الحقيقي كما يلي:
المتغيرات الثلاثة، القيمة والإزاحة والعدد، كلها خاصة، ولا يتم توفير طرق عامة مثل setValue وsetOffset وsetCount لتعديل هذه القيم، لذلك لا يمكن تعديل السلسلة خارج فئة السلسلة. وهذا يعني أنه بمجرد تهيئته، لا يمكن تعديله، ولا يمكن الوصول إلى هؤلاء الأعضاء الثلاثة خارج فئة السلسلة. بالإضافة إلى ذلك، فإن المتغيرات الثلاثة، القيمة والإزاحة والعدد، كلها نهائية، مما يعني أنه داخل فئة السلسلة، بمجرد تهيئة هذه القيم الثلاث، لا يمكن تغييرها. لذلك يمكن اعتبار كائن السلسلة غير قابل للتغيير.
لذلك من الواضح أن هناك بعض الطرق في String، ويمكن أن يؤدي الاتصال بها إلى الحصول على القيمة المتغيرة. تتضمن هذه الأساليب سلسلة فرعية، واستبدال، واستبدال الكل، وtoLowerCase، وما إلى ذلك. على سبيل المثال الكود التالي:
String a = "ABCabc"; System.out.println("a = " + a);النتيجة المطبوعة هي:
أ = ABCabca = aBCabc
ثم يبدو أن قيمة a قد تغيرت، ولكن في الواقع هو نفس سوء الفهم. مرة أخرى، a هو مجرد مرجع، وليس كائن سلسلة حقيقي. عند استدعاء a.replace('A', 'a')، تقوم الطريقة داخليًا بإنشاء كائن سلسلة جديد وإعادة تعيين الكائن الجديد إلى Cited a. يمكن أن يوضح الكود المصدري لطريقة الاستبدال في السلسلة المشكلة:
يمكن للقراء التحقق من الطرق الأخرى بأنفسهم، حيث يقومون جميعًا بإعادة إنشاء كائن سلسلة جديد داخل الطريقة وإرجاع هذا الكائن الجديد ولن يتغير. هذا هو السبب في أن الأساليب مثل استبدال وسلسلة فرعية وtoLowerCase وما إلى ذلك جميعها لها قيم إرجاع. وهذا أيضًا هو السبب في أن الاتصال كما يلي لا يغير قيمة الكائن:
String ss = "123456";System.out.println("ss = " + ss);ss.replace('1', '0');System.out.println("ss = " + ss);طباعة النتيجة:
سس = 123456 سس = 123456
هل كائنات السلسلة غير قابلة للتغيير حقًا؟
كما يتبين مما سبق، فإن متغيرات أعضاء السلسلة نهائية خاصة، أي أنه لا يمكن تغييرها بعد التهيئة. ومن بين هؤلاء الأعضاء، تكون القيمة خاصة لأنها متغير مرجعي، وليست كائنًا حقيقيًا. يتم تعديل القيمة بواسطة Final، مما يعني أن Final لم يعد بإمكانه الإشارة إلى كائنات مصفوفة أخرى، فهل يمكنني تغيير المصفوفة التي تشير إليها القيمة؟ على سبيل المثال، قم بتغيير الحرف الموجود في موضع معين في المصفوفة إلى الشرطة السفلية "_". على الأقل لا يمكننا القيام بذلك في الكود العادي الذي نكتبه بأنفسنا، لأننا لا نستطيع الوصول إلى مرجع القيمة هذا على الإطلاق، ناهيك عن تعديل المصفوفة من خلال هذا المرجع.
فكيف يمكننا الوصول إلى الأعضاء الخاصين؟ هذا صحيح، باستخدام الانعكاس، يمكنك عكس سمة القيمة في كائن السلسلة، ثم تغيير بنية المصفوفة من خلال مرجع القيمة الذي تم الحصول عليه. هنا هو رمز المثال:
public static void testReflection() throws Exception { // أنشئ السلسلة "Hello World" وقم بتعيينها للمرجع s String s = "Hello World"; World // احصل على حقل القيمة في حقل فئة السلسلة valueFieldOfString = String.class.getDeclaredField("value"); // تغيير إذن الوصول لسمة القيمة valueFieldOfString.setAccessible(true); // احصل على قيمة سمة القيمة في كائن s char[] value = (char[]) valueFieldOfString.get(s); // تغيير الحرف الخامس في المصفوفة المشار إليها حسب القيمة value[5] = '_' ; System.out.println("s = " + s);النتيجة المطبوعة هي:
s = أهلا بالعالم s = Hello_World
في هذه العملية، يشير s دائمًا إلى نفس كائن السلسلة، ولكن قبل وبعد الانعكاس، يتغير كائن السلسلة. بمعنى آخر، يمكن تعديل الكائن "غير القابل للتغيير" من خلال الانعكاس. لكن بشكل عام لا نفعل هذا. يمكن أن يوضح مثال الانعكاس هذا أيضًا مشكلة: إذا كانت حالة الكائن والكائنات الأخرى التي يتكون منها يمكن أن تتغير، فمن المحتمل أن هذا الكائن ليس كائنًا غير قابل للتغيير. على سبيل المثال، يتم دمج كائن السيارة مع كائن العجلة، على الرغم من إعلان كائن العجلة باعتباره نهائيًا خاصًا، إلا أن الحالة الداخلية لكائن العجلة يمكن أن تتغير، لذا لا يمكن ضمان أن يكون كائن السيارة غير قابل للتغيير.