«Generic array creation» — الخطأ الذي يزعج كل مطور جافا
\n\nهل سبق لك أن كتبت شيئًا مثل new T[10] وحصلت على موجة حمراء من المترجم؟ إذا كان الأمر كذلك، فأنت لست وحدك. السؤال «How can I create a generic array in Java?» على Stack Overflow حصد مئات الآلاف من المشاهدات. وهذا ليس من قبيل الصدفة: إنشاء مصفوفة عامة في جافا هي إحدى تلك المواضيع حيث تختلف النظرية عن التطبيق العملي، وبيئة التطوير المتكاملة (IDE) ترفض فهم ما تريده منها.
في هذه المقالة، سنشرح لماذا تمنع جافا المصفوفات العامة، وكيفية تجاوز هذا القيد دون فقدان أمان النوع، ومتى تكون المصفوفة أفضل من ArrayList. هيا بنا!
لماذا لا يعمل new T[10]؟ جذر الشر — محو النوع (Type Erasure)
\n\nلفهم كيفية إنشاء مصفوفة عامة في جافا، يجب أولاً إدراك سبب صراخ المترجم. الأمر يتعلق بـ محو النوع (type erasure). الأدوية (Generics) في جافا موجودة فقط في مرحلة الترجمة. أثناء وقت التشغيل، يتم محو معلومات النوع T ويتم استبدالها بـ Object (أو الحد إذا كان محددًا).
أما المصفوفات في جافا فهي أنواع قابلة لإعادة التصنيف (reifiable types). فهي تعرف نوع مكوناتها أثناء وقت التشغيل. لذلك يمكن للآلة الافتراضية (JVM) التحقق من أنك تضع في المصفوفة عناصر من النوع الصحيح فقط. إذا كان بإمكانك كتابة new T[10]، فستكون المصفوفة أثناء وقت التشغيل Object[]، ويمكنك وضع String و Integer و Date معًا — مما ينتهك فكرة أمان النوع بالكامل.
لهذا السبب يظهر المترجم الخطأ التالي:
\n// ❌ خطأ ترجمة: إنشاء مصفوفة عامة\npublic class Box<T> {\n private T[] array = new T[10]; // لا يمكن إنشاء مصفوفة عامة من النوع T\n}\n\nالطريقة 1: الأسهل — استخدم ArrayList
\n\nفي معظم الأحيان، لا تحتاج إلى مصفوفة على الإطلاق. ArrayList يعمل داخليًا مع Object[]، لكنه يوفر واجهة آمنة من حيث النوع. هذا هو الحل المثالي إذا لم تكن مهووسًا بالأداء على مستوى النانو ثانية.
import java.util.ArrayList;\nimport java.util.List;\n\npublic class GenericListExample<T> {\n private List<T> list = new ArrayList<>();\n\n public void add(T item) {\n list.add(item);\n }\n\n public T get(int index) {\n return list.get(index);\n }\n\n public int size() {\n return list.size();\n }\n}\n\nالمزايا: أمان كامل للنوع، لا تحذيرات، دعم للتوسع الديناميكي.
\nالعيوب: حمل إضافي بسيط لتغليف/تفريغ الأنواع البدائية (لكن هذا غير ملحوظ في 99% من التطبيقات).
الطريقة 2: الاختراق الكلاسيكي باستخدام (T[]) new Object[size]
\n\nإذا كنت تحتاج حقًا إلى مصفوفة (على سبيل المثال، للأداء أو التوافق مع واجهات برمجة التطبيقات القديمة)، يمكنك استخدام تحويل النوع. هذا آمن إذا ضمنت أن المصفوفة لن تغادر حدود الفئة الخاصة بك أبدًا.
\n\npublic class Stack<T> {\n private T[] elements;\n private int size = 0;\n private static final int DEFAULT_CAPACITY = 10;\n\n @SuppressWarnings("unchecked")\n public Stack() {\n elements = (T[]) new Object[DEFAULT_CAPACITY];\n }\n\n public void push(T item) {\n if (size == elements.length) {\n resize();\n }\n elements[size++] = item;\n }\n\n public T pop() {\n if (size == 0) {\n throw new EmptyStackException();\n }\n T result = elements[--size];\n elements[size] = null; // نساعد جامع القمامة (GC)\n return result;\n }\n\n private void resize() {\n elements = Arrays.copyOf(elements, elements.length * 2);\n }\n}\n\nهام: التعليق التوضيحي @SuppressWarnings("uncheck