Mire való a Reflection API?

A Java tükrözési mechanizmusa lehetővé teszi a fejlesztő számára, hogy futás közben változtatásokat hajtson végre, és információkat szerezzen az osztályokról, felületekről, mezőkről és metódusokról anélkül, hogy ismerné a nevüket.

A Reflection API lehetővé teszi új objektumok létrehozását, metódusok hívását, valamint mezőértékek lekérését vagy beállítását.

Készítsünk egy listát mindarról, amit a reflexióval megtehet:

  • Határozza meg/határozza meg egy objektum osztályát
  • Információkat szerezhet az osztálymódosítókról, mezőkről, metódusokról, konstansokról, konstruktorokról és szuperosztályokról
  • Megtudhatja, mely metódusok tartoznak a megvalósított interfész(ek)hez
  • Hozzon létre egy példányt egy olyan osztályból, amelynek osztályneve a program végrehajtásáig nem ismert
  • Szerezze be és állítsa be egy példánymező értékét név szerint
  • Példánymetódus hívása név szerint

Szinte minden modern Java technológia tükrözést használ. Ez a legtöbb mai Java / Java EE keretrendszer és könyvtár alapja, például:

  • Tavaszi keretrendszerek webes alkalmazások építéséhez
  • a JUnit tesztelési keretrendszer

Ha még soha nem találkozott ezekkel a mechanizmusokkal, valószínűleg azt kérdezi, miért van erre szükség. A válasz meglehetősen egyszerű, de nagyon homályos: a tükrözés drámaian növeli a rugalmasságot és az alkalmazásunk és kódunk testreszabásának lehetőségét.

De mindig vannak előnyei és hátrányai. Tehát említsünk meg néhány hátrányt:

  • Az alkalmazás biztonságának megsértése. A tükrözés lehetővé teszi számunkra, hogy olyan kódhoz férjünk hozzá, amihez nem kellene (a tokozás megsértése).
  • Biztonsági korlátozások. A tükrözéshez olyan futásidejű engedélyekre van szükség, amelyek nem állnak rendelkezésre a biztonsági kezelőt futtató rendszerek számára.
  • Alacsony teljesítmény. A Java tükrözése dinamikusan határozza meg a típusokat az osztályútvonal vizsgálatával , hogy megtalálja a betöltendő osztályt. Ez csökkenti a program teljesítményét.
  • Nehéz karbantartani. A tükrözést használó kód nehezen olvasható és hibakereshető. Kevésbé rugalmas és nehezebb karbantartani.

Munka osztályokkal a Reflection API használatával

Minden tükrözési művelet egy java.lang.Class objektummal kezdődik. Minden objektumtípushoz létrejön a java.lang.Class változatlan példánya. Metódusokat biztosít az objektumtulajdonságok megszerzéséhez, új objektumok létrehozásához és metódusok hívásához.

Nézzük meg a java.lang.Class használatával kapcsolatos alapvető módszerek listáját :

Módszer Akció
String getName(); Az osztály nevét adja vissza
int getModifiers(); Hozzáférési módosítókat ad vissza
csomag getPackage(); Információkat ad vissza egy csomagról
Osztály getSuperclass(); A szülőosztály adatait adja vissza
Class[] getInterfaces(); Interfészek tömbjét adja vissza
Konstruktor[] getConstructors(); Információkat ad vissza az osztálykonstruktorokról
Fields[] getFields(); Egy osztály mezőit adja vissza
Mező getField(String mezőNév); Egy osztály adott mezőjét adja vissza név szerint
Method[] getMethods(); Metódusok tömbjét adja vissza

Ezek a legfontosabb módszerek az osztályokról, interfészekről, mezőkről és metódusokról való adatszerzéshez. Vannak olyan módszerek is, amelyek lehetővé teszik mezőértékek lekérését vagy beállítását, valamint a privát mezők elérését. Kicsit később megnézzük őket.

Most a java.lang.Class beszerzéséről fogunk beszélni . Ennek három módja van.

1. A Class.forName használata

Egy futó alkalmazásban a forName(String className) metódust kell használnia egy osztály lekéréséhez.

Ez a kód bemutatja, hogyan hozhatunk létre osztályokat reflexióval. Hozzunk létre egy Személy osztályt, amellyel dolgozhatunk:

package com.company;

public class Person {
    private int age;
    private String name;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Példánk második része pedig a tükrözést használó kód:

public class TestReflection {
    public static void main(String[] args) {
        try {
            Class<?> aClass = Class.forName("com.company.Person");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Ez a megközelítés akkor lehetséges, ha az osztály teljes neve ismert. Ezután megkaphatja a megfelelő osztályt a statikus Class.forName() metódussal. Ez a módszer nem használható primitív típusokhoz.

2. .class használata

Ha egy típus elérhető, de nincs belőle példány, akkor az osztályt úgy kaphatja meg, hogy hozzáadja a .class karaktert a típusnévhez. Ez a legegyszerűbb módja egy primitív típus osztályának megszerzésének.

Class aClass = Person.class;

3. A .getClass() használata

Ha elérhető egy objektum, akkor a legegyszerűbb módja annak, hogy osztályt kapjunk , az object.getClass() meghívása .

Person person = new Person();
Class aClass = person.getClass();

Mi a különbség az utolsó két megközelítés között?

Használja az A.class-t , ha tudja, hogy a kódolási időben melyik osztályobjektum érdekli. Ha nem érhető el példány, akkor használja a .class fájlt .

Egy osztály metódusainak beszerzése

Nézzük meg az osztályunk metódusait visszaadó metódusokat: getDeclaredMethods() és getMethods() .

A getDeclaredMethods() egy tömböt ad vissza, amely Method objektumokat tartalmaz a Class objektum által képviselt osztály vagy interfész összes deklarált metódusához, beleértve a nyilvános, privát, alapértelmezett és védett metódusokat, de nem örökölt metódusokat.

A getMethods() egy tömböt ad vissza, amely Method objektumokat tartalmaz a Class objektum által képviselt osztály vagy interfész összes nyilvános metódusához – az osztály vagy interfész által deklarálthoz, valamint a szuperosztályoktól és szuperinterfészektől örököltekhez.

Nézzük meg, hogyan működik mindegyik.

Kezdjük a getDeclaredMethods() -val . Annak érdekében, hogy ismét megértsük a két módszer közötti különbséget, az alábbiakban az absztrakt számok osztályával fogunk dolgozni. Írjunk egy statikus metódust, amely a Method tömbünket List<String> -re konvertálja :

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class TestReflection {
    public static void main(String[] args) {
        final Method[] declaredMethods = Number.class.getDeclaredMethods();
        List<String> actualMethodNames = getMethodNames(declaredMethods);
        actualMethodNames.forEach(System.out::println);
    }

    private static List<String> getMethodNames(Method[] methods) {
        return Arrays.stream(methods)
                .map(Method::getName)
                .collect(Collectors.toList());
    }
}

Íme a kód futtatásának eredménye:

byteValue
shortValue
intValue
longValue
float floatValue;
doubleValue

Ezek a Szám osztályon belül deklarált metódusok. Mit ad vissza a getMethods() ? Változtassunk meg két sort a példában:

final Method[] methods = Number.class.getMethods();
List<String> actualMethodNames = getMethodNames(methods);

Ennek során a következő módszereket fogjuk látni:

byteValue
shortValue
intValue
longValue
float floatValue;
doubleValue
wait
wait egyenlő toString hashCode getClass notify notifyAll
_





Mivel minden osztály örökli az Object -et , metódusunk az Object osztály nyilvános metódusait is visszaadja.

Egy osztály mezőinek megszerzése

A getFields és getDeclaredFields metódusok egy osztály mezőinek lekérésére szolgálnak. Példaként nézzük a LocalDateTime osztályt. Átírjuk a kódunkat:

import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class TestReflection {
    public static void main(String[] args) {
        final Field[] declaredFields = LocalDateTime.class.getDeclaredFields();
        List<String> actualFieldNames = getFieldNames(declaredFields);
        actualFieldNames.forEach(System.out::println);
    }

    private static List<String> getFieldNames(Field[] fields) {
        return Arrays.stream(fields)
                .map(Field::getName)
                .collect(Collectors.toList());
    }
}

A kód végrehajtása eredményeként megkapjuk a LocalDateTime osztályban található mezők halmazát.

MIN
MAX
serialVersionUID
dátum
és idő

A módszerek korábbi vizsgálatával analógián nézzük meg, mi történik, ha egy kicsit megváltoztatjuk a kódot:

final Field[] fields = LocalDateTime.class.getFields();
List<String> actualFieldNames = getFieldNames(fields);

Kimenet:

MIN
MAX

Most nézzük meg a különbséget ezen módszerek között.

A getDeclaredFields metódus Field objektumok tömbjét adja vissza az általa képviselt osztály vagy interfész által deklarált összes mezőhöz.Osztálytárgy.

A getFields metódus Field objektumok tömbjét adja vissza az osztály vagy interfész által képviselt összes nyilvános mezőhöz.Osztálytárgy.

Most nézzük meg a LocalDateTime belsejét .

Az osztályéMINésMAXA mezők nyilvánosak, ami azt jelenti, hogy a getFields metóduson keresztül láthatóak lesznek . Ezzel szemben adátum,idő,serialVersionUIDA metódusok privát módosítóval rendelkeznek , ami azt jelenti, hogy nem lesznek láthatók a getFields metóduson keresztül, de a getDeclaredFields használatával beszerezhetjük őket . Így érhetjük el a Field objektumokat privát mezőkhöz.

Egyéb módszerek leírása

Itt az ideje, hogy beszéljünk a Class osztály néhány metódusáról , nevezetesen:

Módszer Akció
getModifiers A módosítók beszerzése az osztályunkhoz
getPackage Az osztályunkat tartalmazó csomag beszerzése
getSuperclass A szülői osztály megszerzése
getInterfaces Egy osztály által megvalósított interfész tömb lekérése
getName A teljesen minősített osztálynév megszerzése
getSimpleName Egy osztály nevének megszerzése

getModifiers()

A módosítók a segítségével érhetők elOsztálytárgy.

A módosítók olyan kulcsszavak, mint a public , static , interface , stb. A módosítókat a getModifiers() metódussal kapjuk :

Class<Person> personClass = Person.class;
int classModifiers = personClass.getModifiers();

Ez a kód beállítja az an értékétintváltozó, amely egy bit mező. Minden hozzáférésmódosító be- vagy kikapcsolható a megfelelő bit beállításával vagy törlésével. A módosítókat a java.lang.reflect.Modifier osztály metódusaival ellenőrizhetjük :

import com.company.Person;
import java.lang.reflect.Modifier;

public class TestReflection {
    public static void main(String[] args) {
        Class<Person> personClass = Person.class;
        int classModifiers = personClass.getModifiers();

        boolean isPublic = Modifier.isPublic(classModifiers);
        boolean isStatic = Modifier.isStatic(classModifiers);
        boolean isFinal = Modifier.isFinal(classModifiers);
        boolean isAbstract = Modifier.isAbstract(classModifiers);
        boolean isInterface = Modifier.isInterface(classModifiers);

        System.out.printf("Class modifiers: %d%n", classModifiers);
        System.out.printf("Is public: %b%n", isPublic);
        System.out.printf("Is static: %b%n", isStatic);
        System.out.printf("Is final: %b%n", isFinal);
        System.out.printf("Is abstract: %b%n", isAbstract);
        System.out.printf("Is interface: %b%n", isInterface);
    }
}

Emlékezzünk vissza, hogyan néz ki Személyünk nyilatkozata :

public class Person {}

A következő kimenetet kapjuk:

Osztálymódosítók: 1
Nyilvános: igaz
Statikus: hamis
Végleges: hamis
Absztrakt: hamis
Az interfész: hamis

Ha absztrakttá tesszük az osztályunkat, akkor a következőt kapjuk:

public abstract class Person {}

és ez a kimenet:

Osztálymódosítók: 1025
Nyilvános: igaz
Statikus: hamis
Végleges: hamis
Absztrakt: igaz
Az interfész: hamis

Megváltoztattuk a hozzáférés módosítót, ami azt jelenti, hogy a Modifier osztály statikus metódusaival visszaadott adatokat is megváltoztattuk .

getPackage()

Ha csak egy osztályt ismerünk, akkor a csomagjáról kaphatunk információkat:

Class<Person> personClass = Person.class;
final Package aPackage = personClass.getPackage();
System.out.println(aPackage.getName());

getSuperclass()

Ha van Class objektumunk, akkor hozzáférhetünk a szülőosztályához:

public static void main(String[] args) {
    Class<Person> personClass = Person.class;
    final Class<? super Person> superclass = personClass.getSuperclass();
    System.out.println(superclass);
}

Megkapjuk a jól ismert Object osztályt:

class java.lang.Object

De ha az osztályunknak van másik szülői osztálya, akkor azt fogjuk látni helyette:

package com.company;

class Human {
    // Some info
}

public class Person extends Human {
    private int age;
    private String name;

    // Some info
}

Íme a szülői osztályunk:

class com.company.Human

getInterfaces()

A következőképpen kaphatjuk meg az osztály által megvalósított interfészek listáját:

public static void main(String[] args) {
    Class<Person> personClass = Person.class;
    final Class<?>[] interfaces = personClass.getInterfaces();
    System.out.println(Arrays.toString(interfaces));
}

És ne felejtsük el megváltoztatni a Személy osztályunkat:

public class Person implements Serializable {}

Kimenet:

[interfész java.io.Serializable]

Egy osztály sok interfészt képes megvalósítani. Ezért kapunk egy tömbötOsztálytárgyakat. A Java Reflection API-ban az interfészeket az is képviseliOsztálytárgyakat.

Megjegyzés: A metódus csak a megadott osztály által megvalósított interfészeket adja vissza, a szülőosztályát nem. Az osztály által megvalósított interfészek teljes listájának megtekintéséhez hivatkoznia kell az aktuális osztályra és az öröklési láncban feljebb lévő összes ősére.

getName() & getSimpleName() & getCanonicalName()

Írjunk egy példát egy primitív, egy beágyazott osztály, egy névtelen osztály és a String osztályból:

public class TestReflection {
    public static void main(String[] args) {
        printNamesForClass(int.class, "int class (primitive)");
        printNamesForClass(String.class, "String.class (ordinary class)");
        printNamesForClass(java.util.HashMap.SimpleEntry.class,
                "java.util.HashMap.SimpleEntry.class (nested class)");
        printNamesForClass(new java.io.Serializable() {
                }.getClass(),
                "new java.io.Serializable(){}.getClass() (anonymous inner class)");
    }

    private static void printNamesForClass(final Class<?> clazz, final String label) {
        System.out.printf("%s:%n", label);
        System.out.printf("\tgetName()):\t%s%n", clazz.getName());
        System.out.printf("\tgetCanonicalName()):\t%s%n", clazz.getCanonicalName());
        System.out.printf("\tgetSimpleName()):\t%s%n", clazz.getSimpleName());
        System.out.printf("\tgetTypeName():\t%s%n%n", clazz.getTypeName());
    }
}

Programunk eredménye:

int osztály (primitív):
getName()): int
getCanonicalName()): int
getSimpleName()): int
getTypeName(): int

String.class (közönséges osztály):
getName()): java.lang.String
getCanonicalName() ): java.lang.String
getSimpleName()): String
getTypeName(): java.lang.String

java.util.HashMap.SimpleEntry.class (beágyazott osztály):
getName()): java.util.AbstractMap$SimpleEntry
getCanonicalName( )): java.util.AbstractMap.SimpleEntry
getSimpleName()): SimpleEntry
getTypeName(): java.util.AbstractMap$SimpleEntry

new java.io.Serializable(){}.getClass() (anonim belső osztály):
getName() ): TestReflection$1
getCanonicalName()): null
getSimpleName()):
getTypeName(): TestReflection$1

Most elemezzük programunk kimenetét:

  • A getName() az entitás nevét adja vissza.

  • A getCanonicalName() az alaposztály kanonikus nevét adja vissza, a Java nyelvi specifikációban meghatározottak szerint. Null értékkel tér vissza, ha az alaposztálynak nincs kanonikus neve (vagyis ha lokális vagy névtelen osztályról vagy tömbről van szó, amelynek elemtípusának nincs kanonikus neve).

  • A getSimpleName() a forráskódban megadott alaposztály egyszerű nevét adja vissza. Üres karakterláncot ad vissza, ha az alaposztály névtelen.

  • A getTypeName() egy informatív karakterláncot ad vissza ennek a típusnak a nevéhez.