CodeGym /وبلاگ جاوا /Random-FA /Reflection API: Reflection. سمت تاریک جاوا
John Squirrels
مرحله
San Francisco

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

در گروه منتشر شد
درود، پادوان جوان. در این مقاله، من در مورد Force به شما می گویم، قدرتی که برنامه نویسان جاوا فقط در موقعیت های به ظاهر غیرممکن از آن استفاده می کنند. قسمت تاریک جاوا Reflection API است. در جاوا، بازتاب با استفاده از Java Reflection API پیاده سازی می شود.

بازتاب جاوا چیست؟

یک تعریف کوتاه، دقیق و محبوب در اینترنت وجود دارد. انعکاس ( از لاتین اواخر reflexio - به برگشتن ) مکانیزمی است برای کاوش داده های مربوط به یک برنامه در حین اجرای آن. Reflection به شما امکان می دهد اطلاعات مربوط به فیلدها، متدها و سازندگان کلاس را کشف کنید. Reflection به شما امکان می دهد با انواعی کار کنید که در زمان کامپایل وجود نداشتند، اما در زمان اجرا در دسترس بودند. انعکاس و یک مدل منطقی سازگار برای صدور اطلاعات خطا، ایجاد کد دینامیک صحیح را ممکن می سازد. به عبارت دیگر، درک نحوه عملکرد بازتاب در جاوا، فرصت های شگفت انگیزی را برای شما باز می کند. شما به معنای واقعی کلمه می‌توانید کلاس‌ها و اجزای آنها را مدیریت کنید. در اینجا یک لیست اساسی از آنچه بازتاب اجازه می دهد وجود دارد:
  • یادگیری/تعیین کلاس یک شی.
  • اطلاعاتی در مورد اصلاح‌کننده‌ها، فیلدها، متدها، ثابت‌ها، سازنده‌ها و سوپرکلاس‌های کلاس دریافت کنید.
  • دریابید که چه متدهایی به واسط(های) پیاده سازی شده تعلق دارند.
  • یک نمونه از کلاسی ایجاد کنید که نام کلاس آن تا زمان اجرا ناشناخته باشد.
  • مقادیر فیلدهای یک شی را با نام دریافت و تنظیم کنید.
  • متد یک شی را با نام فراخوانی کنید.
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);
   }
}
همانطور که می بینید، این یک کلاس بسیار ابتدایی است. سازنده با پارامترها عمداً نظر داده می شود. بعداً به آن باز خواهیم گشت. اگر با دقت به محتویات کلاس نگاه کرده باشید، احتمالا متوجه عدم وجود گیرنده برای فیلد نام شده اید . خود فیلد نام با اصلاح کننده دسترسی خصوصی مشخص شده است : ما نمی توانیم خارج از خود کلاس به آن دسترسی داشته باشیم، به این معنی که نمی توانیم مقدار آن را بازیابی کنیم. " پس مشکل چیه ؟" تو بگو. "افزودن یک گیرنده یا تغییر اصلاح کننده دسترسی". و حق با شماست، مگر اینکه MyClassدر یک کتابخانه AAR کامپایل شده یا در ماژول خصوصی دیگری بدون توانایی ایجاد تغییرات باشد. در عمل، این همیشه اتفاق می افتد. و برخی از برنامه نویسان بی دقت به سادگی فراموش کردند که یک گیرنده بنویسند . این همان زمان برای یادآوری انعکاس است! بیایید سعی کنیم به قسمت نام خصوصی کلاس برسیم 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()متد را فراخوانی کنید. این متد تمام فیلدهای قابل دسترسی این کلاس را برمی گرداند. این برای ما کار نمی کند، زیرا زمینه ما خصوصی است ، بنابراین از getDeclaredFields()روش استفاده می کنیم. این متد همچنین آرایه ای از فیلدهای کلاس را برمی گرداند، اما اکنون شامل فیلدهای خصوصی و محافظت شده است . در این صورت، ما نام فیلد مورد نظر خود را می دانیم، بنابراین می توانیم از روشی استفاده کنیم که getDeclaredField(String)نام Stringفیلد مورد نظر کجاست. توجه داشته باشید: getFields()و getDeclaredFields()فیلدهای یک کلاس والد را برنگردانید! عالی. ما یک Fieldشی دریافت کردیم که به نام ما اشاره دارد . از آنجایی که فیلد عمومی نبود ، باید اجازه دسترسی به کار با آن را بدهیم. این setAccessible(true)روش به ما اجازه می دهد تا ادامه دهیم. اکنون قسمت نام تحت کنترل کامل ما است! شما می توانید مقدار آن را با فراخوانی متد Fieldشی get(Object)، جایی که Objectنمونه ای از MyClassکلاس ما است، بازیابی کنید. نوع را به متغیر نامString خود تبدیل کرده و مقدار را به آن اختصاص می دهیم . اگر نمی‌توانیم تنظیم‌کننده‌ای برای تنظیم مقدار جدید برای فیلد نام پیدا کنیم، می‌توانید از روش set استفاده کنید :
field.set(myClass, (String) "new value");
تبریک می گویم! شما به تازگی بر اصول بازتاب مسلط شده اید و به یک زمینه خصوصی دسترسی پیدا کرده اید! try/catchبه بلوک و انواع استثناهای در حال رسیدگی توجه کنید . IDE به شما خواهد گفت که حضور آنها به خودی خود الزامی است، اما شما می توانید به وضوح از روی نام آنها بگویید که چرا آنها اینجا هستند. در حال حرکت! همانطور که متوجه شده اید، MyClassکلاس ما قبلاً روشی برای نمایش اطلاعات در مورد داده های کلاس دارد:
private void printData(){
       System.out.println(number + name);
   }
اما این برنامه نویس اثر انگشتش را اینجا هم گذاشت. این روش دارای یک اصلاح کننده دسترسی خصوصی است و ما باید کد خود را بنویسیم تا داده ها را هر بار نمایش دهیم. چه افتضاحی بازتاب ما کجا رفت؟ تابع زیر را بنویسید:
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(Object, Args)که در آن Objectنمونه ای از MyClassکلاس نیز وجود دارد. Argsآرگومان های روش هستند، اگرچه آرگومان ما هیچ کدام را ندارد. printDataاکنون برای نمایش اطلاعات از تابع استفاده می کنیم :
public static void main(String[] args) {
   MyClass myClass = new MyClass();
   int number = myClass.getNumber();
   String name = null; //?
   printData(myClass); // Output: 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
}
هورای! اکنون به متد خصوصی کلاس دسترسی داریم. اما اگر متد دارای آرگومان باشد، چه می‌شود و چرا سازنده نظر داده می‌شود؟ هر چیزی در زمان خودش از تعریف ابتدا مشخص است که انعکاس به شما امکان می دهد نمونه هایی از یک کلاس را در زمان اجرا (در حالی که برنامه در حال اجرا است) ایجاد کنید! ما می توانیم با استفاده از نام کامل کلاس یک شی ایجاد کنیم. نام کامل کلاس، نام کلاس، شامل مسیر بسته آن است .
Reflection API: Reflection.  سمت تاریک جاوا - 2
در سلسله مراتب بسته من ، نام کامل 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، آنگاه ClassLoaderکه مسئول بارگذاری کلاس ها در JVM است، هرگز کلاس را بارگذاری نمی کند. این بدان معناست که شما باید آن را مجبور کنید 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هنگام استفاده از متدهای فراخوانی نیز همینطور خواهد بود . این سؤال پیش می‌آید: چه زمانی فراخوانی سازنده‌ها از طریق بازتاب مفید است؟ همانطور که در ابتدا ذکر شد، فناوری های مدرن جاوا نمی توانند بدون Java Reflection API کار کنند. به عنوان مثال، Dependency Injection (DI)، که حاشیه نویسی را با بازتاب روش ها و سازنده ها ترکیب می کند تا کتابخانه محبوب Darer را برای توسعه اندروید تشکیل دهد. پس از خواندن این مقاله، می توانید با اطمینان خود را در روش های Java Reflection API آموزش دیده بدانید. آنها بیهوده انعکاس را قسمت تاریک جاوا نمی نامند. این به طور کامل پارادایم OOP را می شکند. در جاوا، کپسوله کردن، دسترسی دیگران به اجزای برنامه خاص را پنهان می کند و آن را محدود می کند. وقتی از اصلاح کننده خصوصی استفاده می کنیم، قصد داریم که آن فیلد فقط از داخل کلاسی که در آن وجود دارد قابل دسترسی باشد. و معماری بعدی برنامه را بر اساس این اصل می سازیم. در این مقاله، ما دیدیم که چگونه می توانید از بازتاب استفاده کنید تا به زور در هر جایی راه خود را ادامه دهید. الگوی طراحی خلاقانه Singleton نمونه خوبی از این امر به عنوان یک راه حل معماری است. ایده اصلی این است که کلاسی که این الگو را پیاده‌سازی می‌کند تنها یک نمونه در طول اجرای کل برنامه خواهد داشت. این کار با افزودن اصلاح کننده دسترسی خصوصی به سازنده پیش فرض انجام می شود. و بسیار بد خواهد بود اگر یک برنامه نویس از بازتاب استفاده کند تا نمونه های بیشتری از چنین کلاس هایی ایجاد کند. به هر حال، اخیراً شنیدم که یکی از همکاران سوال بسیار جالبی پرسیده است: آیا کلاسی که الگوی Singleton را پیاده سازی می کند، می تواند ارثی باشد؟ آیا ممکن است در این مورد حتی بازتاب نیز ناتوان باشد؟ نظرات خود را در مورد مقاله و پاسخ خود در نظرات زیر بنویسید و سوالات خود را در آنجا بپرسید!
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION