CodeGym /בלוג Java /Random-HE /Reflection API: Reflection. הצד האפל של ג'אווה
John Squirrels
רָמָה
San Francisco

Reflection API: Reflection. הצד האפל של ג'אווה

פורסם בקבוצה
שלום, פדאוואן הצעיר. במאמר זה, אספר לכם על ה-Force, כוח שמתכנתי ג'אווה משתמשים בו רק במצבים בלתי אפשריים לכאורה. הצד האפל של ג'אווה הוא ה-Reflection API. ב-Java, השתקפות מיושמת באמצעות Java Reflection API.

מהי השתקפות ג'אווה?

יש הגדרה קצרה, מדויקת ופופולרית באינטרנט. רפלקציה ( מאחר רפלקסיו בלטינית - להסתובב לאחור ) הוא מנגנון לחקור נתונים על תוכנית בזמן שהיא פועלת. Reflection מאפשר לך לחקור מידע על שדות, שיטות ובני מחלקות. Reflection מאפשר לך לעבוד עם טיפוסים שלא היו נוכחים בזמן ההידור, אך הפכו זמינים במהלך זמן הריצה. השתקפות ומודל עקבי מבחינה לוגית להנפקת מידע שגיאות מאפשרים ליצור קוד דינמי נכון. במילים אחרות, הבנה כיצד פועלת השתקפות בג'אווה תפתח בפניך מספר הזדמנויות מדהימות. אתה יכול ממש ללהטט בין שיעורים ומרכיביהם. להלן רשימה בסיסית של מה שהשתקפות מאפשרת:
  • למד/קבע את המחלקה של אובייקט;
  • קבל מידע על המשתנים, השדות, השיטות, הקבועים, הבנאים ומחלקות העל של מחלקה;
  • גלה אילו שיטות שייכות לממשקים מיושמים;
  • צור מופע של מחלקה ששם המחלקה שלה לא ידוע עד זמן הריצה;
  • קבל והגדר ערכים של שדות אובייקט לפי שם;
  • קרא למתודה של אובייקט בשם.
השתקפות משמשת כמעט בכל טכנולוגיות Java המודרניות. קשה לדמיין ש-Java, כפלטפורמה, יכלה להשיג אימוץ כה נרחב ללא השתקפות. סביר להניח שזה לא היה. עכשיו, כשאתה מכיר בדרך כלל את הרפלקציה כמושג תיאורטי, בואו נמשיך ליישומו המעשי! לא נלמד את כל השיטות של 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);
   }
}
כפי שאתה יכול לראות, זהו שיעור בסיסי מאוד. הבנאי עם הפרמטרים מוערב בכוונה. נחזור לזה מאוחר יותר. אם הסתכלתם היטב על תוכן השיעור, סביר להניח שמתם לב להיעדר גטר לשדה השם . שדה השם עצמו מסומן במשנה הגישה הפרטית : אנחנו לא יכולים לגשת אליו מחוץ למחלקה עצמה, מה שאומר שאיננו יכולים לאחזר את הערך שלו . " אז מה הבעיה ?" אתה אומר. "הוסף מגטר או שנה את משנה הגישה". ואתה צודק, אלא אם כן היה בספריית AAR מהול או במודול פרטי אחר ללא יכולת לבצע שינויים. בפועל, זה קורה כל הזמן. ואיזה מתכנת רשלני פשוט שכח לכתוב מגביר . זה בדיוק הזמן להיזכר בהשתקפות! בואו ננסה להגיע לשדה השם הפרטי של הכיתה : MyClassMyClass
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. הוא מייצג מחלקות וממשקים ביישום Java בר הפעלה. לא נסקור את הקשר בין Classלבין ClassLoader, מכיוון שזה לא הנושא של מאמר זה. לאחר מכן, כדי לאחזר את השדות של המחלקה הזו, עליך לקרוא למתודה getFields(). שיטה זו תחזיר את כל השדות הנגישים של המחלקה הזו. זה לא עובד לנו, כי התחום שלנו הוא פרטי , אז אנחנו משתמשים getDeclaredFields()בשיטה. שיטה זו גם מחזירה מערך של שדות מחלקה, אך כעת היא כוללת שדות פרטיים ומוגנים . במקרה זה, אנו יודעים את שם השדה בו אנו מעוניינים, כך שנוכל להשתמש בשיטה , היכן שם השדה הרצוי. getDeclaredField(String)Stringהערה: getFields()ואל getDeclaredFields()תחזירו שדות של כיתת הורים! גדול. יש לנו Fieldחפץ שמתייחס לשם שלנו . מכיוון שהתחום לא היה ציבורי , עלינו להעניק גישה לעבוד איתו. השיטה setAccessible(true)מאפשרת לנו להמשיך הלאה. כעת שדה השם נמצא בשליטה מלאה שלנו! אתה יכול לאחזר את הערך שלו על ידי קריאה למתודה Fieldשל האובייקט get(Object), שבו Objectהוא מופע של MyClassהמחלקה שלנו. אנו ממירים את הסוג ל- Stringומקצים את הערך למשתנה השם שלנו . אם אנחנו לא יכולים למצוא מגדיר להגדיר ערך חדש לשדה השם, אתה יכול להשתמש בשיטת ההגדרה :
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()
בואו נשתמש בהשתקפות Java כדי ליצור מופע של המחלקה:
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
}
כאשר יישום Java מתחיל, לא כל המחלקות נטענות לתוך ה-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 מודרניות לא יכולות להסתדר בלי ה-Java Reflection API. לדוגמה, Dependency Injection (DI), המשלבת הערות עם השתקפות של שיטות ובנאים כדי ליצור את ספריית Darer הפופולרית לפיתוח אנדרואיד. לאחר קריאת מאמר זה, אתה יכול לראות את עצמך בביטחון כמשכיל לדרכים של Java Reflection API. הם לא קוראים להשתקפות הצד האפל של ג'אווה סתם. זה שובר לחלוטין את פרדיגמת OOP. ב-Java, אנקפסולציה מסתירה ומגבילה את הגישה של אחרים לרכיבי תוכנה מסוימים. כאשר אנו משתמשים ב-private modifier, אנו מתכוונים שהשדה הזה יהיה זמין רק מתוך המחלקה שבה הוא קיים. ואנחנו בונים את הארכיטקטורה הבאה של התוכנית על בסיס העיקרון הזה. במאמר זה, ראינו כיצד אתה יכול להשתמש בהשתקפות כדי לאלץ את דרכך לכל מקום. דפוס העיצוב היצירתי Singleton הוא דוגמה טובה לכך כפתרון אדריכלי. הרעיון הבסיסי הוא שלכיתה המיישמת דפוס זה יהיה רק ​​מופע אחד במהלך ביצוע התוכנית כולה. זה מושג על ידי הוספת משנה הגישה הפרטית לבנאי ברירת המחדל. וזה יהיה רע מאוד אם מתכנת ישתמש בהשתקפות כדי ליצור עוד מופעים של מחלקות כאלה. אגב, לאחרונה שמעתי עמית לעבודה שואל שאלה מאוד מעניינת: האם מחלקה שמיישמת את דפוס הסינגלטון יכולה לעבור בתורשה? האם יכול להיות שבמקרה הזה אפילו השתקפות תהיה חסרת אונים? השאר את המשוב שלך על המאמר ואת התשובה שלך בתגובות למטה, ושאל את השאלות שלך שם!

קריאה נוספת:

הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION