JavaRush /وبلاگ جاوا /Random-FA /Reflection API. انعکاس. سمت تاریک جاوا
Oleksandr Klymenko
مرحله
Харків

Reflection API. انعکاس. سمت تاریک جاوا

در گروه منتشر شد
درود، پادوان جوان. در این مقاله من در مورد نیرویی که برنامه نویسان جاوا از قدرت آن فقط در شرایط به ظاهر ناامیدکننده استفاده می کنند به شما خواهم گفت. بنابراین، سمت تاریک جاوا این است -Reflection API
Reflection API.  انعکاس.  سمت تاریک جاوا - 1
Reflection در جاوا با استفاده از Java Reflection API انجام می شود. این بازتاب چیست؟ تعریف کوتاه و دقیقی وجود دارد که در اینترنت نیز رایج است. بازتاب (از لاتین reflexio - بازگشت به عقب) مکانیزمی است برای مطالعه داده های یک برنامه در طول اجرای آن. Reflection به شما امکان می دهد اطلاعات مربوط به فیلدها، متدها و سازندگان کلاس را بررسی کنید. مکانیزم بازتاب به خودی خود به شما امکان می دهد انواعی را که در طول کامپایل وجود نداشتند، اما در طول اجرای برنامه ظاهر شدند را پردازش کنید. انعکاس و وجود یک مدل منطقی منسجم برای گزارش خطاها، ایجاد کد پویا صحیح را ممکن می سازد. به عبارت دیگر، درک نحوه عملکرد بازتاب در جاوا، تعدادی فرصت شگفت انگیز را برای شما باز می کند. شما به معنای واقعی کلمه می‌توانید کلاس‌ها و اجزای آنها را مدیریت کنید.
Reflection API.  انعکاس.  سمت تاریک جاوا - 2
در اینجا یک لیست اساسی از آنچه بازتاب اجازه می دهد وجود دارد:
  • پیدا کردن/تعیین کلاس یک شی.
  • اطلاعاتی در مورد اصلاح‌کننده‌های کلاس، فیلدها، روش‌ها، ثابت‌ها، سازنده‌ها و سوپرکلاس‌ها دریافت کنید.
  • دریابید که کدام متدها متعلق به رابط/رابط پیاده سازی شده هستند.
  • یک نمونه از یک کلاس ایجاد کنید و نام کلاس تا زمانی که برنامه اجرا نشود ناشناخته است.
  • مقدار یک فیلد شی را با نام دریافت و تنظیم کنید.
  • متد یک شی را با نام فراخوانی کنید.
Reflection تقریباً در تمام فناوری های مدرن جاوا استفاده می شود. تصور اینکه آیا جاوا به عنوان یک پلتفرم می توانست بدون تأمل به چنین پذیرش عظیمی دست یابد، دشوار است. به احتمال زیاد نتونستم شما با ایده کلی نظری بازتاب آشنا شده اید، اکنون به کاربرد عملی آن می پردازیم! ما تمام روش‌های Reflection API را مطالعه نمی‌کنیم، فقط آنچه را که در عمل با آن مواجه می‌شویم، مطالعه نمی‌کنیم. از آنجایی که مکانیسم بازتاب شامل کار با کلاس ها می شود، یک کلاس ساده خواهیم داشت - MyClass:
public class MyClass {
   private int number;
   private String name = "default";
//    public MyClass(int number, String name) {
//        this.number = number;
//        this.name = name;
//    }
   public int getNumber() {
       return number;
   }
   public void setNumber(int number) {
       this.number = number;
   }
   public void setName(String name) {
       this.name = name;
   }
   private void printData(){
       System.out.println(number + name);
   }
}
همانطور که می بینیم، این کلاس رایج ترین است. سازنده با پارامترها به دلیلی نظر داده می شود، ما بعداً به آن باز خواهیم گشت. اگر به محتویات کلاس دقت کرده باشید، احتمالاً عدم وجود getter"a" را مشاهده کرده اید name. خود فیلد nameبا یک اصلاح کننده دسترسی علامت گذاری شده است private؛ ما نمی توانیم خارج از خود کلاس به آن دسترسی داشته باشیم؛ =>نمی توانیم مقدار آن را دریافت کنیم. "پس مشکل چیست؟ - تو بگو. "افزودن getterیا تغییر اصلاح کننده دسترسی." و حق با شماست، اما اگر MyClassدر یک کتابخانه aar کامپایل شده یا در یک ماژول بسته دیگر بدون دسترسی ویرایش باشد، چه می شود، و در عمل این اتفاق بسیار زیاد می افتد. و برخی از برنامه نویسان بی توجه به سادگی فراموش کردند که بنویسند getter. وقت آن است که در مورد بازتاب به یاد بیاوریم! بیایید سعی کنیم به privateفیلد nameکلاس برسیم MyClass:
public static void main(String[] args) {
   MyClass myClass = new MyClass();
   int number = myClass.getNumber();
   String name = null; //no getter =(
   System.out.println(number + name);//output 0null
   try {
       Field field = myClass.getClass().getDeclaredField("name");
       field.setAccessible(true);
       name = (String) field.get(myClass);
   } catch (NoSuchFieldException | IllegalAccessException e) {
       e.printStackTrace();
   }
   System.out.println(number + name);//output 0default
}
بیایید بفهمیم اینجا چه اتفاقی افتاده است. یک کلاس فوق العاده در جاوا وجود دارد Class. این کلاس ها و رابط ها را در یک برنامه اجرایی جاوا نشان می دهد. ما به ارتباط بین Classو دست نخواهیم داد ClassLoader. این موضوع مقاله نیست. در مرحله بعد، برای دریافت فیلدهای این کلاس، باید متد را فراخوانی کنید getFields()، این متد تمام فیلدهای موجود کلاس را به ما باز می گرداند. این برای ما مناسب نیست، زیرا فیلد ما است private، بنابراین از متد استفاده می کنیم getDeclaredFields()، این متد همچنین آرایه ای از فیلدهای کلاس را برمی گرداند، اما اکنون هر دو privateو protected. در شرایطی که داریم، نام رشته مورد علاقه خود را می دانیم و می توانیم از روشی استفاده کنیم که getDeclaredField(String)نام Stringفیلد مورد نظر کجاست. توجه داشته باشید: getFields()و getDeclaredFields()فیلدهای کلاس والد را برنگردانید! عالی است، ما یک شی Field با پیوندی به ما دریافت کردیم name. زیرا فیلد (عمومی) نبود публичным، باید برای کار با آن دسترسی داده شود. این روش setAccessible(true)به ما اجازه می دهد به کار ادامه دهیم. حالا میدان nameکاملا تحت کنترل ماست! می توانید مقدار آن را با فراخوانی get(Object)شی Field, جایی که Objectنمونه ای از کلاس ما است , بدست آورید MyClass. ما آن را ریخته Stringو به متغیر خود اختصاص می دهیم name. اگر ناگهان "a" نداریم setter، می‌توانیم از روش برای تنظیم مقدار جدیدی برای فیلد نام استفاده کنیم set:
field.set(myClass, (String) "new value");
تبریک می گویم! شما به تازگی بر مکانیسم اصلی بازتاب تسلط پیدا کرده اید و می توانید به privateمیدان دسترسی پیدا کنید! به بلوک try/catchو انواع استثناها توجه کنید. خود IDE حضور اجباری آنها را نشان می دهد، اما نام آنها روشن می کند که چرا آنها اینجا هستند. برو جلو! همانطور که ممکن است متوجه شده باشید، روش ما MyClassقبلاً روشی برای نمایش اطلاعات مربوط به داده های کلاس دارد:
private void printData(){
       System.out.println(number + name);
   }
اما این برنامه نویس در اینجا نیز میراثی به جا گذاشت. این روش تحت تعدیل کننده دسترسی است privateو ما مجبور بودیم هر بار کد خروجی را خودمان بنویسیم. درست نیست، بازتاب ما کجاست؟... بیایید تابع زیر را بنویسیم:
public static void printData(Object myClass){
   try {
       Method method = myClass.getClass().getDeclaredMethod("printData");
       method.setAccessible(true);
       method.invoke(myClass);
   } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
       e.printStackTrace();
   }
}
در اینجا روش تقریباً مشابه با گرفتن یک فیلد است - روش مورد نظر را با نام دریافت می کنیم و به آن دسترسی می دهیم. و برای فراخوانی شیئی که Methodاز آن استفاده می کنیم invoke(Оbject, Args)، که در آن Оbjectنیز نمونه ای از کلاس است MyClass. Args- آرگومان های روش - آرگومان های ما هیچ کدام ندارند. اکنون از تابع برای نمایش اطلاعات استفاده می کنیم printData:
public static void main(String[] args) {
   MyClass myClass = new MyClass();
   int number = myClass.getNumber();
   String name = null; //?
   printData(myClass); // outout 0default
   try {
       Field field = myClass.getClass().getDeclaredField("name");
       field.setAccessible(true);
       field.set(myClass, (String) "new value");
       name = (String) field.get(myClass);
   } catch (NoSuchFieldException | IllegalAccessException e) {
       e.printStackTrace();
   }
   printData(myClass);// output 0new value
}
هورای، حالا ما به متد خصوصی کلاس دسترسی داریم. اما اگر روش هنوز آرگومان‌هایی داشته باشد، چه می‌شود، و چرا سازنده‌ای با نظر وجود دارد؟ هر چیزی زمان خودش را دارد. از تعریف در ابتدا مشخص است که انعکاس به شما امکان می دهد نمونه هایی از یک کلاس را در یک حالت runtime(در حالی که برنامه در حال اجرا است) ایجاد کنید! ما می توانیم یک شی از یک کلاس را با نام کاملا واجد شرایط آن کلاس ایجاد کنیم. نام کلاس کاملاً واجد شرایط نام کلاس است که مسیر آن را در package.
Reflection API.  انعکاس.  سمت تاریک جاوا - 3
در سلسله مراتب من، packageنام کامل MyClass" " خواهد بود reflection.MyClass. همچنین می توانید نام کلاس را به روشی ساده پیدا کنید (نام کلاس را به صورت رشته ای برمی گرداند):
MyClass.class.getName()
بیایید یک نمونه از کلاس با استفاده از بازتاب ایجاد کنیم:
public static void main(String[] args) {
   MyClass myClass = null;
   try {
       Class clazz = Class.forName(MyClass.class.getName());
       myClass = (MyClass) clazz.newInstance();
   } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
       e.printStackTrace();
   }
   System.out.println(myClass);//output created object reflection.MyClass@60e53b93
}
در زمان شروع برنامه جاوا، همه کلاس ها در JVM بارگذاری نمی شوند. اگر کد شما به کلاس اشاره نمی کند MyClass، پس شخصی که مسئول بارگذاری کلاس ها در JVM است، ClassLoaderهرگز آن را در آنجا بارگذاری نمی کند. بنابراین، باید ClassLoaderآن را مجبور به بارگیری و دریافت توضیحی از کلاس خود در قالب متغیری از نوع کنیم Class. برای این کار، یک متد وجود دارد forName(String)که در آن Stringنام کلاسی است که توضیح آن را می‌خواهیم. پس از دریافت Сlass، فراخوانی متد newInstance()برمی گردد Objectکه مطابق همان توضیحات ایجاد می شود. باقی مانده است که این شی را به کلاس خود بیاوریم MyClass. سرد! سخت بود اما امیدوارم قابل درک باشد. اکنون می توانیم نمونه ای از یک کلاس را به معنای واقعی کلمه از یک خط ایجاد کنیم! متأسفانه، روش توصیف شده فقط با سازنده پیش فرض (بدون پارامتر) کار می کند. چگونه متدها را با آرگومان ها و سازنده ها را با پارامترها فراخوانی کنیم؟ زمان آن رسیده است که سازنده خود را از کامنت برداریم. همانطور که انتظار می رود، newInstance()سازنده پیش فرض را پیدا نمی کند و دیگر کار نمی کند. بیایید ایجاد یک نمونه کلاس را بازنویسی کنیم:
public static void main(String[] args) {
   MyClass myClass = null;
   try {
       Class clazz = Class.forName(MyClass.class.getName());
       Class[] params = {int.class, String.class};
       myClass = (MyClass) clazz.getConstructor(params).newInstance(1, "default2");
   } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
       e.printStackTrace();
   }
   System.out.println(myClass);//output created object reflection.MyClass@60e53b93
}
برای بدست آوردن سازنده های کلاس، متد را از توضیحات کلاس getConstructors()و برای به دست آوردن پارامترهای سازنده، فراخوانی کنید getParameterTypes():
Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
   Class[] paramTypes = constructor.getParameterTypes();
   for (Class paramType : paramTypes) {
       System.out.print(paramType.getName() + " ");
   }
   System.out.println();
}
به این ترتیب ما تمام سازنده ها و تمام پارامترها را به آنها می رسانیم. در مثال من، فراخوانی به یک سازنده خاص با پارامترهای مشخص و از قبل شناخته شده وجود دارد. و برای فراخوانی این سازنده از متد استفاده می کنیم newInstanceکه در آن مقادیر این پارامترها را مشخص می کنیم. همین امر invokeبرای فراخوانی متدها نیز اتفاق خواهد افتاد. این سوال مطرح می شود: فراخوانی بازتابی سازنده ها کجا می تواند مفید باشد؟ همانطور که در ابتدا ذکر شد، فناوری‌های جاوا مدرن بدون Reflection API کار نمی‌کنند. به عنوان مثال، DI (تزریق وابستگی)، که در آن حاشیه‌نویسی‌ها با بازتاب روش‌ها و سازنده‌ها ترکیب می‌شوند، کتابخانه Dagger را تشکیل می‌دهند که در توسعه اندروید محبوب است. پس از خواندن این مقاله، می توانید با اطمینان خود را در مکانیزم های Reflection API آگاه بدانید. بیهوده نیست که بازتاب را سمت تاریک جاوا می نامند. این به طور کامل پارادایم OOP را می شکند. در جاوا، کپسوله سازی برای مخفی کردن و محدود کردن دسترسی برخی از اجزای برنامه به برخی دیگر عمل می کند. منظور ما از استفاده از اصلاح کننده خصوصی این است که دسترسی به این فیلد فقط در کلاسی است که این فیلد وجود دارد، بر اساس آن معماری بعدی برنامه را می سازیم. در این مقاله دیدیم که چگونه می توانید از بازتاب برای رسیدن به هر جایی استفاده کنید. یک مثال خوب در قالب یک راه حل معماری، الگوی طراحی مولد - Singleton. ایده اصلی آن این است که در کل عملیات برنامه، کلاسی که این الگو را پیاده‌سازی می‌کند باید فقط یک کپی داشته باشد. این کار با تنظیم تعدیل کننده دسترسی پیش فرض روی خصوصی برای سازنده انجام می شود. و اگر یک برنامه نویس با بازتاب خود چنین کلاس هایی ایجاد کند بسیار بد خواهد بود. ضمناً یک سوال بسیار جالب وجود دارد که اخیراً از کارمندم شنیدم: آیا کلاسی که یک قالب را پیاده سازی می کند می تواند Singletonوارث داشته باشد؟ آیا ممکن است در این مورد حتی تأمل نیز ناتوان باشد؟ نظرات خود را در مورد مقاله و پاسخ در نظرات بنویسید و همچنین سوالات خود را بپرسید! قدرت واقعی Reflection API همراه با Runtime Annotations است که احتمالاً در مقاله‌ای در مورد جنبه تاریک جاوا در مورد آن صحبت خواهیم کرد. با تشکر از توجه شما!
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION