Para saan ang Reflection API?

Ang mekanismo ng reflection ng Java ay nagbibigay-daan sa isang developer na gumawa ng mga pagbabago at makakuha ng impormasyon tungkol sa mga klase, interface, field, at pamamaraan sa runtime nang hindi nalalaman ang kanilang mga pangalan.

Hinahayaan ka rin ng Reflection API na lumikha ng mga bagong bagay, paraan ng pagtawag, at makakuha o magtakda ng mga value ng field.

Gumawa tayo ng isang listahan ng lahat ng magagawa mo gamit ang reflection:

  • Kilalanin / tukuyin ang klase ng isang bagay
  • Kumuha ng impormasyon tungkol sa mga modifier ng klase, field, pamamaraan, constant, constructor, at superclass
  • Alamin kung aling mga pamamaraan ang nabibilang sa (mga) ipinatupad na interface
  • Lumikha ng isang instance ng isang klase na ang pangalan ng klase ay hindi kilala hanggang sa ang programa ay naisakatuparan
  • Kunin at itakda ang halaga ng field ng instance ayon sa pangalan
  • Tumawag ng paraan ng instance ayon sa pangalan

Halos lahat ng modernong teknolohiya ng Java ay gumagamit ng repleksyon. Pinagbabatayan nito ang karamihan sa mga Java / Java EE frameworks at library ngayon, halimbawa:

  • Spring frameworks para sa pagbuo ng mga web application
  • ang balangkas ng pagsubok ng JUnit

Kung hindi mo pa naranasan ang mga mekanismong ito, malamang na nagtatanong ka kung bakit kailangan ang lahat ng ito. Ang sagot ay medyo simple ngunit masyadong malabo: ang pagmuni-muni ay kapansin-pansing nagpapataas ng flexibility at ang kakayahang i-customize ang aming application at code.

Ngunit palaging may mga kalamangan at kahinaan. Kaya banggitin natin ang ilang mga kahinaan:

  • Mga paglabag sa seguridad ng aplikasyon. Hinahayaan kami ng Reflection na ma-access ang code na hindi namin dapat (paglabag sa encapsulation).
  • Mga paghihigpit sa seguridad. Nangangailangan ang Reflection ng mga pahintulot sa runtime na hindi available sa mga system na nagpapatakbo ng security manager.
  • Mababang pagganap. Ang Reflection sa Java ay dynamic na tumutukoy sa mga uri sa pamamagitan ng pag-scan sa classpath upang mahanap ang klase na ilo-load. Binabawasan nito ang pagganap ng programa.
  • Mahirap i-maintain. Mahirap basahin at i-debug ang code na gumagamit ng reflection. Ito ay hindi gaanong nababaluktot at mas mahirap mapanatili.

Paggawa sa mga klase gamit ang Reflection API

Ang lahat ng mga pagpapatakbo ng pagmuni-muni ay nagsisimula sa isang bagay na java.lang.Class . Para sa bawat uri ng object, isang hindi nababagong instance ng java.lang.Class ay nalikha. Nagbibigay ito ng mga pamamaraan para sa pagkuha ng mga katangian ng bagay, paglikha ng mga bagong bagay, at mga paraan ng pagtawag.

Tingnan natin ang listahan ng mga pangunahing pamamaraan para sa pagtatrabaho sa java.lang.Class :

Pamamaraan Aksyon
String getName(); Ibinabalik ang pangalan ng klase
int getModifiers(); Ibinabalik ang mga modifier ng access
Package getPackage(); Ibinabalik ang impormasyon tungkol sa isang pakete
Class getSuperclass(); Nagbabalik ng impormasyon tungkol sa isang klase ng magulang
Class[] getInterfaces(); Nagbabalik ng hanay ng mga interface
Constructor[] getConstructors(); Nagbabalik ng impormasyon tungkol sa mga tagabuo ng klase
Fields[] getFields(); Ibinabalik ang mga field ng isang klase
Field getField(String fieldName); Ibinabalik ang isang partikular na field ng isang klase ayon sa pangalan
Paraan[] getMethods(); Nagbabalik ng hanay ng mga pamamaraan

Ito ang pinakamahalagang pamamaraan para sa pagkuha ng data tungkol sa mga klase, interface, field, at pamamaraan. Mayroon ding mga pamamaraan na nagbibigay-daan sa iyong makakuha o magtakda ng mga halaga ng field, at ma-access ang mga pribadong field. Titingnan natin sila mamaya.

Sa ngayon ay pag-uusapan natin ang tungkol sa pagkuha ng java.lang.Class mismo. Mayroon kaming tatlong paraan upang gawin ito.

1. Paggamit ng Class.forName

Sa isang tumatakbong application, dapat mong gamitin ang paraang forName(String className) para makakuha ng klase.

Ipinapakita ng code na ito kung paano tayo makakagawa ng mga klase gamit ang reflection. Gumawa tayo ng klase ng Tao na maaari nating gamitin:

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

At ang pangalawang bahagi ng aming halimbawa ay ang code na gumagamit ng pagmuni-muni:

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

Posible ang diskarteng ito kung alam ang buong pangalan ng klase. Pagkatapos ay maaari mong makuha ang kaukulang klase gamit ang static na Class.forName() na pamamaraan. Ang pamamaraang ito ay hindi maaaring gamitin para sa mga primitive na uri.

2. Gamit ang .class

Kung available ang isang uri ngunit walang instance nito, maaari mong makuha ang klase sa pamamagitan ng pagdaragdag ng .class sa pangalan ng uri. Ito ang pinakamadaling paraan upang makuha ang klase ng isang primitive na uri.

Class aClass = Person.class;

3. Paggamit ng .getClass()

Kung ang isang bagay ay magagamit, kung gayon ang pinakamadaling paraan upang makakuha ng isang klase ay ang tumawag sa object.getClass() .

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

Ano ang pagkakaiba sa pagitan ng huling dalawang diskarte?

Gamitin ang A.class kung alam mo kung aling class object ang interesado ka sa oras ng coding. Kung walang available na instance, dapat mong gamitin ang .class .

Pagkuha ng mga pamamaraan ng isang klase

Tingnan natin ang mga pamamaraan na nagbabalik ng mga pamamaraan ng aming klase: getDeclaredMethods() at getMethods() .

getDeclaredMethods() ay nagbabalik ng array na naglalaman ng Method object para sa lahat ng ipinahayag na pamamaraan ng klase o interface na kinakatawan ng Class object, kabilang ang pampubliko, pribado, default, at protektadong mga pamamaraan, ngunit hindi minanang mga pamamaraan.

Ang getMethods() ay nagbabalik ng array na naglalaman ng Method object para sa lahat ng pampublikong pamamaraan ng klase o interface na kinakatawan ng Class object — ang mga idineklara ng klase o interface, pati na rin ang mga minana mula sa mga superclass at superinterface.

Tingnan natin kung paano gumagana ang bawat isa sa kanila.

Magsimula tayo sa getDeclaredMethods() . Upang muling matulungan kaming maunawaan ang pagkakaiba sa pagitan ng dalawang pamamaraan, sa ibaba ay gagana kami sa abstract na klase ng Mga Numero . Sumulat tayo ng isang static na pamamaraan na magko-convert sa aming Method array sa 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());
    }
}

Narito ang resulta ng pagpapatakbo ng code na ito:

byteValue
shortValue
intValue
longValue
float floatValue;
doubleValue

Ito ang mga pamamaraan na idineklara sa loob ng klase ng Numero . Ano ang ibinabalik ng getMethods() ? Baguhin natin ang dalawang linya sa halimbawa:

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

Sa paggawa nito, makikita natin ang sumusunod na hanay ng mga pamamaraan:

byteValue
shortValue
intValue
longValue
float floatValue;
doubleValue
wait
wait
wait
katumbas
ngString
hashCode
getClass
notify
notifyAll

Dahil lahat ng klase ay nagmamana ng Object , ibinabalik din ng aming pamamaraan ang mga pampublikong pamamaraan ng klase ng Object .

Pagkuha ng mga patlang ng isang klase

Ang mga pamamaraan ng getFields at getDeclaredFields ay ginagamit upang makuha ang mga field ng isang klase. Bilang halimbawa, tingnan natin ang klase ng LocalDateTime . Muli naming isusulat ang aming code:

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

Bilang resulta ng pagpapatupad ng code na ito, nakukuha namin ang hanay ng mga patlang na nasa klase ng LocalDateTime.

MIN
MAX
serialVersionUID
date
time

Sa pamamagitan ng pagkakatulad sa aming nakaraang pagsusuri ng mga pamamaraan, tingnan natin kung ano ang mangyayari kung babaguhin natin nang kaunti ang code:

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

Output:

MIN
MAX

Ngayon, alamin natin ang pagkakaiba sa pagitan ng mga pamamaraang ito.

Ang pamamaraang getDeclaredFields ay nagbabalik ng hanay ng mga bagay sa Field para sa lahat ng mga patlang na idineklara ng klase o interface na kinakatawan nito.Klasebagay.

Ang getFields method ay nagbabalik ng hanay ng mga bagay sa Field para sa lahat ng pampublikong field ng klase o interface na kinakatawan ngKlasebagay.

Ngayon tingnan natin ang loob ng LocalDateTime .

Ang klaseMINatMAXpampubliko ang mga field, na nangangahulugang makikita ang mga ito sa pamamagitan ng getFields method. Sa kabaligtaran, angpetsa,oras,serialVersionUIDAng mga pamamaraan ay may pribadong modifier, na nangangahulugang hindi sila makikita sa pamamagitan ng pamamaraang getFields , ngunit maaari nating makuha ang mga ito gamit ang getDeclaredFields . Ito ay kung paano namin maa-access ang mga bagay sa Field para sa mga pribadong field.

Mga paglalarawan ng iba pang mga pamamaraan

Ngayon ay oras na upang pag-usapan ang ilang mga pamamaraan ng klase ng Klase , katulad:

Pamamaraan Aksyon
getModifiers Pagkuha ng mga modifier para sa aming klase
getPackage Pagkuha ng package na naglalaman ng aming klase
getSuperclass Pagkuha ng klase ng magulang
getInterfaces Pagkuha ng hanay ng mga interface na ipinatupad ng isang klase
getName Pagkuha ng ganap na kwalipikadong pangalan ng klase
getSimpleName Pagkuha ng pangalan ng isang klase

getModifiers()

Maaaring ma-access ang mga modifier gamit ang aKlasebagay.

Ang mga modifier ay mga keyword tulad ng public , static , interface , atbp. Kumuha kami ng mga modifier gamit ang getModifiers() method:

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

Itinatakda ng code na ito ang halaga ng isangintvariable na medyo field. Ang bawat access modifier ay maaaring i-on o i-off sa pamamagitan ng pagtatakda o pag-clear sa kaukulang bit. Maaari naming suriin ang mga modifier gamit ang mga pamamaraan sa klase ng 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);
    }
}

Alalahanin kung ano ang hitsura ng deklarasyon ng ating Tao :

public class Person {}

Nakukuha namin ang sumusunod na output:

Mga modifier ng klase: 1
Ay pampubliko: true
Ay static: false
Ay final: false
Ay abstract: false
Ay interface: false

Kung gagawin nating abstract ang ating klase, mayroon tayong:

public abstract class Person {}

at ang output na ito:

Mga modifier ng klase: 1025
Ay pampubliko: true
Ay static: false
Ay final: false
Ay abstract: true
Ay interface: false

Binago namin ang access modifier, na nangangahulugang binago din namin ang data na ibinalik sa pamamagitan ng mga static na pamamaraan ng klase ng Modifier .

getPackage()

Ang pag-alam lamang ng isang klase, makakakuha tayo ng impormasyon tungkol sa package nito:

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

getSuperclass()

Kung mayroon tayong Class object, maaari nating ma-access ang parent class nito:

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

Nakukuha namin ang kilalang klase ng Bagay :

class java.lang.Object

Ngunit kung ang aming klase ay may ibang klase ng magulang, sa halip ay makikita namin ito:

package com.company;

class Human {
    // Some info
}

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

    // Some info
}

Narito ang aming klase ng magulang:

class com.company.Human

getInterfaces()

Narito kung paano natin makukuha ang listahan ng mga interface na ipinapatupad ng klase:

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

At huwag nating kalimutang baguhin ang klase ng Person :

public class Person implements Serializable {}

Output:

[interface java.io.Serializable]

Ang isang klase ay maaaring magpatupad ng maraming mga interface. Iyon ang dahilan kung bakit nakakakuha kami ng isang array ngKlasemga bagay. Sa Java Reflection API, ang mga interface ay kinakatawan din ngKlasemga bagay.

Pakitandaan: Ibinabalik lamang ng pamamaraan ang mga interface na ipinatupad ng tinukoy na klase, hindi ang parent class nito. Upang makakuha ng kumpletong listahan ng mga interface na ipinatupad ng klase, kailangan mong sumangguni sa parehong kasalukuyang klase at sa lahat ng mga ninuno nito sa chain ng mana.

getName() at getSimpleName() at getCanonicalName()

Sumulat tayo ng halimbawang kinasasangkutan ng primitive, nested class, anonymous na klase, at String class:

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

Resulta ng aming programa:

int class (primitive):
getName()): int
getCanonicalName()): int
getSimpleName()): int
getTypeName(): int

String.class (ordinaryong klase):
getName()): java.lang.String
getCanonicalName() ): java.lang.String
getSimpleName()): String
getTypeName(): java.lang.String

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

new java.io.Serializable(){}.getClass() (anonymous inner class):
getName() ): TestReflection$1
getCanonicalName()): null
getSimpleName()):
getTypeName(): TestReflection$1

Ngayon suriin natin ang output ng aming programa:

  • getName() ay nagbabalik ng pangalan ng entity.

  • Ibinabalik ng getCanonicalName() ang canonical na pangalan ng base class, gaya ng tinukoy ng Java Language Specification. Ibinabalik ang null kung ang batayang klase ay walang canonical na pangalan (iyon ay, kung ito ay isang lokal o anonymous na klase o isang array na ang uri ng elemento ay walang canonical na pangalan).

  • getSimpleName() ay nagbabalik ng simpleng pangalan ng base class gaya ng tinukoy sa source code. Nagbabalik ng walang laman na string kung ang batayang klase ay hindi nagpapakilala.

  • getTypeName() ay nagbabalik ng isang nagbibigay-kaalaman na string para sa pangalan ng ganitong uri.