الجزء الأول ، النظري |
الجزء الثاني ، عملي
استنادا إلى سقسقة من Evgeny Mandrikov الملقب godin:
في ذلك ، يتساءل عن الحد الأقصى لعدد القيم التي يمكن تحديدها في
enum
في Java. بعد سلسلة من التجارب واستخدام السحر الأسود ConstantDynamic (
JEP 309 ) ، يأتي مؤلف السؤال إلى الرقم 8191.
في سلسلة من مقالتين ، نبحث عن الحدود النظرية لعدد العناصر في التعداد ، ونحاول الاقتراب منها في الممارسة العملية ، ونكتشف على طول الطريق كيف يمكن أن يساعد JEP 309.
استطلاع
فصل مراجعة رأينا فيه التعداد مفككًا.أولاً ، دعنا نرى ما يترجم التعداد التالي إلى:
public enum FizzBuzz { Fizz, Buzz, FizzBuzz; }
بعد التجميع والتفكيك:
japap -c -s -p -v FizzBuzz.class Classfile /dev/null/FizzBuzz.class Last modified 32 . 2019 .; size 903 bytes MD5 checksum add0af79de3e9a70a7bbf7d57dd0cfe7 Compiled from "FizzBuzz.java" public final class FizzBuzz extends java.lang.Enum<FizzBuzz> minor version: 0 major version: 58 flags: (0x4031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM this_class: #2 // FizzBuzz super_class: #13 // java/lang/Enum interfaces: 0, fields: 4, methods: 4, attributes: 2 Constant pool: #1 = Fieldref #2.#3
في القائمة التقينا
- حقل
public static final
واحد لكل قيمة محددة في التعداد - الحقل الاصطناعي الخاص
$VALUES
، تفاصيل تنفيذ طريقة values()
- تطبيق
values()
و valueOf()
الأساليب - منشئ خاص
- كتلة التهيئة الثابتة ، حيث يحدث في الواقع الشيء الأكثر إثارة للاهتمام. لننظر في الأمر بمزيد من التفصيل.
في شكل java code ، يبدو هذا الأخير كالتالي:
static { Fizz = new FizzBuzz("Fizz", 0); Buzz = new FizzBuzz("Buzz", 1); FizzBuzz = new FizzBuzz("FizzBuzz", 2); $VALUES = new FizzBuzz[] { Fizz, Buzz, FizzBuzz }; }
أولاً ، يتم إنشاء مثيلات عناصر التعداد. تتم كتابة الحالات التي تم إنشاؤها على الفور إلى الحقول
public static final
المقابلة.
ثم يتم إنشاء صفيف وتعبئته بروابط إلى مثيلات جميع عناصر التعداد. الروابط مأخوذة من حقول الفئة التي قمنا بتهيئتها في الفقرة أعلاه. يتم تخزين الصفيف المعبأ في الحقل
private static final
$VALUES
.
بعد ذلك ، القائمة جاهزة للعمل.
عنق الزجاجة
فصل ممل حيث نبحث عن قيود على عدد عناصر التعداد.يمكنك بدء البحث باستخدام الفصل
JLS §8.9.3 "تعداد الأعضاء":
JLS 8.9.3 عدد الأعضاءأعضاء التعداد من النوع E هم كل مما يلي:
...
* لكل إحصاء ثابت c معلن في نص إعلان E ، E لديه
حقل نهائي ثابت ثابت عام من النوع E له نفس الشيء
اسم ج. يحتوي الحقل على مُهيئ متغير يقوم بإنشاء مثيل E ويمرر أي
وسيطات c إلى المُنشئ الذي تم اختياره لـ E. يحتوي الحقل على نفس التعليقات التوضيحية
كما ج (إن وجدت).
يتم الإعلان عن هذه الحقول ضمنيًا بنفس الترتيب الخاص بها
التعداد الثوابت ، قبل أي حقول ثابتة أعلن صراحة في جسم
إعلان E.
...
* الأساليب المعلنة ضمنيًا التالية:
public static E[] values(); public static E valueOf(String name);
لذلك ، كل فئة من فئات التعداد لها طريقة
values()
تُرجع صفيفًا مع جميع العناصر المعلنة في هذا التعداد. ويترتب على ذلك أن التعداد الكروي في الفراغ لا يمكن أن يحتوي على أكثر من عناصر
Integer.MAX_VALUE + 1
.
المضي قدما. يتم تمثيل التعدادات في Java
java.lang.Enum
لفئة
java.lang.Enum
، وبالتالي فهي تخضع لجميع القيود الملازمة للفئات في JVM.
دعونا نلقي نظرة على الوصف رفيع المستوى لهيكل ملف الفصل الوارد في
JVMS §4.1 "هيكل ClassFile":
ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; }
كما نعلم بالفعل من JLS §8.9.3 ، يتم إنشاء حقل يحمل نفس الاسم لكل عنصر تعداد في الفئة الناتجة. يُعرّف عدد الحقول في الفصل
fields_count
غير الموقّع
fields_count
من 16
fields_count
، والذي يحدنا إلى 65_535 حقلًا في ملف فئة واحد أو 65_534 عنصر تعداد. يتم حجز حقل واحد لصفيف
$VALUES
، حيث يؤدي استنساخه إلى إرجاع طريقة
values()
. لم يتم ذكر ذلك بشكل صريح في المواصفات ، ولكن من غير المحتمل التوصل إلى حل أكثر أناقة.
يتم تخزين أسماء الحقول والأساليب والفئات والقيم الثابتة وغير ذلك الكثير في التجمع الثابت.
إذا كنت لا تعرف أي شيء عن البنية الداخلية للمجمع الثابت ، فإنني أوصي بقراءة المقالة القديمة من lany . على الرغم من حقيقة أنه منذ كتابتها في مجموعة الثوابت ظهرت الكثير من الأشياء الجديدة والمثيرة للاهتمام ، تظل المبادئ الأساسية على حالها.
يقتصر حجم تجمع ثوابت الفئة أيضًا على عدد العناصر 65_535. مجموعة الثوابت للفئة المشكلة بشكل صحيح ليست فارغة أبدًا. كحد أدنى ، سيكون هناك اسم لهذه الفئة.
على سبيل المثال ، تحتوي مجموعة ثوابت فئة فئة التعداد الفارغ التي تم تجميعها بواسطة javac من OpenJDK 14-ea + 29 بدون معلومات تصحيح الأخطاء على 29 إدخالًا.
ويترتب على ذلك أن عدد العناصر 65_534 في تعداد واحد غير ممكن أيضًا. في أفضل الأحوال ، يمكننا الاعتماد على 65_505 أو رقم قريب من ذلك.
الوتر الأخير في هذه المقدمة المطولة:
يمكن
<clinit>
القيمة إلى الحقل
static final
فقط في كتلة التهيئة الثابتة ، والتي يتم تمثيلها على مستوى ملف الفئة بطريقة تسمى
<clinit>
. لا يمكن أن تشغل bytecode لأي طريقة أكثر من 65_535 بايت. عدد مألوف ، أليس كذلك؟
putstatic
إحدى تعليمات الكتابة الثابتة الثابتة 3 بايتات ، مما يعطينا تقدير تقريبي قدره
65_535 / 3 = 21_845
. في الواقع ، هذا التقدير مبالغ فيه. تأخذ التعليمة القيمة للكتابة إلى الحقل من أعلى المكدس ، وهو أحد الإرشادات السابقة الموضوعة هناك. ويأخذ هذا الإرشادات أيضًا البايتات الثمينة. ولكن حتى إذا لم تأخذ ذلك في الاعتبار ، فإن الرقم الناتج لا يزال أقل بكثير من 65_505.
باختصار:
- يحد تنسيق ملف الفئة الحد الأقصى لعدد عناصر التعداد إلى 65_505 تقريبًا
- آلية تهيئة الحقل النهائي الثابتة تحدنا أكثر. من الناحية النظرية - ما يصل إلى 21_845 عنصرًا كحد أقصى ، في الممارسة العملية يكون هذا الرقم أقل
في المقالة الأخيرة من السلسلة ، سنركز على التحسين غير الصحي وإنشاء ملفات فئة.