![translation](https://cdn.durumis.com/common/trans.png)
Dit is een door AI vertaalde post.
[Java] Reflectie concept en gebruik
- Taal van de tekst: Koreaans
- •
-
Referentieland: Alle landen
- •
- Informatietechnologie
Selecteer taal
Samengevat door durumis AI
- Reflectie is een API die toegang tot klasse-informatie tijdens runtime mogelijk maakt, zodat klassen naar wens kunnen worden gemanipuleerd.
- Met reflectie kunnen instanties van klassen worden gemaakt en velden en methoden worden gebruikt, ongeacht toegangsbeheerders, en het is met name nuttig in grote ontwikkelingsfasen, zoals frameworks, om afhankelijkheden dynamisch te beheren.
- Het is echter raadzaam om reflectie alleen te gebruiken wanneer dat noodzakelijk is, omdat het inbreuk kan maken op inkapseling en prestatieverlies kan veroorzaken.
Reflectie?
Reflectie is een API die ondersteuning biedt voor het maken van instanties van een gewenste klasse via het Class-type-object dat in de heap-ruimte is geladen, en voor toegang tot de velden en methoden van de instantie, ongeacht de toegangsbeheerders.
Een geladen klasse betekent hier dat de klasselader van de JVM het laden van het klassebestand heeft voltooid en een Class-type-objectheeft gemaakt dat de informatie van die klasse bevat en in de heap-ruimte van het geheugen heeft opgeslagen. Merk op dat dit anders is dan de objecten die worden gemaakt met het nieuwe trefwoord. Als je niet goed begrijpt wat een Class-type-object is, raadpleeg dan de JDK-documentatie voor het java.lang.class-object.
Gebruik
Voordat je reflectie kunt gebruiken, moet je het Class-type-object ophalen dat is geladen in de heap-ruimte. Er zijn in totaal drie manieren.
- Ophalen met klasse.klasse
- Ophalen met instantie.getClass()
- Ophalen met Class.forName("klasse-naam")
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("Het wachtwoord is 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, "Dara쓰 development");
Class extends Member> memberClass2 = member.getClass();
System.out.println(System.identityHashCode(memberClass2));
Class> memberClass3 = Class.forName("{package name}.Member");
System.out.println(System.identityHashCode(memberClass3));
}
}
// Uitvoer
1740000325
1740000325
Je kunt zien dat de Class-type-instanties die met de drie methoden zijn opgehaald, allemaal hetzelfde zijn. Ongeacht welke methode je gebruikt, de hash-waarde is hetzelfde, dus je kunt deze naar behoefte gebruiken.
Nu kun je met het opgehaalde Class-type instanties van die klasse maken, en kun je toegang krijgen tot de velden en methoden van de instantie, ongeacht de toegangsbeheerders. Laten we eerst instanties van die klasse maken.
public class Main {
public static void main(String[] args) throws Exception {
// Alle constructoren van Member afdrukken
Member member = new Member();
Class extends Member> memberClass = member.getClass();
Arrays.stream(memberClass.getConstructors()).forEach(System.out::println);
// Instantie maken via de standaardconstructor van Member
Constructor extends Member> constructor = memberClass.getConstructor();
Member member2 = constructor.newInstance();
System.out.println("member2 = " + member2);
// Instantie maken via een andere constructor van Member
Constructor extends Member> fullConstructor =
memberClass.getConstructor(String.class, int.class, String.class);
Member member3 = fullConstructor.newInstance("Jayon", 23, "Dara쓰 development");
System.out.println("member3 = " + member3);
}
}
// Uitvoer
public Member()
public Member(java.lang.String,int,java.lang.String)
member2 = Member{name='null', age=0, hobby='null'}
Met getConstructor() kun je de constructor ophalen, en met newInstance() kun je dynamisch Member-instanties maken.
Laten we ten slotte de velden en methoden van de instantie benaderen, ongeacht de toegangsbeheerders.
public class Main {
public static void main(String[] args) throws Exception {
Member member = new Member("Jayon", 23, "Dara쓰 development");
Class extends Member> memberClass = member.getClass();
// Toegang tot velden
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);
// Toegang tot methoden
Method speakMethod = memberClass.getDeclaredMethod("speak", String.class);
speakMethod.invoke(member, "Reflectie test");
Method secretMethod = memberClass.getDeclaredMethod("secret");
secretMethod.setAccessible(true);
secretMethod.invoke(member);
}
}
// Uitvoer
Jayon
23
Dara쓰 development
Member{name='Jayon2', age=23, hobby='Dara쓰 development'}
Reflectie test
Met getDeclaredFileds() kun je alle instantie-variabelen van de klasse ophalen, met get() kun je de veldwaarde terugkrijgen, en met set() kun je de veldwaarde wijzigen. Let erop dat wanneer je toegang hebt tot velden met een private toegangsbeheerder, je setAccessible() met true als argument moet doorgeven.
Methoden kunnen ook worden opgehaald met getDeclaredMethod(). In dat geval moet je de naam van de methode en het type van de parameter samen als argument doorgeven. Op dezelfde manier, wanneer je toegang hebt tot methoden met een private toegangsbeheerder, moet je setAccessible() met true als argument instellen. Ten slotte kun je de methode die is opgehaald met de reflectie-API aanroepen met de methode invoke().
Voor- en nadelen
- Voordelen
- Het heeft de flexibiliteit om instanties van klassen te maken tijdens runtime, en om toegang te krijgen tot velden en methoden ongeacht de toegangsbeheerders, om de benodigde taken uit te voeren.
- Nadelen
- Het ondermijnt inkapseling.
- Omdat instanties tijdens runtime worden gemaakt, kan het type niet op compileertijd worden gecontroleerd.
- Omdat instanties tijdens runtime worden gemaakt, is het moeilijk om de specifieke workflow te begrijpen.
- Het is langzamer om toegang te krijgen via reflectie dan om rechtstreeks toegang te krijgen tot velden en methoden. (Niet alle situaties zijn langzamer.)
Redenen voor gebruik
Via de reflectie-API kun je tijdens runtime toegang krijgen tot klasse-informatie en klassen naar wens manipuleren. Zelfs velden en methoden die zijn verklaard met een private toegangsbeheerder kunnen worden gemanipuleerd. Het lijkt misschien een technologie die niet mag worden gebruikt, omdat het de inkapseling, die belangrijk is in objectgeoriënteerd ontwerp, ondermijnt.
In kleine console-omgevingen kan een ontwikkelaar alle objecten en afhankelijkheden die in het programma worden gebruikt, op compileertijd identificeren. In grote ontwikkelingsprojecten zoals frameworks is het echter moeilijk om een groot aantal objecten en afhankelijkheden te identificeren. In dat geval kan reflectie worden gebruikt om dynamisch klassen te maken en afhankelijkheden tot stand te brengen.
Bijvoorbeeld, als je naar de Bean Factory van Spring kijkt, zie je dat Bean Factory automatisch de klassen met de annotaties @Controller, @Service en @Repository maakt en beheert, zolang je deze annotaties maar aan de klassen toevoegt. De ontwikkelaar heeft Bean Factory nooit deze klassen laten weten, maar dit is mogelijk dankzij reflectie. Als een klasse met de bijbehorende annotatie tijdens runtime wordt gevonden, wordt deze via reflectie gebruikt om een instantie van die klasse te maken, de benodigde velden te injecteren en op te slaan in Bean Factory.
Natuurlijk is het vanwege de inkapseling die het ondermijnt, het beste om het alleen te gebruiken wanneer dat echt nodig is.