مرة أخرى حول ImmutableList في جاوة

في مقالتي السابقة ، " الإخفاء حول ImmutableList في Java " ، اقترحت حلاً لمشكلة عدم وجود قوائم ثابتة في Java ، والتي لم يتم حلها ، لا الآن ولا في أي وقت مضى ، في Java.


بعد ذلك تم حل هذا الحل فقط على مستوى "هناك فكرة من هذا القبيل" ، وتم تنفيذ التطبيق في التعليمات البرمجية ، وبالتالي تم النظر إلى كل شيء بشكل متشكك. في هذه المقالة ، أقترح حلاً معدلاً. يتم نقل منطق الاستخدام و API إلى مستوى مقبول. تنفيذ التعليمات البرمجية يصل إلى مستوى تجريبي.


بيان المشكلة


سوف نستخدم التعاريف الواردة في المقال الأصلي. على وجه الخصوص ، هذا يعني أن ImmutableList هي قائمة ثابتة من الإشارات إلى بعض الكائنات. إذا تبين أن هذه الكائنات غير قابلة للتغيير ، فلن تكون القائمة أيضًا كائنًا ثابتًا ، على الرغم من الاسم. في الممارسة العملية ، من غير المحتمل أن يؤذي هذا أي شخص ، ولكن لتجنب التوقعات غير المبررة ، من الضروري ذكر ذلك.


من الواضح أيضًا أنه يمكن اختراق "ثبات" القائمة عن طريق الانعكاسات ، أو عن طريق إنشاء فصولك الخاصة في الحزمة نفسها ، يليها التسلق إلى الحقول المحمية في القائمة ، أو شيء مشابه.


بخلاف المقالة الأصلية ، لن نلتزم بمبدأ "الكل أو لا شيء": يبدو أن المؤلف يعتقد أنه إذا تعذر حل المشكلة على مستوى JDK ، فلا ينبغي فعل شيء. (في الواقع ، هناك سؤال آخر ، "لا يمكن حلها" أو "لم يكن لدى مؤلفي Java رغبة في حلها". يبدو لي أنه لا يزال من الممكن بإضافة واجهات وفئات وطرق إضافية لتقريب المجموعات الحالية من المظهر المرغوب ، على الرغم من أنه أقل جمالا مما لو كنت قد فكرت به على الفور ، ولكن الأمر الآن ليس كذلك.)


سننشئ مكتبة يمكنها التعايش بنجاح مع المجموعات الموجودة في Java.


الأفكار الرئيسية للمكتبة:


  • هناك MutableList و MutableList . عن طريق صب أنواع من المستحيل الحصول على واحد من الآخر.
  • في مشروعنا ، الذي نريد تحسينه باستخدام المكتبة ، استبدلنا جميع القوائم بأحد هاتين الواجهتين. إذا لم تتمكن من الاستغناء عن List ، فعند أول فرصة سنقوم بتحويل List من / إلى واحد من واجهتين. الأمر نفسه ينطبق على لحظات تلقي / نقل البيانات إلى مكتبات الطرف الثالث باستخدام List .
  • يجب إجراء التحويلات المتبادلة بين MutableList و MutableList ، List بأسرع ما يمكن (أي ، دون نسخ القوائم ، إن أمكن). بدون تحويلات "رخيصة" ذهابًا وإيابًا ، تبدأ الفكرة بأكملها في الظن.

تجدر الإشارة إلى أنه يتم النظر في القوائم فقط ، حيث يتم تنفيذها في الوقت الحالي فقط في المكتبة. ولكن لا شيء يمنع المكتبة من الإضافة إلى Set and Map s.


API


ImmutableList


يعد ImmutableList هو خليفة ReadOnlyList (والذي ، كما في المقالة السابقة ، عبارة عن واجهة List منسوخة يتم إلقاء جميع أساليب التحور منها). الأساليب المضافة:


 List<E> toList(); MutableList<E> mutable(); boolean contentEquals(Iterable<? extends E> iterable); 

يوفر أسلوب القائمة toList القدرة على تمرير قائمة غير ImmutableList إلى أجزاء من التعليمات البرمجية في انتظار List . يتم إرجاع مجمّع تُرجع فيه كل أساليب التعديل UnsupportedOperationException ، وتتم إعادة توجيه الطرق المتبقية إلى ImmutableList الأصلي.


تحويل الأسلوب mutable MutableList إلى قائمة MutableList . يتم إرجاع مجمّع يتم فيه إعادة توجيه كل الطرق إلى قائمة ImmutableList الأصلية حتى التغيير الأول. قبل التغيير ، يتم ربط برنامج التضمين من قائمة ImmutableList الأصلية ، مع نسخ محتوياته إلى قائمة ArrayList الداخلية ، حيث يتم إعادة توجيه جميع العمليات.


الغرض من طريقة contentEquals مقارنة محتويات القائمة مع محتويات Iterable التعسفي الذي تم تمريره (بالطبع ، هذه العملية ذات مغزى فقط لتلك التطبيقات Iterable التي لها ترتيب محدد للعناصر).


لاحظ أنه في تطبيقنا لـ ReadOnlyList ، listIterator iterator و listIterator java.util.Iterator / java.util.Iterator القياسية. تحتوي هذه التكرارات على طرق تعديل يجب إخمادها عن طريق طرح UnsupportedOperationException . سيكون من الأفضل إعداد ReadOnlyIterator بنا ، لكن في هذه الحالة لم نتمكن من الكتابة for (Object item : immutableList) ، والذي من شأنه أن يفسد على الفور كل متعة استخدام المكتبة.


MutableList


MutableList هو سليل القائمة العادية. الأساليب المضافة:


 ImmutableList<E> snapshot(); void releaseSnapshot(); boolean contentEquals(Iterable<? extends E> iterable); 

تم تصميم طريقة snapshot للحصول على "لقطة" للحالة الحالية من MutableList باعتبارها MutableList . يتم حفظ "لقطة" داخل MutableList ، وإذا لم تتغير الحالة في وقت استدعاء الأسلوب التالي ، ImmutableList نفس مثيل ImmutableList . يتم تجاهل "اللقطة" المخزنة في الداخل في المرة الأولى التي يتم فيها استدعاء أي طريقة تعديل ، أو عند releaseSnapshot . يمكن استخدام طريقة releaseSnapshot لحفظ الذاكرة إذا كنت متأكدًا من أن أحدًا لن يحتاج إلى "لقطة" ، ولكن لن يتم استدعاء أساليب التعديل قريبًا.


Mutabor


توفر الفئة Mutabor مجموعة من الأساليب الثابتة التي تمثل "نقاط الدخول" إلى المكتبة.


نعم ، يُطلق على المشروع الآن "mutabor" (إنه يتوافق مع "mutable" ، ويعني في الترجمة "سوف أتحول" ، وهو ما يتفق جيدًا مع فكرة "تحويل" بعض أنواع المجموعات سريعًا إلى مجموعات أخرى).


 public static <E> ImmutableList<E> copyToImmutableList(E[] original); public static <E> ImmutableList<E> copyToImmutableList(Collection<? extends E> original); public static <E> ImmutableList<E> convertToImmutableList(Collection<? extends E> original); public static <E> MutableList<E> copyToMutableList(Collection<? extends E> original); public static <E> MutableList<E> convertToMutableList(List<E> original); 

copyTo* تصميم أساليب copyTo* لإنشاء مجموعات مناسبة عن طريق نسخ البيانات المقدمة. convertTo* أساليب convertTo* التحويل السريع للمجموعة المنقولة إلى النوع المطلوب ، وإذا لم يكن من الممكن التحويل بسرعة ، فإنها تؤدي عملية النسخ البطيء. إذا كان التحويل السريع ناجحًا ، فسيتم مسح المجموعة الأصلية ، ويُفترض أنه لن يتم استخدامها في المستقبل (على الرغم من إمكانية ذلك ، ولكن هذا بالكاد يكون منطقيًا).


الاستدعاءات إلى MutableList ImmutableList تطبيق MutableList / MutableList مخفية. من المفترض أن يتعامل المستخدم فقط مع الواجهات ، ولا يقوم بإنشاء مثل هذه الكائنات ، ويستخدم الطرق الموضحة أعلاه لتحويل المجموعات.


تفاصيل التنفيذ


ImmutableListImpl


بتغليف مجموعة من الكائنات. يتوافق التطبيق تقريبًا مع تطبيق ArrayList ، والذي يتم من خلاله طرح جميع طرق التعديل والتحقق من التعديل المتزامن.


يعد تطبيق أساليب قائمة toList و toList تافهاً للغاية. إرجاع الأسلوب toList مجمّع يعيد توجيه المكالمات إلى toList معطى ؛ لا يحدث النسخ البطيء للبيانات.


إرجاع الأسلوب mutable MutableListImpl إنشاؤها استناداً إلى هذا ImmutableList . لا يحدث نسخ البيانات حتى يتم استدعاء أي أسلوب تعديل على MutableList المتلقاة.


MutableListImpl


يغلف ارتباطات إلى List ImmutableList . عند إنشاء كائن ، يتم دائمًا ملء أحد هذين الارتباطين فقط ، ويبقى الآخر null .


 protected ImmutableList<E> immutable; protected List<E> list; 

الأساليب غير القابلة لإعادة التوجيه إعادة توجيه المكالمات إلى ImmutableList إذا لم تكن null ، وإلى List خلاف ذلك.


تعديل أساليب إعادة توجيه المكالمات إلى List ، بعد التهيئة:


 protected void beforeChange() { if (list == null) { list = new ArrayList<>(immutable.toList()); } immutable = null; } 

تبدو طريقة snapshot كما يلي:


 public ImmutableList<E> snapshot() { if (immutable != null) { return immutable; } immutable = InternalUtils.convertToImmutableList(list); if (immutable != null) { //    //   ,  . //     immutable     . list = null; return immutable; } immutable = InternalUtils.copyToImmutableList(list); return immutable; } 

تطبيق أساليب releaseSnapshot و releaseSnapshot تافه.


يتيح لك هذا النهج تقليل عدد نسخ البيانات أثناء الاستخدام "العادي" ، مع استبدال النسخ بتحويلات سريعة.


تحويل قائمة سريعة


التحويلات السريعة ممكنة لفئات ArrayList أو Arrays$ArrayList (نتيجة Arrays.asList() ). في الممارسة العملية ، في الغالبية العظمى من الحالات ، هذه الفئات هي بالضبط التي تأتي عبر.


داخل هذه الفئات تحتوي على مجموعة من العناصر. يكمن جوهر التحويل السريع في الحصول على مرجع إلى هذه المجموعة من خلال الانعكاسات (هذا حقل خاص) واستبداله بمرجع إلى صفيف فارغ. هذا يضمن أن المرجع الوحيد للصفيف يبقى مع كائننا ، ويظل الصفيف بدون تغيير.


في الإصدار السابق من المكتبة ، تم إجراء تحويلات سريعة لأنواع المجموعات عن طريق استدعاء المنشئ. في الوقت نفسه ، تدهور كائن المجموعة الأصلي (أصبح غير مناسب للاستخدام مرة أخرى) ، وهو ما لا تتوقعه من اللاوعي من المصمم. الآن ، يتم استخدام طريقة ثابتة خاصة للتحويل ، ولا تفسد المجموعة الأصلية ، ولكن يتم مسحها ببساطة. وبالتالي ، تم القضاء على السلوك غير العادي المخيف.


مشاكل مع يساوي / hashCode


تستخدم مجموعات Java أسلوبًا غريبًا للغاية لتطبيق أساليب equals و hashCode .


يتم إجراء المقارنة وفقًا للمحتوى الذي يبدو منطقيًا ، لكن فئة القائمة نفسها لا تؤخذ في الاعتبار. لذلك ، على سبيل المثال ، سيكون ArrayList و LinkedList مع نفس المحتوى equals .


هنا هو تطبيق يساوي / hashCode من AbstractList (الذي منه موروث ArrayList)
 public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof List)) return false; ListIterator<E> e1 = listIterator(); ListIterator e2 = ((List) o).listIterator(); while (e1.hasNext() && e2.hasNext()) { E o1 = e1.next(); Object o2 = e2.next(); if (!(o1==null ? o2==null : o1.equals(o2))) return false; } return !(e1.hasNext() || e2.hasNext()); } public int hashCode() { int hashCode = 1; for (E e : this) hashCode = 31*hashCode + (e==null ? 0 : e.hashCode()); return hashCode; } 

وبالتالي ، الآن جميع تطبيقات List مطلوبة أن يكون لها تنفيذ مماثل equals (ونتيجة لذلك ، hashCode ). خلاف ذلك ، يمكنك الحصول على مواقف عندما تكون a.equals(b) && !b.equals(a) ، وهي ليست جيدة. وضع مماثل مع Set و Map .


عند تطبيقه على المكتبة ، فإن هذا يعني أن تطبيق equals و hashCode for MutableList محدد مسبقًا ، وفي مثل هذا التطبيق ، لا يمكن أن يكون MutableList و MutableList مع نفس المحتويات equals (لأن ImmutableList ليست List ). لذلك ، تمت إضافة أساليب contentEquals لمقارنة المحتوى.


يتم تنفيذ أساليب equals و hashCode لـ ImmutableList مشابهة تمامًا للنسخة من AbstractList ، ولكن مع استبدال List من قبل ReadOnlyList .


في المجموع


يتم نشر مصادر المكتبة والاختبارات بالرجوع في شكل مشروع مخضرم.


في حالة رغبة شخص ما في استخدام المكتبة ، أنشأ مجموعة على اتصال للحصول على "تعليقات".


استخدام المكتبة واضح جدًا ، فيما يلي مثال قصير:


 private boolean myBusinessProcess() { List<Entity> tempFromDb = queryEntitiesFromDatabase("SELECT * FROM my_table"); ImmutableList<Entity> fromDb = Mutabor.convertToImmutableList(tempFromDb); if (fromDb.isEmpty() || !someChecksPassed(fromDb)) { return false; } //... MutableList<Entity> list = fromDb.mutable(); //time to change list.remove(1); ImmutableList<Entity> processed = list.snapshot(); //time to change ended //... if (!callSideLibraryExpectsListParameter(processed.toList())) { return false; } for (Entity entity : processed) { outputToUI(entity); } return true; } 

حظا سعيدا للجميع! إرسال تقارير الأخطاء!

Source: https://habr.com/ru/post/ar471344/


All Articles