目录
前言
Java反射机制是Java语言的一个高级特性,它的应用特别广泛,尤其是开源框架,例如mybatis、spring等都用到了反射技术。本文将简单介绍下反射的使用。
1、反射机制概述
1.1 反射概念
Java的反射机制是指在程序运行过程中,对于任意一个类,都可以知道此类中的所有属性和方法;对于任意一个对象,都可以调用它的任意一个方法和属性(包括私有方法和属性);对于这种动态获取类属性和方法以及动态调用对象的属性和方法的功能我们称为Java的反射机制。
1.2 反射相关类
Java中主要通过以下四个类来实现反射功能,这些类都在java.lang.reflect包中
Class类:代表一个类,类表示正在运行的Java应用程序中的类和接口
Constructor类:代表类的构造方法。
Method类:代表类的方法。
Field类:代表类的属性。
2、反射相关类简介
2.1 Class类
调用此类中的方法可以获取相应的Class对象,通过此Class对象,我们可以操作此类。实际上,在JVM加载调用类加载器加载Class时,会给每个Class生成一个Class对象,此对象就存储类的相关信息。获取Class对象的方式有三种,分别为
- 调用类的getClass()方法
- 获取类的class静态属性
- 调用Class类的forName()方法
示例代码如下:
public static void testCreateClassObj() {
Student stu = new Student(22, "张三");
// 1、调用类的getClass()方法
Class<?> stuClassOne = stu.getClass();
// 2、获取类的class属性
Class<?> stuClassTwo = Student.class;
try {
// 3、调用Class的forName()方法
// 此种方式可能会抛出异常,因为传入的类可能不存在,参数是类的全包名
Class<?> stuClassThree = Class.forName("com.base.reflect.Student");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
// 后面的示例中也会用到此类,此类中的方法主要用于示例
public class Student {
public String studentNO = "19101";
protected int age = 20;
private String studentName;
public Student() {}
public Student(int age, String studentName) {
this.age = age;
this.studentName = studentName;
}
private Student(int age) {
this.age = age;
}
public String getStudentNO() {
return studentNO;
}
public void setStudentNO(String studentNO) {
this.studentNO = studentNO;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getStudentName() {
return studentName;
}
public void setStudentName(String studentName) {
this.studentName = studentName;
}
private void testPrivateMethod() {
System.out.println("调用私有方法");
}
@Override
public String toString() {
return "Student [age=" + age + ", studentName=" + studentName + ", studentNO=" + studentNO + "]";
}
}
小结:
对于上面三种方式,一般采用第三种方式;第一种要借助对象获取Class对象,因为已经对象了,还获取Class对象就没有多大意义了(直接调用对象就可以了);第二种在使用的时候可能需要导包,依赖性比较强;所以一般采用第三种方式获取Class对象。
2.2 Constructor类
通过调用Constructor类的newInstance()方法可以获取对象,但前提是先获得Constructor对象,可以通过Class类中的方法可以获取类构造方法对象。相关方法如下:
Class类中获取构造方法的方法有如下几种:
Constructor<T> getConstructor(Class<?>... parameterTypes) // 返回指定类型的公有构造方法对象
Constructor<?>[] getConstructors() // 返回所有公有构造方法对象
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) // 返回指定参数类型的构造方法对象(包括公有,私有,保护方法)
Constructor<?>[] getDeclaredConstructors() // 返回所有构造方法对象(包括公有,私有,保护方法)
Constructor类中创建对象方法:
T newInstance(Object... initargs) //根据参数列表生成对象
具体示例如下:
public static void testConstructor() throws Exception {
Class<?> stuClass = Class.forName("com.base.reflect.Student");
// 获取所有/单个公有构造函数
Constructor<?>[] pubConArr = stuClass.getConstructors();
Constructor<?> pubCon = stuClass.getConstructor(new Class[] {int.class, String.class});
// 调用公有构造方法
Object stu = pubCon.newInstance(new Object[] {23, "李四"});
// 获取所有/单个(私有/公有/保护)构造函数
Constructor<?>[] allConArr = stuClass.getDeclaredConstructors();
Constructor<?> con = stuClass.getDeclaredConstructor(new Class[] {int.class});
// 调用私有构造方法,需要设置accessible参数
con.setAccessible(true);
Object stu1 = con.newInstance(new Object[] {24});
}
2.3 Method类
通过调用Method类中的invoke()方法可以执行对象中的方法。在此之前需要先获取目标对象和Method对象,目标对象可通过Constructor对象获取,Method对象可通过Class类中的相关方法获取,所需方法如下:
Class类中方法:
Method getDeclaredMethod(String name, Class<?>... parameterTypes) // 返回指定参数类型列表的方法对象(包括公有,私有,保护方法)
Method[] getDeclaredMethods() // 返回所有方法对象(包括公有,私有,保护方法)
Method getMethod(String name, Class<?>... parameterTypes) // 返回指定参数类型列表的公有方法对象
Method[] getMethods() // 返回所有公有方法对象
Method中方法:
Object invoke(Object obj, Object... args) // 执行obj对象中的方法
示例如下:
public static void testMethod() throws Exception {
Class<?> stuClass = Class.forName("com.base.reflect.Student");
Constructor<?> constructor = stuClass.getDeclaredConstructor(new Class[] {});
Student stu = (Student) constructor.newInstance(new Object[] {});
// 获取所有/单个公有方法
Method[] methodArr = stuClass.getMethods();
Method pubMethod = stuClass.getMethod("setAge", new Class[] {int.class});
// 调用公有方法
pubMethod.invoke(stu, new Object[] {25});
// 获取所有/单个(私有/公有/保护)方法
Method[] allMethodArr = stuClass.getDeclaredMethods();
Method method = stuClass.getDeclaredMethod("testPrivateMethod", new Class[] {});
// 调用私有方法,需要设置accessible参数
method.setAccessible(true);
method.invoke(stu, new Object[] {});
}
2.4 Field类
通过调用Field类中的set()方法设置属性值。再此之前需要先获取目标对象和Field对象,目标对象可通过上述Constructor对象获取,Field对象可通过Class类中的相关方法获取,所需方法如下:
Class类中方法:
Field getDeclaredField(String name) // 返回单个Field对象
Field[] getDeclaredFields() // 返回所有Field对象
Field getField(String name) // 返回单个公有Field对象
Field[] getFields() // 返回所有公有Field对象
Field中方法:
void set(Object obj, Object value) // 给obj对象设置值value
示例如下:
public static void testField() throws Exception {
Class<?> stuClass = Class.forName("com.base.reflect.Student");
Constructor<?> constructor = stuClass.getDeclaredConstructor(new Class[] {});
Student stu = (Student) constructor.newInstance(new Object[] {});
// 获取所有/单个公有属性
Field[] fieldArr = stuClass.getFields();
Field pubField = stuClass.getField("studentNO");
// 设置单个公有属性值
pubField.set(stu, "19102");
// 获取所有/单个(私有/公有/保护)属性
Field[] allFieldArr = stuClass.getDeclaredFields();
Field field = stuClass.getDeclaredField("studentName");
// 设置私有属性,需要设置accessible参数
field.setAccessible(true);
field.set(stu, "王五");
}
3、反射示例
下面通过一个小例子来熟悉一下反射。需求:给定一个对象(已设置相关属性值),使用反射技术拷贝与此对象相同的对象。功能实现如下:
package com.base.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class CopyObjectDemo {
public static void main(String[] args) {
User user = new User(23, "张三");
System.out.println("拷贝前对象:" + user.toString());
try {
User copyUser = copyObjectByReflect(user);
System.out.println("拷贝后对象:" + copyUser.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
public static User copyObjectByReflect(User user) throws Exception {
Class<?> userClass = Class.forName("com.base.reflect.User");
Constructor<?> constructor = userClass.getDeclaredConstructor(new Class[] {});
User copyUser = (User)constructor.newInstance(new Object[] {});
Field[] fields = userClass.getDeclaredFields();
for (Field field : fields) {
// 以下采用2种方式设置对象值,主要为了熟悉反射方法和属性的使用
// 方法1 调用方法设置属性值
String fieldName = field.getName().substring(0,1).toUpperCase()
+field.getName().substring(1);
String getMethodName = "get" + fieldName;
Method getMethod = userClass.getDeclaredMethod(getMethodName, new Class[] {});
Object fieldVal = getMethod.invoke(user, new Object[] {});
String setMethodName = "set" + fieldName;
Method setMethod = userClass.getDeclaredMethod(setMethodName,
new Class[] {field.getType()});
setMethod.invoke(copyUser, new Object[] {fieldVal});
// 方法2 直接使用反射设置属性值
field.setAccessible(true);
Object value = field.get(user);
field.set(copyUser, value);
}
return copyUser;
}
}
class User {
private int age;
private String studentName;
public User() {}
public User(int age, String studentName) {
this.age = age;
this.studentName = studentName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getStudentName() {
return studentName;
}
public void setStudentName(String studentName) {
this.studentName = studentName;
}
@Override
public String toString() {
return "User [age=" + age + ", studentName=" + studentName + "]";
}
}
4、总结
本文简单描述了反射相关类的使用,并举了个若干例子,从中可以发现反射确实很强大,私有属性和方法也可以间接的访问。但是从代码中也可以看到,有些时候完全可以通过Object获取对象值,这将越过java的类型检查从而会导致安全问题;同时性能也是一个问题,反射相当于一系列解释操作,通知jvm要做的事情,性能比直接的java代码要慢很多。因此运用反射要慎重,当然一般的业务功能开发中也很少用到反射技术,开源框架中用的还是比较多的。