欢迎访问:我的个人网站
Java 反射
在正常编写Java程序的过程中,我们是知道某个类具有什么样的功能,什么样的调用形式。在这种前提下,我们创建实例并进行相关方法的调用。这种在编译前就已经确认类型的编译方式成为静态编译。反射指的是程序可以在运行期间访问,检测,修改它本身的状态的一种行为,并根据自身行为状态和结果,调整或修改应用所描述行为的状态和相关的语义。换句话说,通过反射Java程序可以加载一个运行时候才能得知名字的类并对其进行操作。
反射是Java中一种非常强大的工具,能够创建更加灵活的代码,这些代码可以再运行时装配,无需在组件之间进行源代码链接,因此这种这种反射方式也被成为动态编译。
反射提供的功能
在运行时的情况下:
- 判断一个对象所属的类。
- 判断一个类所具有的成员变量以及方法。
- 构造一个类的对象。
- 调用任意一个对象的方法。
- 更改任意一个对象的变量。
- 生成动态代理。
第一个简单的例子
使用下面的Person类进行反射操作:
public class Person {
private Integer id;
protected String name;
public Integer score;
private int fun(){
System.out.println("私有方法");
return 1;
}
public Person(){}
private Person(Integer val){}
protected Person(Boolean val){ }
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Integer getScore() { return score; }
public void setScore(Integer score) { this.score = score; }
}
获取Person类中的所有方法:
Class clazz = Person.class;
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method method : declaredMethods) {
System.out.println(method.toGenericString());
}
输出结果:
public java.lang.String com.bestbigkk.Person.getName()
public void com.bestbigkk.Person.setName(java.lang.String)
public java.lang.Integer com.bestbigkk.Person.getId()
public java.lang.Integer com.bestbigkk.Person.getScore()
public void com.bestbigkk.Person.setScore(java.lang.Integer)
public void com.bestbigkk.Person.setId(java.lang.Integer)
private int com.bestbigkk.Person.fun()
反射可以获取这个类中的字节码信息,包括:类修饰符(public,static等) , 基类,实现的接口,字段,方法 。 并根据获取到的信息来实例化该类的对象,并操作实例对象。Class类是Java反射中的一个核心类,代表了内存中的一个Java类,反射获取的信息都是通过Class对象获取到的。一个Class对象包含以下的几个部分:
- Constructor:描述一个类的构造方法。
- Field:描述一个类的成员变量。
- Method:描述一个类的方法。
- Modifier:描述修饰符。
- Array:对数组进行操作。
对一个类进行反射操作,首先需要加载这个被操作的类,提供了三种加载类的方式:
- 使用类的路径进行加载:
- Class clazz = Class.forName(“类路径”);
- 使用关键字进行加载:
- Class clazz = new ClassName().getClass();
- 直接进行加载:
- Class clazz = ClassName.class;
在正常加载一个类之后,就可以尝试获取类中的信息了:
所有方法可以通过其名称了解其含义,需要注意的是,部分方法为getDeclaedXXX形式表示可以获取私有的XXX。比如下面的获取构造函数示例,后续将不再进行说明:
获取构造函数
//获取类所在的包
Package getPackage();
//获取类的名称
String getName();
//仅获取类public的构造参数
Constructor<?>[] getConstructors();
//获取类的全部构造参数,包含私有的构造函数
Constructor<?>[] getDeclaedConstructors();
//获取类的构造参数,该构造函数具有具有指定的参数类型
Constructor<?> getDeclaredConstructor(Class<?>...params);
//获取本地或者匿名类的构造函数。
Constructor<?> getEnclosingConstructor();
在获取一个类的构造函数对象之后,可以对其进行相关操作,包含:
- 获取其参数。
- 获取其参数个数。
- 获取其参数类型。
- 获取其参数注解。
- 获取其名称。
- 获取其修饰符。
- 获取其抛出的异常。
- 等等等等…
获取变量
//获取
Filed getField(String name); //获取类或接口的指定公共成员字段
Field getDeclaredField(String name); //获取指定接口或类的成员字段(所有修饰符修饰均可获取);
Fields[] getFieled(); //获取类或接口的全部字段
Fields[] getDeclaredField(); //获取类或接口所有的字段
//操作, 这里由于大部分方法都是一样的形式,所以使用XXX代替了具体的类型。Object指的是为哪个对象设置属性。
XXX getXXX(Object obj); //获取某个字段的值,该字段的类型为XXX(Boolean, Byte, Char...);
void setXXX(Object obj, XXX x);//设置某个字段的值,该字段的类型为XXX, 需要传递一个同类型的参数以设置。
获取方法
Method getMethod(String name, Class<?> ...paramsType);//获取一个指定名称的方法,并输入与该方法匹配的参数类型。
Method getDeclaredMethod(String name, Class<?>...paramsType);//同上,但是可以获取全部的方法,包括私有类型的。
Method[] getMethods();//获取所有公开的方法
Method[] getDeclaredMethods();//获取所有方法,包括私有。
Method getEnclosingMethod();//获取本地或者匿名类的方法。
在获取方法之后,同样提供了一些操作:
- 取得参数类型。
- 取得返回类型。
- 执行方法。
- 等等等等…
获取修饰符
针对Method类,Class类, Parameter类均提供了获取修饰符的方法:
int getMoodifier();
返回的值代表了一种修饰符,在 Modifier类里面对其含义进行了全面的定义,内部同时也定义了一些方法以判断当前返回值的含义,比较好理解,下面仅列举了部分:
反射操作
注意在操作过程中,如果获取到了私有的构造函数,字段,方法,是无法直接对其操作的。需要设置可访问性:
通过反射创建对象
setAccessible(Boolean access);
public class Person {
private String name;
private int age;
public Person(){
System.out.println("默认构造方法");
}
private Person(Integer val){
this.age = val;
System.out.println("私有构造方法,传递一个int值:"+val);
}
protected Person(String str){
this.name = str;
System.out.println("受保护的构造方法,传递一个字符串:"+str);
}
}
下面描述了实例化一个Person对象的过程:
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class clazz = Person.class;
//获取构造函数,该函数具有一个String类型的参数
Constructor constructor = clazz.getDeclaredConstructor(String.class);
//实例化该对象,并为其传递它所需类型的参数
Person p = (Person) constructor.newInstance("Test");
}
###通过反射修改属性的值:
依旧使用上面的Person类进行操作:
public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class clazz = Person.class;
//获取构造函数并实例化一个对象
Constructor constructor = clazz.getDeclaredConstructor(Integer.class);
constructor.setAccessible(true);
Person person = (Person) constructor.newInstance(21);
//获取name字段
Field field = clazz.getField("name");
field.set(person, "XGK");
//由于是设置给person对象,可以直接访问
System.out.println(person.name);
//也可以直接通过Filed类,获取person对象的name值
System.out.println(field.get(person));
}
通过反射调用方法
在上面Person的基础上,再为其添加两个方法:
public Person(String name, Integer age){
this.name = name;
this.age = age;
}
private void sayInfo(){
System.out.println("你好,我是"+name+", 今年"+age+"岁");
}
通过刚添加的方法演示:
Class clazz = Person.class;
//获取构造函数并实例化一个对象
Constructor constructor = clazz.getDeclaredConstructor(String.class, Integer.class);
constructor.setAccessible(true);
Person person = (Person) constructor.newInstance("小感触" , 21);
//获取方法
Method method = clazz.getDeclaredMethod("sayInfo");
method.setAccessible(true);
method.invoke(person);