Hvad er Reflection API til?

Javas refleksionsmekanisme giver en udvikler mulighed for at foretage ændringer og få information om klasser, grænseflader, felter og metoder under kørsel uden at kende deres navne.

Reflection API lader dig også oprette nye objekter, kalde metoder og hente eller indstille feltværdier.

Lad os lave en liste over alt, hvad du kan gøre ved hjælp af refleksion:

  • Identificer/bestem klassen af ​​et objekt
  • Få information om klassemodifikatorer, felter, metoder, konstanter, konstruktører og superklasser
  • Find ud af, hvilke metoder der hører til implementerede grænseflader
  • Opret en forekomst af en klasse, hvis klassenavn ikke kendes, før programmet er afviklet
  • Hent og indstil værdien af ​​et forekomstfelt efter navn
  • Kald en instansmetode ved navn

Næsten alle moderne Java-teknologier bruger refleksion. Det ligger til grund for de fleste af nutidens Java / Java EE frameworks og biblioteker, for eksempel:

  • Forårsrammer til opbygning af webapplikationer
  • JUnits testramme _

Hvis du aldrig har stødt på disse mekanismer før, spørger du sikkert, hvorfor alt dette er nødvendigt. Svaret er ganske enkelt, men også meget vagt: Refleksion øger dramatisk fleksibiliteten og muligheden for at tilpasse vores applikation og kode.

Men der er altid fordele og ulemper. Så lad os nævne et par ulemper:

  • Overtrædelser af applikationssikkerhed. Refleksion giver os adgang til kode, som vi ikke bør (overtrædelse af indkapsling).
  • Sikkerhedsbegrænsninger. Refleksion kræver runtime-tilladelser, som ikke er tilgængelige for systemer, der kører en sikkerhedsmanager.
  • Lav ydeevne. Refleksion i Java bestemmer typer dynamisk ved at scanne klassestien for at finde den klasse, der skal indlæses. Dette reducerer programmets ydeevne.
  • Svært at vedligeholde. Kode, der bruger refleksion, er svær at læse og fejlfinde. Det er mindre fleksibelt og sværere at vedligeholde.

Arbejde med klasser ved hjælp af Reflection API

Alle reflektionsoperationer begynder med et java.lang.Class- objekt. For hver type objekt oprettes en uforanderlig forekomst af java.lang.Class . Det giver metoder til at få objektegenskaber, oprette nye objekter og kalde metoder.

Lad os se på listen over grundlæggende metoder til at arbejde med java.lang.Class :

Metode Handling
String getName(); Returnerer navnet på klassen
int getModifiers(); Returnerer adgangsmodifikatorer
Pakke getPackage(); Returnerer oplysninger om en pakke
Klasse getSuperklasse(); Returnerer oplysninger om en overordnet klasse
Klasse[] getInterfaces(); Returnerer en række grænseflader
Konstruktør[] getConstructors(); Returnerer oplysninger om klassekonstruktører
Felter[] getFields(); Returnerer felterne i en klasse
Felt getField(String fieldName); Returnerer et specifikt felt i en klasse efter navn
Metode[] getMethods(); Returnerer en række metoder

Dette er de vigtigste metoder til at få data om klasser, grænseflader, felter og metoder. Der er også metoder, der giver dig mulighed for at få eller indstille feltværdier og få adgang til private felter. Vi ser på dem lidt senere.

Lige nu vil vi tale om at få selve java.lang.Class . Vi har tre måder at gøre dette på.

1. Brug af Class.forName

I et kørende program skal du bruge metoden forName(String className) for at få en klasse.

Denne kode viser, hvordan vi kan oprette klasser ved hjælp af refleksion. Lad os oprette en personklasse , som vi kan arbejde med:

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;
    }
}

Og den anden del af vores eksempel er koden, der bruger refleksion:

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

Denne tilgang er mulig, hvis klassens fulde navn er kendt. Så kan du få den tilsvarende klasse ved at bruge den statiske Class.forName() metode. Denne metode kan ikke bruges til primitive typer.

2. Brug af .class

Hvis en type er tilgængelig, men der ikke er nogen forekomst af den, kan du få klassen ved at tilføje .class til typenavnet. Dette er den nemmeste måde at få klassen af ​​en primitiv type.

Class aClass = Person.class;

3. Brug af .getClass()

Hvis et objekt er tilgængeligt, så er den nemmeste måde at få en klasse på at kalde object.getClass() .

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

Hvad er forskellen mellem de to sidste tilgange?

Brug A.class hvis du ved hvilket klasseobjekt du er interesseret i på kodningstidspunktet. Hvis ingen instans er tilgængelig, skal du bruge .class .

Få en klasses metoder

Lad os se på de metoder, der returnerer metoderne i vores klasse: getDeclaredMethods() og getMethods() .

getDeclaredMethods() returnerer et array, der indeholder metodeobjekter for alle erklærede metoder i klassen eller grænsefladen repræsenteret af klasseobjektet, inklusive offentlige, private, standard- og beskyttede metoder, men ikke nedarvede metoder.

getMethods() returnerer et array, der indeholder Method- objekter for alle offentlige metoder i klassen eller grænsefladen repræsenteret af Class-objektet - dem, der er erklæret af klassen eller grænsefladen, såvel som dem, der er nedarvet fra superklasser og supergrænseflader.

Lad os tage et kig på, hvordan hver af dem fungerer.

Lad os starte med getDeclaredMethods() . For igen at hjælpe os med at forstå forskellen mellem de to metoder, vil vi nedenfor arbejde med den abstrakte Numbers- klasse. Lad os skrive en statisk metode, der vil konvertere vores Metode- array til List<String> :

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());
    }
}

Her er resultatet af at køre denne kode:

byteValue
shortValue
intValue
longValue
float floatValue;
dobbeltværdi

Disse er metoderne erklæret i Number- klassen. Hvad returnerer getMethods() ? Lad os ændre to linjer i eksemplet:

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

Når vi gør dette, vil vi se følgende sæt metoder:

byteValue
shortValue
intValue
longValue
float floatValue;
doubleValue
vent
vent
vent
er lig
medString
hashCode
getClass
notify
notifyAll

Fordi alle klasser arver Object , returnerer vores metode også de offentlige metoder for Object- klassen.

Hent felterne i en klasse

Metoderne getFields og getDeclaredFields bruges til at hente felterne i en klasse. Lad os som et eksempel se på klassen LocalDateTime . Vi omskriver vores kode:

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());
    }
}

Som et resultat af at udføre denne kode får vi det sæt af felter, der er indeholdt i LocalDateTime-klassen.

MIN
MAX
serialVersionUID
dato
tid

I analogi med vores tidligere undersøgelse af metoder, lad os se, hvad der sker, hvis vi ændrer koden lidt:

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

Produktion:

MIN
MAKS

Lad os nu finde ud af forskellen mellem disse metoder.

GetDeclaredFields - metoden returnerer en matrix af feltobjekter for alle felter erklæret af klassen eller grænsefladen repræsenteret af denneklasseobjekt.

GetFields - metoden returnerer en matrix af feltobjekter for alle offentlige felter i klassen eller grænsefladen repræsenteret afklasseobjekt.

Lad os nu se inde i LocalDateTime .

KlassensMINogMAKSfelter er offentlige, hvilket betyder, at de vil være synlige gennem getFields -metoden. Derimoddato,tid,serialVersionUIDmetoder har den private modifikator, hvilket betyder, at de ikke vil være synlige gennem getFields -metoden, men vi kan få dem ved hjælp af getDeclaredFields . Sådan kan vi få adgang til feltobjekter til private felter.

Beskrivelser af andre metoder

Nu er det tid til at tale om nogle metoder i klasseklassen , nemlig:

Metode Handling
getModifiers Henter modifikatorerne til vores klasse
getPackage Henter pakken, der indeholder vores klasse
få Superklasse Få forældreklassen
getInterfaces At få en række grænseflader implementeret af en klasse
fåNavn At få det fuldt kvalificerede klassenavn
getSimpleName Hent navnet på en klasse

getModifiers()

Modifikatorer kan tilgås ved hjælp af enklasseobjekt.

Modifikatorer er nøgleord som public , static , interface , osv. Vi får modifikatorer ved hjælp af getModifiers() metoden:

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

Denne kode angiver værdien af ​​enintvariabel, der er et bitfelt. Hver adgangsmodifikator kan slås til eller fra ved at indstille eller slette den tilsvarende bit. Vi kan kontrollere modifikatorer ved hjælp af metoderne i klassen java.lang.reflect.Modifier :

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);
    }
}

Husk, hvordan vores persons erklæring ser ud:

public class Person {}

Vi får følgende output:

Klassemodifikatorer: 1
Er offentlig: sand
Er statisk: falsk
Er endelig: falsk
Er abstrakt: falsk
Er grænseflade: falsk

Hvis vi gør vores klasse abstrakt, så har vi:

public abstract class Person {}

og dette output:

Klassemodifikatorer: 1025
Er offentlig: sand
Er statisk: falsk
Er endelig: falsk
Er abstrakt: sand
Er grænseflade: falsk

Vi ændrede adgangsmodifikatoren, hvilket betyder, at vi også ændrede de data, der returneres gennem de statiske metoder i Modifier- klassen.

getPackage()

Når vi kun kender en klasse, kan vi få oplysninger om dens pakke:

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

getSuperclass()

Hvis vi har et klasseobjekt, så kan vi få adgang til dets overordnede klasse:

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

Vi får den velkendte Objektklasse :

class java.lang.Object

Men hvis vores klasse har en anden forældreklasse, så ser vi den i stedet:

package com.company;

class Human {
    // Some info
}

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

    // Some info
}

Her får vi vores forældreklasse:

class com.company.Human

getInterfaces()

Sådan kan vi få listen over grænseflader implementeret af klassen:

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

Og lad os ikke glemme at ændre vores personklasse :

public class Person implements Serializable {}

Produktion:

[interface java.io.Serialiserbar]

En klasse kan implementere mange grænseflader. Derfor får vi en række afklassegenstande. I Java Reflection API er grænseflader også repræsenteret afklassegenstande.

Bemærk venligst: Metoden returnerer kun de grænseflader, der er implementeret af den angivne klasse, ikke dens overordnede klasse. For at få en komplet liste over grænseflader implementeret af klassen, skal du henvise til både den nuværende klasse og alle dens forfædre op i arvekæden.

getName() & getSimpleName() & getCanonicalName()

Lad os skrive et eksempel, der involverer en primitiv, en indlejret klasse, en anonym klasse og klassen String :

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());
    }
}

Resultatet af vores program:

int klasse (primitiv):
getName()): int
getCanonicalName()): int
getSimpleName()): int
getTypeName(): int

String.class (almindelig klasse):
getName()): java.lang.String
getCanonicalName() ): java.lang.String
getSimpleName()): String
getTypeName(): java.lang.String

java.util.HashMap.SimpleEntry.class (indlejret klasse):
getName()): java.util.AbstractMap$SimpleEntry
getCanonicalName( )): java.util.AbstractMap.SimpleEntry
getSimpleName()): SimpleEntry
getTypeName(): java.util.AbstractMap$SimpleEntry

new java.io.Serializable(){}.getClass() (anonym indre klasse):
getName() ): TestReflection$1
getCanonicalName()): null
getSimpleName()):
getTypeName(): TestReflection$1

Lad os nu analysere vores programs output:

  • getName() returnerer navnet på enheden.

  • getCanonicalName() returnerer det kanoniske navn på basisklassen, som defineret af Java Language Specification. Returnerer null, hvis basisklassen ikke har et kanonisk navn (det vil sige, hvis det er en lokal eller anonym klasse eller et array, hvis elementtype ikke har et kanonisk navn).

  • getSimpleName() returnerer det simple navn på basisklassen som angivet i kildekoden. Returnerer en tom streng, hvis basisklassen er anonym.

  • getTypeName() returnerer en informativ streng for navnet på denne type.