وقت جيد من اليوم!
كُتب المقال في أعقاب منشور
"الأشياء التي لم تعرفها عن جافا" من قبل مؤلف آخر ، والتي صنفتها على أنها "للمبتدئين". عند قراءتها والتعليق عليها ، أدركت أن هناك عددًا من الأشياء المثيرة للاهتمام التي تعلمتها ، والتي قمت بالفعل بالبرمجة في جافا لأكثر من عام. ربما تبدو هذه الأشياء غريبة بالنسبة لشخص آخر.
الحقائق التي ، من وجهة نظري ، قد تكون مفيدة للمبتدئين ، قمت بإزالتها في "المفسدين". بعض الأشياء قد لا تزال مثيرة للاهتمام بالنسبة لتلك الأكثر خبرة. على سبيل المثال ، أنا شخصياً لم أعرف حتى وقت كتابة المقال أن Boolean.hashCode (صواب) == 1231 و Boolean.hashCode (false) == 1237.
للمبتدئين- Boolean.hashCode (صواب) == 1231
- Boolean.hashCode (false) == 1237
- Float.hashCode (value) == Float.floatToIntBits (value)
- Double.hashCode (value) - xor من halfwords 32 بت الأول والثاني Double.doubleToLongBits (قيمة)
لم يعد Object.hashCode () عنوان كائن في الذاكرة
تنويه: هذا هو jvm التفاصيل من Oracle (HotSpot).
ذات مرة كان هذا هو الحال.من jdk1.2.1 / docs / api / java / lang / Object.html # hashCode ():
بقدر ما هو عملي معقول ، فإن طريقة hashCode المعرّفة من قِبل كائن الفئة لا تُرجع أعدادًا صحيحة مميزة للكائنات المتميزة. (يتم تنفيذ هذا عادةً عن طريق تحويل العنوان الداخلي للكائن إلى عدد صحيح ، ولكن تقنية التنفيذ هذه غير مطلوبة من قبل لغة برمجة JavaTM.)
ثم رفضوا ذلك. هذا ما يقوله
javadoc لـ jdk 12 .
اقترح
vladimir_dolzhenko أنه يمكن استعادة السلوك القديم باستخدام -XX: hashCode = 4. وكان تغيير السلوك نفسه تقريبا من الإصدار 1.2 جافا.
Integer.valueOf (15) == Integer.valueOf (15)؛ Integer.valueOf (128)! = Integer.valueOf (128)
إخلاء المسئولية: هذا جزء من
jls .
من الواضح أنه عند مقارنة مغلفين مع المشغل == (! =) ، لن يحدث أي تسمم تلقائي تلقائي. وبصفة عامة ، فإن المساواة الأولى هي التي تربك. الحقيقة هي أنه بالنسبة لقيم الأعداد الصحيحة i: -129 <i <128 كائنات مجمعة عدد صحيح يتم تخزينها مؤقتًا. لذلك ، بالنسبة إلى i من هذا النطاق ، لا يُنشئ Integer.valueOf (i) كائنًا جديدًا في كل مرة ، ولكنه يُرجع كائنًا تم إنشاؤه بالفعل. بالنسبة إلى i التي لا تقع في هذا النطاق ، يقوم Integer.valueOf (i) دائمًا بإنشاء كائن جديد. لذلك ، إذا كنت لا تراقب عن كثب ما هو بالضبط وكيف تتم مقارنتها بدقة ، يمكنك كتابة التعليمات البرمجية التي يبدو أنها تعمل وحتى مغطاة الاختبارات ، ولكن في نفس الوقت تحتوي على "أشعل النار".
في jvm من Oracle (HotSpot) ، يمكن تغيير الحد الأعلى للتخزين المؤقت من خلال الخاصية
"java.lang.Integer.IntegerCache.high" .
في بعض الحالات ، يتم حل قيم الحقول الثابتة البدائية أو السلسلة النهائية لفئة أخرى في وقت الترجمة
يبدو مربكا ، والبيان طويل بعض الشيء. المعنى هو هذا. إذا كان لدينا فئة تعرّف ثوابت الأنواع البدائية أو السلاسل على أنها حقول ثابتة نهائية مع التهيئة الفورية ،
class AnotherClass { public final static String CASE_1 = "case_1"; public final static String CASE_2 = "case_2"; }
عند استخدامها في فصول أخرى ،
class TheClass {
يتم حل قيم هذه الثوابت ("case_1" ، "case_2") في وقت الترجمة. ويتم إدراجها في الكود كقيم ، وليس كروابط. أي إذا استخدمنا هذه الثوابت من المكتبة ، ثم حصلنا على نسخة جديدة من المكتبة التي تغيرت فيها قيم الثوابت ، فيجب أن نعيد ترجمة المشروع. خلاف ذلك ، قد يستمر استخدام القيم الثابتة القديمة في التعليمات البرمجية.
يتم ملاحظة هذا السلوك في جميع الأماكن التي يلزم فيها استخدام تعبيرات ثابتة (على سبيل المثال ، رمز التبديل / الحالة) ، أو يُسمح للمترجم بتحويل التعبيرات إلى ثابت ويمكنه القيام بذلك.
لا يمكن استخدام هذه الحقول في تعبيرات ثابتة بمجرد إزالة التهيئة الفورية عن طريق نقل التهيئة إلى الكتلة الثابتة.
للمبتدئينفي ظل ظروف معينة ، قد لا يعمل جامع البيانات المهملة مطلقًا.
نتيجة لذلك ، لن يتم إطلاق أي وضع نهائي (). لذلك ، يجب عدم كتابة التعليمات البرمجية التي تعتمد على حقيقة أن final (() ستعمل دائمًا. نعم ، وإذا وصل الكائن إلى القمامة قبل نهاية البرنامج ، فعلى الأرجح لن يتم تجميعها بواسطة المجمع.
يمكن استدعاء طريقة finalize () لكائن معين مرة واحدة فقط.
في finalize () ، يمكننا أن نجعل الكائن مرئيًا مرة أخرى ، ولن يقوم جامع البيانات المهملة "بإزالته" هذه المرة. عندما يقع هذا الكائن في البيانات المهملة مرة أخرى ، سيتم "تجميع" دون استدعاء finalize (). إذا تم طرح استثناء في الصيغة النهائية () ولا يزال الكائن غير مرئي لأي شخص ، فسيتم "تجميعه" بعد ذلك. وضع اللمسات الأخيرة () لن يتم استدعاء مرة أخرى.
الدفق الذي سيتم فيه وضع اللمسات الأخيرة () سيتم استدعاؤه غير معروف مسبقًا
إنه مضمون فقط أن هذا الموضوع سيكون خاليًا من الأقفال المرئية بواسطة البرنامج الرئيسي.
تباطؤ وجود أسلوب finalize () overridden على الكائنات عملية جمع البيانات المهملة
ما يكمن على السطح هو الحاجة إلى التحقق من توفر الكائنات - مرة واحدة قبل استدعاء finalize () ، مرة واحدة في أحد مجموعات البيانات المهملة التالية.
من الصعب للغاية محاربة الجمود في النهاية ()
في اللمسات الأخيرة غير التافهة () ، قد تكون الأقفال ضرورية ، والتي يصعب تصحيحها نظرًا للخصائص الموضحة أعلاه.
Object.finalize () منذ أن تم تمييز الإصدار 9 من java على أنه مهمل!
وهذا ليس مفاجئًا ، بالنظر إلى التفاصيل الموضحة أعلاه. تهيئة مفردة كسول الكلاسيكية: تأمين مزدوج مطلوب
هناك فكرة خاطئة عن هذا الموضوع مفادها أن النهج التالي (المصطلح "تحقق مزدوج") ، والذي يبدو منطقيًا جدًا ، يعمل دائمًا:
public class UnsafeDCLFactory { private Singleton instance; public Singleton get() { if (instance == null) {
نحن ننظر إلى ما إذا كان يتم إنشاء الكائن (اقرأ 1 ، تحقق 1). إذا كان الأمر كذلك ، ثم إعادته. إذا لم يكن كذلك ، فقم بضبط القفل ، وتأكد من عدم إنشاء الكائن ، وقم بإنشاء الكائن (القفل تمت إزالته) ، ثم أعد الكائن.
النهج لا يعمل للأسباب التالية.
(اقرأ 1 ، تحقق من 1) و (اقرأ 3) ليست متزامنة. وفقًا لمفهوم نموذج ذاكرة java ، قد لا تكون التغييرات التي تم إجراؤها في سلسلة رسائل أخرى مرئية لمؤشر الترابط الخاص بنا حتى تتم مزامنتها. شكرًا
mk2 على التعليق ، إليك الوصف الصحيح للمشكلة:
نعم ، لا تتم مزامنة read1 و read3 ، ولكن المشكلة ليست في مؤشر ترابط آخر. وحقيقة أن القراءات غير المتزامنة يمكن إعادة ترتيبها ، أي read1! = فارغة ، ولكن read3 == فارغة. وفي الوقت نفسه ، بسبب "مثيل = جديد Singleton () ؛" يمكننا الحصول على إشارة إلى الكائن قبل إنشائه بالكامل ، وهذه مشكلة مزامنة مع مؤشر ترابط آخر ، ولكن ليس read1 و read3 ، لكن read3 والوصول لأعضاء المثال.
يتم التعامل معها إما عن طريق إضافة التزامن أثناء القراءة ، أو عن طريق وضع علامة على المتغير الذي يعيش فيه الارتباط المفرد ، متقلبة. (يعمل الحل المتقلّب فقط مع java 5+. قبل ذلك ، كان لدى java نموذج ذاكرة مع عدم اليقين في هذا الموقف.) فيما يلي إصدار عمل (مع تحسين إضافي - تمت إضافة المتغير المحلي `res` لتقليل عدد القراءات من الحقل المتقلب).
public class SafeLocalDCLFactory { private volatile Singleton instance; public Singleton getInstance() { Singleton res = instance;
الرمز مأخوذ
من هنا ، من موقع Alexei Shipilev. مزيد من التفاصيل حول هذه المشكلة يمكن العثور عليها.
كلمة "التهيئة عند الطلب" - وهي تهيئة "كسولة" جميلة جدًا للأحادية المنفردة
java تهيئة الفئات (كائنات فئة) فقط حسب الضرورة ، وبطبيعة الحال ، مرة واحدة فقط. ويمكنك الاستفادة من هذا! آلية المصطلح التهيئة عند الطلب تفعل ذلك. (الرمز
من هنا .)
public class Something { private Something() {} private static class LazyHolder { static final Something INSTANCE = new Something(); } public static Something getInstance() { return LazyHolder.INSTANCE; } }
سيتم تهيئة الفئة LazyHolder فقط في المرة الأولى التي يتم فيها استدعاء Something.getInstance (). سوف تتأكد Jvm من أن هذا يحدث مرة واحدة فقط ، علاوة على ذلك ، بكفاءة عالية - إذا تمت تهيئة الفصل بالفعل ، فلن يكون هناك أي تكاليف إضافية. وفقًا لذلك ، سيتم أيضًا تهيئة LazyHolder.INSTANCE مرة واحدة ، و "كسول" وخيط آمن.
قطعة من المواصفات حول النفقات العامةإذا اكتمل إجراء التهيئة هذا بشكل طبيعي وتمت تهيئة كائن Class بالكامل وجاهز للاستخدام ، فحينئذٍ لم يعد الاحتجاج بإجراء التهيئة ضروريًا وقد يتم حذفه من الكود - على سبيل المثال ، عن طريق تصحيحه أو إعادة إنشاء الكود بطريقة أخرى .
مصدر. وبصفة عامة ، لا تعتبر الأحرف المفردة أفضل ممارسة.
المواد لم تنته بعد. لذا ، إذا كانت "متناول" الأيدي وما تمت كتابته بالفعل ستكون مطلوبة ، سأكتب بطريقة أو بأخرى حول هذا الموضوع.
شكرا للتعليقات البناءة. تم توسيع العديد من الأماكن في المقالة بفضل
سيرجي غورنوستاييف ،
فلاديمير_دولزينكو ،
أوليك كوربياك ،
MK2 .