反射

141 阅读3分钟

反射的概述

Reflection(反射)是被视为动态语言的关键,反射机制允许程序在运行时借助于Reflection API获取任何类的内部信息,并能直接操作任意对象的内部属性及方法。

加载完类之后,在堆的方法区中就产生了一个Class类型的对象,一个只有一个Class对象,这个对象就包含了完整的2类的结构信息。我们可以通过对象看到类的结构,这个对象就像一面镜子,透过这个镜子看到类的内部结构,所以我们称之为反射。

通过反射调用类型中声明的相关的结构

示例:

这里声明了一个person类

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

    private Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Person() {
        System.out.println("Person实例创建。。。。。");
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
    public void show(){
        System.out.println("我是一个·Person类的方法");
    }
    private String showNation(String str){
        return "我的国籍是" + str ;
    }
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + ''' +
                ", age=" + age +
                '}';
    }
}

通过反射去调用Person类中的相关的属性和方法,修改相关属性的值【以下代码均使用throws的方式去解决异常问题】;

通过反射获取类中的属性:
@Test
public void test02() throws InstantiationException, IllegalAccessException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException {
    Class<Person> clazz = Person.class;
    Person person = clazz.newInstance();//调用了Person类中的构造器。

    System.out.println(person);
    //调用属性
    Field ageField = clazz.getField("age");
    //给age属性赋值
    ageField.set(person, 10);

    System.out.println(person.getAge());
    //另一种获取属性的方法
    System.out.println(ageField.get(person));
    //一样的效果
    //调用方法show()
    Method showMethoid = clazz.getMethod("show");
    showMethoid.invoke(person);


}
通过反射去调用类中的构造器创建实例:
@Test
public void test03() throws Exception {
    //调用私有的构造器创建对象
    Class<Person> personClass = Person.class;
    Constructor<Person> declaredConstructor = personClass.getDeclaredConstructor(String.class, int.class);
    declaredConstructor.setAccessible(true);//设置为可以访问
    Person tom = declaredConstructor.newInstance("yom", 12);

    System.out.println(tom);
    /**
     *访问私有的属性
     */
    Field declaredField = personClass.getDeclaredField("name");
    declaredField.setAccessible(true);
    declaredField.set(tom, "Tom");
    System.out.println(tom.getClass());
    System.out.println(tom);
    System.out.println(declaredField.get(tom));
    //调用私有化的方法
    Method showNation = personClass.getDeclaredMethod("showNation", String.class);
    showNation.setAccessible(true);
    Object chn = showNation.invoke(tom, "CHN");
    System.out.println(chn);

}

注意:通过反射来获取类中的构造器有两种方法,一个是getConstructor,另一个是getDeclaredConstructure 二者的区别是前者只能获取权限足够大的构造器,后者则是可以获取全部的已经声明在类中的构造器。不过在获取私有的构造器时会出无法访问的异常,这时只需要调用setAccessible方法去设置为可以访问即可。

通过反射获取并调用类中声明的方法:

@Test
public void test03() throws NoSuchFieldException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
    //调用指定的方法
    Class<Person> personClass = Person.class;
    Person person = personClass.newInstance();

    Method showNationMethod = personClass.getDeclaredMethod("showNation", String.class);
    showNationMethod.setAccessible(true);
    String s = (String) showNationMethod.invoke(person, "CHN");
    System.out.println(s);


}

创建Class实例的四种途径:

@Test
public void test04() throws ClassNotFoundException {
    //创建Class的四种方式
    //第一种调用运行时类的静态属性
    Class<Person> personClass = Person.class;
    //第二种调用运行时类的对象的getclass方法
    Person p1 = new Person();
    Class<? extends Person> aClass = p1.getClass();
    //比较二者的地址、
    System.out.println(aClass == personClass);//true
    //调用Class的静态方法froName(String className)
    String className = "ReflectTest.Person";
    Class<?> aClass1 = Class.forName(className);
    //方法四,使用类的加载器
    Class clazz = ClassLoader.getSystemClassLoader().loadClass(className);
    System.out.println(personClass == clazz);//true

}

反射的优点:

提高了java程序的灵活性与扩展性,降低了耦合性,提升了自适应能力

允许程序创建和控制任何类的对象,无需提前硬编码目标类

对CLass类的理解

针对于编写好的.java源文件进行编译,会生成一个或者多个.class文件。接着,我们使用java.exe命令对指定的.class文件进行解释运行,这个解释运行的过程中,我们需要将.calss字节码文件加载到内存中,加载到内存中的.calss文件对应的结构即为CLass的一个实例。

理解:CLass可以看作反射的源头。

反射的应用:

image.png