ما هي طريقة مقابض في جافا

1. مقدمة


في هذا البرنامج التعليمي ، سننظر إلى واجهة برمجة تطبيقات مهمة تم تقديمها في Java 7 وتم تمديدها في إصدارات جديدة ، java.lang.invoke.MethodHandles .



سوف نتعلم ماهية مقابض الطريقة ، وكيفية إنشائها واستخدامها.


2. ما هي مقابض الطريقة؟


في وثائق API ، يحتوي مقبض الأسلوب على هذا التعريف:


مقبض الأسلوب هو مرجع مكتوب قابل للتنفيذ إلى الطريقة الأساسية أو المُنشئ أو الحقل أو أي عملية أخرى منخفضة المستوى مع تحويلات إضافية للوسيطات أو قيم الإرجاع.

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


لإنشاء MethodHandle واستخدامها ، يجب إجراء 4 خطوات:


  1. إنشاء واصف بحث - بحث
  2. تعلن طريقة النوع
  3. طريقة بحث مقبض
  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; // constructor } 

كشرط أولي ، لدينا رؤية مباشرة بين مقبض الأسلوب والخاصية المعلنة ، حتى نتمكن من إنشاء مقبض أسلوب بسلوك مماثل لسلوك أسلوب 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. نظرًا لأن مقابض طريقة الاتصال هي عملية ذات مستوى منخفض إلى حد ما ، فإن استخدامها يكون مبررًا فقط إذا كانت تناسب مهامك تمامًا.


كالعادة ، كل شفرة المصدر للمقالة متاحة على جيثب .

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


All Articles