1. مقدمة
في هذا البرنامج التعليمي ، سننظر إلى واجهة برمجة تطبيقات مهمة تم تقديمها في Java 7 وتم تمديدها في إصدارات جديدة ، java.lang.invoke.MethodHandles .
سوف نتعلم ماهية مقابض الطريقة ، وكيفية إنشائها واستخدامها.
2. ما هي مقابض الطريقة؟
في وثائق API ، يحتوي مقبض الأسلوب على هذا التعريف:
مقبض الأسلوب هو مرجع مكتوب قابل للتنفيذ إلى الطريقة الأساسية أو المُنشئ أو الحقل أو أي عملية أخرى منخفضة المستوى مع تحويلات إضافية للوسيطات أو قيم الإرجاع.
بمعنى آخر ، تعد مقابض الطريقة آلية منخفضة المستوى للعثور على أساليب الاتصال وتكييفها. مقابض الطريقة غير قابلة للتغيير وليس لها حالة عرض.
لإنشاء MethodHandle
واستخدامها ، يجب إجراء 4 خطوات:
- إنشاء واصف بحث - بحث
- تعلن طريقة النوع
- طريقة بحث مقبض
- استدعاء طريقة مقبض
2.1. طريقة مقابض مقابل التفكير
تم تقديم مقابض الطريقة لتعمل جنبًا إلى جنب مع java.lang.reflect API ، as يتم إنشاؤها لأغراض مختلفة وتختلف في خصائصها.
من حيث الأداء ، يمكن أن يكون MethodHandles API أسرع بكثير من Reflection API ، لأنه يتم إجراء اختبارات الوصول في وقت الإنشاء بدلاً من التنفيذ . إذا كان هناك مدير أمان ، فإن هذا الاختلاف يزداد بسبب البحث عن الفصول والحصول على عناصرها يخضع لفحوصات إضافية.
ومع ذلك ، فإن الأداء ليس هو المؤشر الوحيد على أمثلية المهمة ، يجب مراعاة أن MethodHandles API أكثر صعوبة في الاستخدام بسبب نقص الآليات مثل الحصول على أساليب الفصل ، والتحقق من رموز الوصول ، وما إلى ذلك.
على الرغم من ذلك ، فإن MethodHandles API تتيح طرق الكاري ، وتغيير نوع وترتيب المعلمات.
الآن ، ومعرفة تعريف والغرض من API MethodHandles ، يمكننا العمل معهم. لنبدأ بالبحث عن الأساليب.
3. إنشاء بحث
أول ما يجب فعله عندما نريد إنشاء مقبض أسلوب هو الحصول على بحث ، كائن المصنع المسؤول عن إنشاء مقابض الطريقة للأساليب والمنشئين والحقول المرئية لفئة البحث.
باستخدام MethodHandles API ، يمكنك إنشاء كائن بحث مع أوضاع وصول مختلفة.
إنشاء بحث يوفر الوصول إلى الطرق العامة:
MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();
ومع ذلك ، إذا كنا بحاجة إلى الوصول إلى الطرق الخاصة والمحمية ، فيمكننا استخدام طريقة lookup () بدلاً من ذلك:
MethodHandles.Lookup lookup = MethodHandles.lookup();
4. إنشاء MethodType
لإنشاء MethodHandle ، يجب تعيين كائن البحث على نوع ، ويمكن القيام بذلك باستخدام فئة MethodType.
على وجه الخصوص ، يمثل MethodType الوسائط ونوع الإرجاع المقبول والمرجع بواسطة مقبض الطريقة ، أو يتم تمريره والمتوقع بواسطة رمز الاتصال.
بنية MethodType بسيطة ، يتم تكوينها من خلال نوع الإرجاع ، جنبًا إلى جنب مع عدد أنواع المعلمات المقابل ، الذي يجب أن يرتبط بالكامل بين مقبض الأسلوب ورمز الاتصال.
مثل MethodHandle ، كل مثيلات MethodType غير قابلة للتغيير.
دعونا نرى كيفية تعريف MethodType الذي يعرف فئة java.util.List
بنوع الإرجاع وصفيف الكائنات كنوع إدخال البيانات:
MethodType mt = MethodType.methodType(List.class, Object[].class);
في حالة إرجاع الأسلوب لنوع بسيط أو (void.class, int.class …)
من القيمة ، فإننا نستخدم فئة تمثل هذه الأنواع (void.class, int.class …)
.
تحديد MethodType يقوم بإرجاع int وقبول كائن:
MethodType mt = MethodType.methodType(int.class, Object.class);
يمكنك البدء في إنشاء MethodHandle.
5. طريقة البحثالمعالجة
بعد أن نقوم بتعيين نوع الأسلوب ، لإنشاء MethodHandle ، تحتاج إلى العثور عليه باستخدام كائن البحث أو publicLookup ، والذي يُرجع أيضًا الفئة المصدر واسم الطريقة.
يوفر Lookup مجموعة من الأساليب التي تسمح لك بالعثور على مقبض الطريقة بالطريقة المثلى ، مع مراعاة نطاق الطريقة. النظر في النهج الأساسية ، بدءا من أبسط.
5.1. طريقة التعامل مع الأساليب
باستخدام طريقة findVirtual()
، يمكنك إنشاء MethodHandle لطريقة المثيل. قم concat()
استنادًا إلى طريقة concat()
لفئة String
:
MethodType mt = MethodType.methodType(String.class, String.class); MethodHandle concatMH = publicLookup.findVirtual(String.class, "concat", mt);
5.2. طريقة التعامل مع الطرق الثابتة
للوصول إلى الأسلوب الثابت ، يمكنك استخدام طريقة findStatic()
:
MethodType mt = MethodType.methodType(List.class, Object[].class); MethodHandle asListMH = publicLookup.findStatic(Arrays.class, "asList", mt);
في هذه الحالة ، أنشأنا مؤشرًا لطريقة تقوم بتحويل صفيف نوع Object
إلى List
.
5.3. طريقة التعامل مع البنائين
يمكنك الوصول إلى المنشئ باستخدام طريقة findConstructor()
.
قم بإنشاء مقبض أسلوب بسلوك مشابه لمنشئ الفئة Integer باستخدام المعلمة String:
MethodType mt = MethodType.methodType(void.class, String.class); MethodHandle newIntegerMH = publicLookup.findConstructor(Integer.class, mt);
5.4. طريقة التعامل مع الحقول
باستخدام مقبض الأسلوب ، يمكنك أيضًا الوصول إلى الحقول.
لنبدأ بتحديد فئة الكتاب:
public class Book { String id; String title;
كشرط أولي ، لدينا رؤية مباشرة بين مقبض الأسلوب والخاصية المعلنة ، حتى نتمكن من إنشاء مقبض أسلوب بسلوك مماثل لسلوك أسلوب get:
MethodHandle getTitleMH = lookup.findGetter(Book.class, "title", String.class);
لمزيد من المعلومات حول إدارة المتغيرات / الحقول ، راجع مقال Java 9 Variable Handles Demystified ، حيث نتحدث عن java.lang.invoke.VarHandle API المقدمة في Java 9.
5.5. طريقة التعامل مع الطرق الخاصة
يمكنك إنشاء مؤشر أسلوب لطريقة الكتابة الخاصة باستخدام java.lang.reflect API .
لنبدأ بإنشاء طريقة خاصة لفئة الكتاب:
private String formatBook() { return id + " > " + title; }
الآن يمكننا إنشاء مقبض أسلوب بسلوك الأسلوب formatBook()
:
Method formatBookMethod = Book.class.getDeclaredMethod("formatBook"); formatBookMethod.setAccessible(true); MethodHandle formatBookMH = lookup.unreflect(formatBookMethod);
6. استدعاء طريقة مقبض
بمجرد قيامنا بإنشاء مقبض الأسلوب الخاص بنا ، انتقل إلى الخطوة التالية. تعطينا الفئة MethodHandle 3 طرق مختلفة لاستدعاء مقبض الأسلوب: invoke()
و invokeWithArugments()
و invokeExact()
.
لنبدأ بطريقة invoke
.
6.1. استدعاء طريقة مقبض
عند استخدام طريقة invoke()
، يتم إصلاح عدد الوسائط (arity) ، لكن من الممكن إجراء casting type و packing / unpacking للوسائط وأنواع قيمة الإرجاع.
الآن ، لنرى كيف يمكن استخدام invoke()
مع وسيطة محزومة:
MethodType mt = MethodType.methodType(String.class, char.class, char.class); MethodHandle replaceMH = publicLookup.findVirtual(String.class, "replace", mt); String output = (String) replaceMH.invoke("jovo", Character.valueOf('o'), 'a'); assertEquals("java", output);
في هذه الحالة ، تتطلب replaceMH
وسيطات char
، لكن طريقة invoke()
replaceMH
ضغط وسيطة Character قبل تنفيذها.
6.2. دعوة مع الحجج
استدعاء أسلوب استدعاء باستخدام invokeWithArguments
لديه أقل تقييد.
في الواقع ، بالإضافة إلى التحقق من الأنواع ووسائط التعبئة / التفريغ وقيم الإرجاع ، يسمح لك بإجراء مكالمات باستخدام عدد متغير من المعلمات.
في الممارسة العملية ، يمكننا إنشاء قائمة عدد صحيح مع مجموعة من القيم int ذات الطول غير المعروف:
MethodType mt = MethodType.methodType(List.class, Object[].class); MethodHandle asList = publicLookup.findStatic(Arrays.class, "asList", mt); List<Integer> list = (List<Integer>) asList.invokeWithArguments(1, 2); assertThat(Arrays.asList(1,2), is(list));
6.3. اتصل بالضبط
إذا احتجنا إلى تنفيذ مقبض الأسلوب بشكل أكثر تقييدًا (من خلال مجموعة الوسائط ونوعها) ، فإننا نستخدم طريقة invokeExact()
.
في الواقع ، لا يوفر القدرة على إلقاء أنواع من الفصل ويتطلب مجموعة ثابتة من الوسائط.
دعونا نرى كيف يمكننا إضافة قيمتين int باستخدام مقبض الأسلوب:
MethodType mt = MethodType.methodType(int.class, int.class, int.class); MethodHandle sumMH = lookup.findStatic(Integer.class, "sum", mt); int sum = (int) sumMH.invokeExact(1, 11); assertEquals(12, sum);
في هذه الحالة ، إذا مررنا رقمًا غير invokeExact
طريقة invokeExact
، فعندما نتصل ، نحصل على WrongMethodTypeException
.
7. العمل مع المصفوفات
يمكن أن تعمل MethodHandles ليس فقط مع الحقول والكائنات ، ولكن أيضًا مع الصفائف. باستخدام API asSpreader()
، يمكنك إنشاء مقبض أسلوب يدعم المصفوفات asSpreader()
.
في هذه الحالة ، يأخذ مقبض الأسلوب صفيفًا ، ويوزع عناصره كوسيطات موضعية ، واختيارياً ، طول الصفيف.
دعونا نرى كيفية الحصول على مؤشر الأسلوب للتحقق مما إذا كانت وسيطات الصفيف هي نفس السلسلة:
MethodType mt = MethodType.methodType(boolean.class, Object.class); MethodHandle equals = publicLookup.findVirtual(String.class, "equals", mt); MethodHandle methodHandle = equals.asSpreader(Object[].class, 2); assertTrue((boolean) methodHandle.invoke(new Object[] { "java", "java" }));
8. توضيح طريقة التعامل
بمجرد تعيين مؤشر الأسلوب ، يمكنك تحسينه بالربط بالوسيطة ، دون استدعاء الطريقة.
على سبيل المثال ، في Java 9 ، يتم استخدام هذه الخدعة لتحسين تسلسل السلسلة.
دعونا نرى كيف يمكن أن يتم تسلسل عن طريق إرفاق لاحقة ل concatMH
:
MethodType mt = MethodType.methodType(String.class, String.class); MethodHandle concatMH = publicLookup.findVirtual(String.class, "concat", mt); MethodHandle bindedConcatMH = concatMH.bindTo("Hello "); assertEquals("Hello World!", bindedConcatMH.invoke("World!"));
9. تحديثات جافا 9
أدخل Java 9 العديد من التغييرات على MethodHandles API لتسهيل استخدامه.
تحديثات تتعلق 3 جوانب رئيسية:
- وظائف البحث - تسمح بالبحث من سياقات مختلفة ودعم الأساليب غير المجردة في الواجهات.
- العمليات باستخدام الوسائط - تحسين وظيفة الطي وجمع وتوزيع الوسائط.
- تضيف مجموعات إضافية عمليات loop (
loop
، whileLoop
، doWhileLoop
، ...) وتحسين إدارة الاستثناءات باستخدام tryFinally
.
تنطوي هذه التغييرات على ابتكارات مفيدة أخرى:
- تحسين JVM مترجم الأمثل
- انخفاض مثيل
- صب الخرسانة من استخدام API MethodHandles
تتوفر قائمة أكثر تفصيلاً بالتغييرات في واجهة برمجة تطبيقات Javadoc MethodHandles .
10. الخاتمة
في هذه المقالة ، التقينا بواجهة برمجة تطبيقات MethodHandles ، وتعلمنا أيضًا ماهية مقابض الطريقة وكيفية استخدامها.
لقد وصفنا أيضًا كيفية ارتباطه بواجهة برمجة تطبيقات Reflection. نظرًا لأن مقابض طريقة الاتصال هي عملية ذات مستوى منخفض إلى حد ما ، فإن استخدامها يكون مبررًا فقط إذا كانت تناسب مهامك تمامًا.
كالعادة ، كل شفرة المصدر للمقالة متاحة على جيثب .