שלום, פדאוואן הצעיר. במאמר זה אספר לכם על ה-Force, שבכוחו משתמשים מתכנתי ג'אווה רק במצב חסר סיכוי לכאורה. אז, הצד האפל של ג'אווה הוא -
השתקפות ב-Java מתבצעת באמצעות Java Reflection API. מה זה ההשתקפות הזו? ישנה הגדרה קצרה ומדויקת שפופולרית גם באינטרנט. רפלקציה (מתוך Late Latin reflexio - חוזר אחורה) הוא מנגנון ללימוד נתונים על תוכנית במהלך ביצועה. 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
זה בספריית ar מהודרת או במודול סגור אחר ללא גישת עריכה, ובפועל זה קורה לעתים קרובות ביותר. ואיזה מתכנת לא קשוב פשוט שכח לכתוב 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
. הוא מייצג מחלקות וממשקים ביישום Java בר הפעלה. לא ניגע בקשר בין Class
לבין ClassLoader
. זה לא נושא המאמר. לאחר מכן, כדי לקבל את השדות של המחלקה הזו, צריך לקרוא ל- method getFields()
, שיטה זו תחזיר לנו את כל השדות הזמינים של המחלקה. זה לא מתאים לנו, מכיוון שהתחום שלנו הוא private
, אז אנחנו משתמשים בשיטה getDeclaredFields()
.שיטה זו גם מחזירה מערך של שדות מחלקה, אבל עכשיו גם private
וגם protected
. במצב שלנו, אנחנו יודעים את שם התחום שמעניין אותנו, ונוכל להשתמש בשיטה getDeclaredField(String)
, איפה String
שם השדה הרצוי. הערה: getFields()
ואל getDeclaredFields()
תחזירו את השדות של כיתת ההורים! מעולה, קיבלנו חפץ Field
עם קישור ל- name
. כי השדה לא היה публичным
(ציבורי), יש לתת גישה לעבוד איתו. השיטה setAccessible(true)
מאפשרת לנו להמשיך לעבוד. עכשיו השדה name
לגמרי בשליטתנו! אתה יכול לקבל את הערך שלו על ידי קריאה get(Object)
לאובייקט Field
, שבו Object
הוא מופע של המחלקה שלנו MyClass
. אנו מטילים אותו String
ומשייכים אותו למשתנה שלנו name
. במקרה שאין לנו פתאום setter
'a, נוכל להשתמש בשיטה כדי להגדיר ערך חדש לשדה השם 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
.
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
}
בזמן הפעלת יישום ה-Java, לא כל המחלקות נטענות ל-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
לשיטות שיחות. נשאלת השאלה: היכן קריאה רפלקטיבית של בנאים יכולה להיות שימושית? טכנולוגיות Java מודרניות, כאמור בהתחלה, אינן יכולות להסתדר בלי Reflection API. לדוגמה, DI (Dependency Injection), שבו הערות בשילוב עם השתקפות של שיטות ובנאים יוצרים את ספריית Dagger, הפופולרית בפיתוח אנדרואיד. לאחר קריאת מאמר זה, אתה יכול לראות את עצמך בביטחון מואר במנגנוני ה-Reflection API. לא בכדי השתקפות נקראת הצד האפל של ג'אווה. זה שובר לחלוטין את פרדיגמת OOP. ב-java, אנקפסולציה משמשת להסתרה ולהגביל את הגישה של חלק ממרכיבי התוכנית לאחרים. בשימוש ב-private modifier אנו מתכוונים שהגישה לשדה זה תהיה רק בתוך המחלקה שבה קיים שדה זה, על בסיס זה אנו בונים את הארכיטקטורה הנוספת של התוכנית. במאמר זה ראינו כיצד אתה יכול להשתמש בהשתקפות כדי להגיע לכל מקום. דוגמה טובה בצורת פתרון אדריכלי היא תבנית העיצוב הגנרטיבית - Singleton
. הרעיון המרכזי שלו הוא שלאורך כל פעולת התוכנית, לכיתה המיישמת תבנית זו צריך להיות רק עותק אחד. זה נעשה על ידי הגדרת ברירת המחדל של שינוי הגישה לפרטי עבור הבנאי. וזה יהיה רע מאוד אם איזה מתכנת עם השתקפות משלו יוצר שיעורים כאלה. אגב, יש שאלה מאוד מעניינת ששמעתי לאחרונה מהעובד שלי: האם לכיתה המיישמת תבנית יכולים להיות Singleton
יורשים? האם ייתכן שאפילו השתקפות חסרת אונים במקרה זה? כתבו את המשוב שלכם על המאמר ואת התשובה בתגובות, וגם שאלו את שאלותיכם! הכוח האמיתי של Reflection API מגיע בשילוב עם Runtime Annotations, שעליהם כנראה נדבר במאמר עתידי על הצד האפל של Java. תודה לך על תשומת הלב!
GO TO FULL VERSION