Java 反射

java.lang.Class

类的加载过程:

  • javac命令生成字节码文件后,使用java命令解释运行字节码文件,使用java命令时会将类加载到内存中,这个过程就是类的加载,而加载到内存中的类称作运行时类.而这个运行时类就是一个Class实例.每一个运行时类对应了一个Class实例.

获取Class实例

package com.oylong;

/**
 * @ProjectName: test
 * @Description:
 * @Author: OyLong
 * @Date: 2020/7/14 12:02
 */
public class test {
    public static void main(String[] args) throws ClassNotFoundException {
        //方法1: 对象.class
        Class clazz1 = Person.class;
        System.out.println(clazz1);

        //方法2: 实例.getClass()
        Person person = new Person();
        Class clazz2 = person.getClass();
        System.out.println(clazz2);

        //方法3: Class.forName
        Class clazz3 = Class.forName("com.oylong.Person");
        System.out.println(clazz3);

        //方式4: 类加载器,classLoader
        ClassLoader classLoader = test.class.getClassLoader();
        Class clazz4 = classLoader.loadClass("com.oylong.Person");
        System.out.println(clazz4);

        System.out.println(clazz1 == clazz2);
        System.out.println(clazz1 == clazz3);
        System.out.println(clazz1 == clazz4);
    }
}

运行结果:

image-20200714121510489.png
通过上图中的运行结果可以看出,四种方法获得的Class对象,在内存中相同地址的同一个对象.其中第三种使用通过forName的方法获取运行时类实例的方法较为常用,在各大框架中的源码中都可看到.

可以获得Class实例的类型:

(1)class: 外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类

(2)interface:接口

(3)[]:数组

(4)enum:枚举

(5)annotation:注解@interface

(6)primitive type:基本数据类型

(7)void

通过反射创建运行时类的对象

package com.oylong;


import org.junit.Test;

/**
 * @ProjectName: test
 * @Description:
 * @Author: OyLong
 * @Date: 2020/7/14 12:02
 */

public class test {
    @Test
    public void test1() throws Exception {
        //1
        Class clazz = Person.class;

        Person person = (Person) clazz.newInstance();

        System.out.println(person);

        //2
        Class clazz1 = Class.forName("com.oylong.Person");

        Person person1 = (Person) clazz1.newInstance();

        System.out.println(person1);
    }
}

如上代码,有两种方式,一种是通过类名先获取运行时类,之后通过调用newInstance()方法去new一个新的对象,另外一种则更为通用,通过ClassforName方法获得运行时类,在通过newInstance方法来创建新的对象.

newInstanc()方法,则是通过对应的运行时类,去找到对应的无参构造器,通过这个构造器来获得一个新的对象,因此在使用这个方法时,必须保证:

  • 无参构造器存在
  • 无参构造器的在外部能访问(如不能是private)

运行结果:

image-20200714140005020.png

获取类的结构

获取成员变量的结构

public class Person {
    private String name;
    private Integer age;
    public Integer height;
...

上面为自定义的Person类,通过反射获取其成员变量:

@Test
    public void test1() throws Exception {
        Class clazz = Person.class;

        Field[] fields = clazz.getFields();
        for (Field field : fields) {
            System.out.println(field);
        }
    }

image-20200714144007102.png

可以看到,getFields()方法只能获取public修饰符声明的变量,并且通过getFields()方法还能获取到父类的public修饰的变量

获取所有的变量则需要通过getDeclaredFields(),如下:

@Test
public void test1() throws Exception {
    Class clazz = Person.class;

    Field[] fields = clazz.getDeclaredFields();
    for (Field field : fields) {
        System.out.println(field);
    }
}

image-20200714144219888.png

但需要区别的是使用getDeclaredFields()不会获取父类的成员变量

获取构造器

@Test
public void test1() throws Exception {
    Class clazz = Person.class;

    Constructor[] constructors = clazz.getConstructors();
    for (Constructor constructor : constructors) {
        System.out.println(constructor);
    }
}

image-20200714145600639.png

如上,获取了所有声明为public的构造器

    @Test
    public void test1() throws Exception {
        Class clazz = Person.class;

        Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
        for (Constructor declaredConstructor : declaredConstructors) {
            System.out.println(declaredConstructor);
        }
    }

而使用getDeclaredConstructors()则可以获取运行时类中所有的构造器.

其他结构

获取方法类似上面的方式,使用方法getMethods即可,注解使用getAnnotations即可,而获取方法的注解先获取methods再获取annotation即可.

还有更多的类似的获取结构也是如上,直接getXXX找到对应的方法即可获取.

调用运行时类的属性

@Test
public void test1() throws Exception {
    Class clazz = Person.class;

    Person person = (Person) clazz.newInstance();

    System.out.println(person);

    Field name = clazz.getDeclaredField("name");

    name.setAccessible(true);

    name.set(person, "Jack");

    System.out.println(person);
}

image-20200714153304060.png

如上代码中,首先获取运行时类,之后通过运行时类获取对应的属性,在设置访问权限,通过Fieldset方法,指定实例对象和值即可更改.

而代码中的setAccessible,是将当前操作的的变量设置为可访问.使用getDeclaredField不使用getField是因为后者不能获取非public修饰的变量.

调用运行时类指定方法

类似上面,使用getDeclaredMethod()方法,执行使用invoke()方法即可

@Test
public void test1() throws Exception {
    Class clazz = Person.class;

    Person person = (Person) clazz.newInstance();

    Method hello = clazz.getDeclaredMethod("hello");

    hello.invoke(person);
}

如果方法有参数,在getDeclaredMethod方法和invoke()方法后面追加参数即可.同时如果方法有返回值,那么直接在invoke()方法后面接受即可.

@Test
public void test1() throws Exception {
    Class clazz = Person.class;

    Person person = (Person) clazz.newInstance();

    Method hello = clazz.getDeclaredMethod("hello", String.class);
    
    hello.invoke(person, "tom");
}
如果方法或者变量是可访问的,那么就不需要使用setAccessible(true)

调用构造方法与上面相同,获取Constructor对象后,使用newInstance方法即可生成相应的对象.

Last modification:July 14, 2020
If you think my article is useful to you, please feel free to appreciate