Java 反射机制是在运行状态中,对于任意一个类,都能够获得这个类的所有属性和方法,对于任意一个对象都能够调用它的任意一个属性和方法。这种在运行时动态的获取信息以及动态调用对象的方法的功能称为Java 的反射机制。
首先先定义一个类
@Data //getset lombok 插件
@AllArgsConstructor //有参构造
@NoArgsConstructor //无参构造
public class Student {
public String stuName;
}
//正常调用
Student student = new Student();
student.setStuName("ws");
System.out.println(student.getStuName());
输出结果 ws
这时候,我们使用 JDK 提供的反射 API 进行反射调用:
//获取 Class 类对象
Class student1 = Class.forName("Student");
// 获取方法的 Method 对象
Method setStuName = student1.getMethod("setStuName", String.class); //第一个参数 方法名称 第二个参数入参类型
//根据class对象获取构造器
Constructor constructor = student1.getConstructor();
//使用 Constructor 对象的 newInstance 方法获取反射类对象
Object o = constructor.newInstance();
//利用 invoke 方法调用方法
setStuName.invoke(o, "ws");
//获取方法的Method 对象
Method getStuName = student1.getMethod("getStuName");
System.out.println(getStuName.invoke(o));
上面两段代码的执行结果,其实是完全一样的。但是其思路完全不一样,第一段代码在未运行时就已经确定了要运行的类(Student),而第二段代码则是在运行时通过字符串值才得知要运行的类(“Student”)。
从这个简单的例子可以看出,一般情况下我们使用反射获取一个对象的步骤:
- 获取class实例
Class student1 = Class.forName("Student");
- 根据 Class 对象实例获取 Constructor 对象
Constructor constructor = student1.getConstructor();
- 使用 Constructor 对象的 newInstance 方法获取反射类对象
Object o = constructor.newInstance();
而如果要调用某一个方法,则需要经过下面的步骤:
- 获取方法的 Method 对象
Method setStuName = student1.getMethod("setStuName", String.class); //第一个参数 方法名称 第二个参数入参类型
- 利用 invoke 方法调用方法
setStuName.invoke(o, "yhl");
反射常用API
class 对象
Class对象是Java反射的基础,它包含了与类相关的信息,事实上,Class对象就是用来创建类的所有对象的。Class对象是java.lang.Class这个类生成的对象,其中类型参数T表示由此 Class 对象建模的类的类型。例如,String.class的类型是 Class;如果将被建模的类未知,则使用Class<?>
获取反射中的Class对象
(1) 通过实例变量的getClass()方法。例如:
Class c1 = new String("abc").getClass();
(2) 通过Class类的静态方法——forName()来实现,例如:
Class class =Class.forName(className);
注意:当使用Class.forName()方法时,你必须提供完全限定类名。即类名要包括所有包
名。例如,如果MyObject是位于包com.jenkov.myapp下,那么类的完全限定名称是com.jenkov.myapp.MyObject。如果在运行时类路径上找不到类,Class.forName()方法会抛出一个ClassNotFoundException。
(3) 使用类字面常量或TYPE字段,例如:
Class myObjectClass= MyObject.class;//(类字面常量不仅可以应用于普通的类,也可以应用
于接口、数组以及基本数据类型),这种方式不仅更简单,而且更安全,因为它在编译时就会受到检查,并且根除了对forName方法的调用,所以也更高效,建议使用“.class”的形式。
Class c = Integer.TYPE;
(TYPE是基本数据类型的包装类型的一个标准字段,它是一个引用,指向对应的基本数据类型的Class对象),附表如下,两边等价:
类名
从Class对象中可以获取两个不同的类名。完全限定类名(包括包名)可以使用getName()或getCanonicalName()方法获取,例如:
Class aClass = MyObject.class;
String className = aClass.getName();
String className1 = aClass.getCanonicalName();
如果想要获取不含包名的类名可以使用getSimpleName() 方法,如下:
Class aClass = MyObject.class;
String simpleClassName = aClass.getSimpleName();
修饰符
使用Class对象可以获取一个类的修饰符.类的修饰符即关键字"public",“private”, "static"等. 如下:
Class aClass = MyObject.class;
int modifiers = aClass.getModifiers();
包信息
使用Class对象可以获取包信息,如下:
Class aClass = MyObject.class;
Package package = aClass.getPackage();
String packageName = package.getname();
父类
通过Class对象可以获取类的父类,如下:
Class aClass = MyObject.class;
Class superclass = aClass.getSuperclass();
//父类的Class对象和其它Class对象一样是一个Class对象,可以继续使用反射.
实现的接口
通过给定的类可以获取这个类所实现的接口列表,如下:
Class aClass = MyObject.class;
Class[] interfaces = aClass.getInterfaces();
构造函数
使用Class对象可以获取类的构造函数,如下:
Class aClass = MyObject.class;
Constructor[] constructors = aClass.getConstructors();
通过反射创建类对象
通过反射创建类对象主要有两种方式:通过 Class 对象的 newInstance() 方法、通过 Constructor 对象的 newInstance() 方法。
第一种:通过 Class 对象的 newInstance() 方法。
Class<Student> studentClass = Student.class;
Student ww = (Student)student1.newInstance();
第二种:通过 Constructor 对象的 newInstance() 方法
Class<Student> studentClass = Student.class;
Constructor constructor = studentClass.getConstructor();
Student ww = (Student )constructor.newInstance();
通过 Constructor 对象创建类对象可以选择特定构造方法,而通过 Class 对象则只能使用默认的无参数构造方法。
下面的代码就调用了一个有参数的构造方法进行了类对象的初始化。
Class<Student> studentClass = Student.class;
Constructor<Student> constructor = studentClass.getConstructor(String.class);// 参数的类型
Student ww = constructor.newInstance("ww");
通过反射获取类属性、方法
我们通过 Class 对象的 getFields() 方法可以获取 Class 类的属性,但无法获取私有属性。
Class<Student> stu = Student.class;
Field[] fields = stu.getFields();
for (Field field : fields) {
System.out.println(field.getName());
}
而如果使用 Class 对象的 getDeclaredFields() 方法则可以获取包括私有属性在内的所有属性
与获取类属性一样,当我们去获取类方法、类构造器时,如果要获取私有方法或私有构造器,则必须使用有 declared 关键字的方法。