Questo è un post tradotto da IA.
[Java] Concetto e utilizzo della Reflection
- Lingua di scrittura: Coreana
- •
- Paese di riferimento: Tutti i paesi
- •
- Tecnologia dell'informazione
Seleziona la lingua
Testo riassunto dall'intelligenza artificiale durumis
- La Reflection è un'API che consente di accedere alle informazioni di classe e di manipolare le classi in fase di runtime.
- Tramite la Reflection, è possibile creare istanze di classe e accedere a campi e metodi indipendentemente dai modificatori di accesso e risulta particolarmente utile nella gestione dinamica delle dipendenze durante le fasi di sviluppo di grandi progetti, come nei framework.
- Tuttavia, può violare l'incapsulamento e causare cali di prestazioni, quindi è meglio utilizzarla solo quando strettamente necessario.
Cos'è la Reflection?
La reflection è un'API che supporta la creazione di istanze di classi desiderate tramite oggetti di tipo Class caricati nell'area heap e supporta l'accesso ai campi e ai metodi delle istanze indipendentemente dai modificatori di accesso.
Qui, il termine classe caricata si riferisce alla creazione di un oggetto di tipoClassche contiene informazioni sulla classe corrispondente nella memoria heap dopo che il class loader JVM ha completato il caricamento del file di classe. Tieni presente che questo è diverso dagli oggetti creati tramite la parola chiave new. Se non capisci l'oggetto di tipo Class, dai un'occhiata alla documentazione JDK per l'oggetto java.lang.class.
Come usare
Prima di utilizzare la reflection, è necessario ottenere un oggetto di tipo Class caricato nell'area heap. Esistono 3 metodi.
- Ottienilo con Class.class
- Ottienilo con instance.getClass()
- Ottienilo con Class.forName("nome_classe")
public class Member {
private String name;
protected int age;
public String hobby;
public Member() {
}
public Member(String name, int age, String hobby) {
this.name = name;
this.age = age;
this.hobby = hobby;
}
public void speak(String message) {
System.out.println(message);
}
private void secret() {
System.out.println("La password è 1234.");
}
@Override
public String toString() {
return "Member{" +
"name='" + name + '\'' +
", age=" + age +
", hobby='" + hobby + '\'' +
'}';
}
}
public class Main {
public static void main(String[] args) throws ClassNotFoundException {
Class memberClass = Member.class;
System.out.println(System.identityHashCode(memberClass));
Member member = new Member("Jayon", 23, "Sviluppo Darath");
Class extends Member> memberClass2 = member.getClass();
System.out.println(System.identityHashCode(memberClass2));
Class> memberClass3 = Class.forName("{nome_pacchetto}.Member");
System.out.println(System.identityHashCode(memberClass3));
}
}
// Risultati dell'esecuzione
1740000325
1740000325
Puoi vedere che le istanze di tipo Class ottenute con i 3 metodi sono tutte uguali. Indipendentemente dal metodo utilizzato, il valore hash è lo stesso, quindi puoi usarlo in base alle tue esigenze.
Ora puoi creare istanze della classe corrispondente tramite il tipo Class ottenuto e accedere ai campi e ai metodi dell'istanza indipendentemente dai modificatori di accesso. Prima crea un'istanza della classe corrispondente.
public class Main {
public static void main(String[] args) throws Exception {
// Stampa tutti i costruttori di Member
Member member = new Member();
Class extends Member> memberClass = member.getClass();
Arrays.stream(memberClass.getConstructors()).forEach(System.out::println);
// Creazione di un'istanza tramite il costruttore predefinito di Member
Constructor extends Member> constructor = memberClass.getConstructor();
Member member2 = constructor.newInstance();
System.out.println("member2 = " + member2);
// Creazione di un'istanza tramite un altro costruttore di Member
Constructor extends Member> fullConstructor =
memberClass.getConstructor(String.class, int.class, String.class);
Member member3 = fullConstructor.newInstance("Jayon", 23, "Sviluppo Darath");
System.out.println("member3 = " + member3);
}
}
// Risultati dell'esecuzione
public Member()
public Member(java.lang.String,int,java.lang.String)
member2 = Member{name='null', age=0, hobby='null'}
Puoi ottenere il costruttore tramite getConstructor() e creare dinamicamente un'istanza di Member tramite newInstance().
Infine, accedi ai campi e ai metodi dell'istanza indipendentemente dai modificatori di accesso.
public class Main {
public static void main(String[] args) throws Exception {
Member member = new Member("Jayon", 23, "Sviluppo Darath");
Class extends Member> memberClass = member.getClass();
// Accesso ai campi
Field[] fields = memberClass.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
System.out.println(field.get(member));
}
fields[0].set(member, "Jayon2");
System.out.println(member);
// Accesso ai metodi
Method speakMethod = memberClass.getDeclaredMethod("speak", String.class);
speakMethod.invoke(member, "Test di Reflection");
Method secretMethod = memberClass.getDeclaredMethod("secret");
secretMethod.setAccessible(true);
secretMethod.invoke(member);
}
}
// Risultati dell'esecuzione
Jayon
23
Sviluppo Darath
Member{name='Jayon2', age=23, hobby='Sviluppo Darath'}
Test di Reflection
Puoi ottenere tutte le variabili di istanza della classe tramite getDeclaredFileds() e ottenere il valore del campo tramite get() e modificare il valore del campo tramite set(). Tieni presente che quando accedi a un campo con un modificatore di accesso privato, devi impostare l'argomento di setAccessible() su true.
Puoi anche ottenere il metodo tramite getDeclaredMethod(). In questo caso, devi passare come argomenti il nome del metodo e il tipo di parametro. Allo stesso modo, quando accedi a un metodo con un modificatore di accesso privato, devi impostare l'argomento di setAccessible() su true. Infine, puoi chiamare il metodo ottenuto tramite l'API di reflection tramite invoke().
Pro e contro
- Vantaggi
- Ha la flessibilità di creare istanze di classi e accedere a campi e metodi indipendentemente dai modificatori di accesso in fase di esecuzione per eseguire le attività necessarie.
- Svantaggi
- Viola l'incapsulamento.
- Poiché le istanze vengono create in fase di esecuzione, il tipo corrispondente non può essere verificato in fase di compilazione.
- Poiché le istanze vengono create in fase di esecuzione, è difficile comprendere il flusso di lavoro specifico.
- Le prestazioni sono più lente rispetto all'accesso diretto ai campi e ai metodi utilizzando la reflection. (Non è più lento in tutte le situazioni.)
Perché usarlo
Tramite l'API di reflection, puoi accedere alle informazioni sulla classe in fase di esecuzione e manipolare la classe a tuo piacimento. Puoi anche manipolare campi e metodi dichiarati con modificatori di accesso privati. Sembra una tecnologia che non dovrebbe essere utilizzata perché viola l'incapsulamento importante nella progettazione orientata agli oggetti.
In fase di sviluppo a livello di console di piccole dimensioni, lo sviluppatore può comprendere tutti gli oggetti e le dipendenze utilizzati nel programma in fase di compilazione. Tuttavia, nelle fasi di sviluppo di grandi dimensioni come i framework, è difficile comprendere numerosi oggetti e dipendenze. In questo caso, utilizzando la reflection, è possibile creare dinamicamente classi e stabilire dipendenze.
Ad esempio, guardando la Bean Factory di Spring, puoi vedere che se aggiungi un'annotazione come @Controller, @Service o @Repository, Bean Factory crea e gestisce automaticamente la classe a cui è stata aggiunta l'annotazione. Lo sviluppatore non ha mai comunicato a Bean Factory la classe corrispondente. Questo è possibile grazie alla reflection. Se viene trovata una classe a cui è stata aggiunta l'annotazione in fase di esecuzione, la reflection viene utilizzata per creare un'istanza della classe corrispondente, iniettare i campi necessari e salvarla nella Bean Factory.
Naturalmente, come detto sopra, poiché viola l'incapsulamento, è meglio usarlo solo quando necessario.