عباءة حول ImmutableList في جاوة

قرأت المقال "لن تكون هناك مجموعات ثابتة في Java - لا الآن ولا أبدًا" واعتقدت أن مشكلة عدم وجود قوائم ثابتة في Java ، والتي تجعل المؤلف حزينًا ، يمكن حلها على نطاق محدود. أقدم أفكاري وأجزاء من التعليمات البرمجية حول هذا الموضوع.


(هذا مقال إجابة ، اقرأ المقال الأصلي أولاً.)


UnmodifiableList مقابل ImmutableList


السؤال الأول الذي يطرح نفسه هو: لماذا أحتاج إلى قائمة UnmodifiableList قابلة UnmodifiableList ، إذا كان هناك قائمة غير قابلة ImmutableList ؟ نتيجة للمناقشة ، يتم عرض فكرتين بخصوص معنى UnmodifiableList في تعليقات المقالة الأصلية:


  • تتلقى الطريقة قائمة UnmodifiableList ، ولا يمكنها تغييرها بنفسها ، ولكنها تعلم أنه يمكن تغيير المحتويات بواسطة سلسلة رسائل أخرى (وتعرف كيفية معالجتها بشكل صحيح)
  • لا تؤثر مؤشرات الترابط الأخرى على أن UnmodifiableList و ImmutableList لطريقة ما ، لكن UnmodifiableList يستخدم كطريقة "خفيفة الوزن" أكثر.

الخيار الأول يبدو نادرًا جدًا في الممارسة. وبالتالي ، إذا ImmutableList تنفيذ تطبيق "سهل" لـ ImmutableList ، فإن UnmodifiableList يصبح غير ضروري للغاية. لذلك ، في المستقبل سوف ننسى ذلك وسوف ننفذ فقط قائمة ImmutableList للتطبيق.


بيان المشكلة


سنقوم بتطبيق خيار ImmutableList :


  • يجب أن تكون واجهة برمجة التطبيقات متطابقة مع واجهة برمجة تطبيقات List المعتادة في جزء "القراءة". يجب أن يكون جزء "الكتابة" غائبًا.
  • لا يجب أن تكون List ImmutableList الموروث مرتبطة بقائمة الميراث. لماذا هكذا - يفهم المقال الأصلي.
  • فمن المنطقي أن تفعل التنفيذ عن طريق القياس مع ArrayList . هذا هو الخيار الأسهل.
  • يجب أن يتجنب التطبيق نسخ المصفوفات كلما أمكن ذلك.

تطبيق ImmutableList


أولاً ، نتعامل مع API. نقوم بفحص واجهات Collection والقوائم ونسخ جزء "القراءة" منها إلى واجهاتنا الجديدة.


 public interface ReadOnlyCollection<E> extends Iterable<E> { int size(); boolean isEmpty(); boolean contains(Object o); Object[] toArray(); <T> T[] toArray(T[] a); boolean containsAll(Collection<?> c); } public interface ReadOnlyList<E> extends ReadOnlyCollection<E> { E get(int index); int indexOf(Object o); int lastIndexOf(Object o); ListIterator<E> listIterator(); ListIterator<E> listIterator(int index); ReadOnlyList<E> subList(int fromIndex, int toIndex); } 

بعد ذلك ، قم بإنشاء فئة ImmutableList . يشبه التوقيع ArrayList (لكن ينفذ واجهة ReadOnlyList بدلاً من List ).


 public class ImmutableList<E> implements ReadOnlyList<E>, RandomAccess, Cloneable, Serializable 

نقوم بنسخ تنفيذ الفصل من ArrayList و refactor بحزم ، ونطرد كل ما يتعلق بجزء "الكتابة" ، ونتحقق من التعديل المتزامن ، إلخ.


سوف يكون البنائين على النحو التالي:


 public ImmutableList() public ImmutableList(E[] original) public ImmutableList(Collection<? extends E> original) 

الأول يخلق قائمة فارغة. الثاني بإنشاء قائمة عن طريق نسخ الصفيف. لا يمكننا الاستغناء عن النسخ إذا كنا نريد تحقيق التغيير. والثالث هو أكثر إثارة للاهتمام. يقوم مُنشئ ArrayList مماثل أيضًا بنسخ البيانات من المجموعة. سنفعل نفس الشيء ، ما لم يكن orginal هو مثيل ArrayList أو Arrays$ArrayList (هذا ما يتم إرجاعه بواسطة طريقة Arrays.asList() ). يمكننا أن نفترض بأمان أن هذه الحالات سوف تغطي 90 ٪ من مكالمات المنشئ.


في هذه الحالات ، سنقوم "بسرقة" الصفيف original خلال الانعكاسات (هناك أمل في أن يكون هذا أسرع من نسخ صفائف الجيجابايت). جوهر "السرقة":


  • نصل إلى الحقل الخاص original ، الذي يخزن الصفيف ( ArrayList.elementData )
  • انسخ الرابط إلى المجموعة إلى أنفسنا
  • وضعت في حقل مصدر فارغ

 protected static final Field data_ArrayList; static { try { data_ArrayList = ArrayList.class.getDeclaredField("elementData"); data_ArrayList.setAccessible(true); } catch (NoSuchFieldException | SecurityException e) { throw new IllegalStateException(e); } } public ImmutableList(Collection<? extends E> original) { Object[] arr = null; if (original instanceof ArrayList) { try { arr = (Object[]) data_ArrayList.get(original); data_ArrayList.set(original, null); } catch (@SuppressWarnings("unused") IllegalArgumentException | IllegalAccessException e) { arr = null; } } if (arr == null) { //   ArrayList,      -  arr = original.toArray(); } this.data = arr; } 

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


فصول أخرى


لنفترض أننا قررنا استخدام قائمة ImmutableList قابلة ImmutableList في مشروع حقيقي.


يتفاعل المشروع مع المكتبات: يستقبل منهم ويرسل لهم قوائم مختلفة. في الغالبية العظمى من الحالات ، ستكون هذه القوائم قائمة ArrayList . يسمح ImmutableList التطبيق الموصوف لـ ImmutableList بتحويل ArrayList الناتج بسرعة إلى ImmutableList . مطلوب أيضًا تنفيذ التحويل للقوائم المرسلة إلى المكتبات: ImmutableList to List . من أجل التحويل السريع ، فأنت بحاجة إلى برنامج ImmutableList للتطبيق يقوم بتطبيق List ، وإلقاء استثناءات عند محاولة الكتابة إلى القائمة (على غرار Collections.unmodifiableList ).


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


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


عادة ما يكون التحويل بطيئًا ، مع نسخ الصفيف. لكن بالنسبة للحالات التي لا MutableList المتلقاة دائمًا ، يمكنك إنشاء مجمّع: MutableList ، الذي يحفظ مرجعًا إلى ImmutableList ويستخدمه في أساليب "القراءة" ، وإذا تم استدعاء طريقة "الكتابة" ، عندئذٍ ينسى فقط عن ImmutableList ، بعد نسخ محتوياته الصفيف لنفسه ، ثم يعمل بالفعل مع CopyOnWriteArrayList (شيء مشابه عن بعد موجود في CopyOnWriteArrayList ).


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


في المجموع


يتم نشر نتائج التجربة في شكل الشفرة هنا


يتم ImmutableList نفسه ، الموضح في قسم "الفئات الأخرى" (ليس بعد؟).


يمكننا أن نفترض أن فرضية المقال الأصلي ، "المجموعات الثابتة في Java لن تكون" خاطئة.


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


شيء واحد: إذا كانت هناك رغبة ... (تاهيتي ، تاهيتي ... لم نكن في أي تاهيتي! إنهم يطعموننا جيدًا هنا.)

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


All Articles