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);
}
}
运行结果:
通过上图中的运行结果可以看出,四种方法获得的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一个新的对象,另外一种则更为通用,通过Class
的forName
方法获得运行时类,在通过newInstance
方法来创建新的对象.
而newInstanc()
方法,则是通过对应的运行时类,去找到对应的无参构造器,通过这个构造器来获得一个新的对象,因此在使用这个方法时,必须保证:
- 无参构造器存在
- 无参构造器的在外部能访问(如不能是private)
运行结果:
获取类的结构
获取成员变量的结构
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);
}
}
可以看到,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);
}
}
但需要区别的是使用getDeclaredFields()
不会获取父类的成员变量
获取构造器
@Test
public void test1() throws Exception {
Class clazz = Person.class;
Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
}
如上,获取了所有声明为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);
}
如上代码中,首先获取运行时类,之后通过运行时类获取对应的属性,在设置访问权限,通过Field
的set
方法,指定实例对象和值即可更改.
而代码中的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
方法即可生成相应的对象.