文章目录
Java反射机制
动态语言
动态语言是指程序在运行时可以改变其内部结构:比如新增成员函数,删除已有删除等结构上的一些变化。常见的动态语言有JavaScript和Ruby、Python等。而C、C++等不属于动态语言。从反射角度而言,Java属于半动态的语言。
什么是反射
反射是指在在运行状态中,对于任意一个类都能够获取这个类的所有属性和方法;并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YMVIATAk-1636517914730)(https://s3.bmp.ovh/imgs/2021/11/ad0a73868854d498.png)]
反射的应用场景
编译时类型和运行时类型
在Java程序中许多对象在运行时都会出现两种类型:编译时类型和运行时类型。
- 编译时类型是由声名对象是所使用的类型。
- 运行时类型是指实际运行时由实际赋值给对象的类型决定
Person p = new Student()
编译时类型:Person
运行时类型:Student
编译时类型是无法获取具体方法的。
程序在运行时还可能接收到外部传入的对象,该对象的编译时类型为Object,但是程序有需要调用该对象的运行时类型的方法。为了解决这些问题,程序需要在运行时发现类和对象的真实性信息。然而编译时根本无法预知该对象和类属于哪些类,程序只能依靠在运行时的信息来发现该类和对象的真实信息。此时则必须使用到反射机制。
使用反射机制可以打破封装性,导致了java对象的属性不安全。
Java反射API
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HKKRkTwG-1636517914732)(https://s3.bmp.ovh/imgs/2021/11/ad58181a1cf4bcb5.jpg)]
反射API用来生成JVM中的类、接口或对象的信息。
- Class类:该类是反射的核心类,可以获取类的属性、方法等信息。
- Field类:Java.lang.reflect包中的类,表示该类的成员变量,它可以用来获取和设置类之中的属性值
- Method类:Java.lang.reflect包中的类,表示该类的成员方法,它可以用来获取类中的方法信息或执行方法。
- Construcor类:Java.lang.reflect包中的类,表示该类的构造方法。
相关API
getName():获得类的完整名字。
getFields():获得类的public类型的属性。
getDeclaredFields():获得类的所有属性。包括private 声明的和继承类
getMethods():获得类的public类型的方法。
getDeclaredMethods():获得类的所有方法。包括private 声明的和继承类
getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes 参数指定方法的参数类型。
getConstructors():获得类的public类型的构造方法。
getConstructor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes 参数指定构造方法的参数类型。
newInstance():通过类的不带参数的构造方法创建这个类的一个对象。
反射的使用步骤及方法(获取Class对象,调用对象的方法)
- 获取想要操作类的Class的对象,这是反射类的核心,通过该类可以调用任意类的方法。
- 调用Class类的方法,就是反射的使用阶段。
- 使用反射API来获取这些信息。
获取某个类的Class对象的3种方法
-
调用某个类的getClass()方法。
Person p = new Person() Class clazz = p.getClass()
-
调用某个类的class属性来获取该类对应的Class对象。
Class clazz = Person.class//该属性是该类的静态属性
-
使用Class类中的forName()静态方法获取(最安全/性能最好)
Class clazz = Class.forName('类路径');//最常用 //但可能抛出 ClassNotFoundException 异常
- 第一种方法是通过类的全路径字符串获取 Class 对象,这也是我们平时最常用的反射获取 Class 对象的方法;
- 第二种方法有限制条件:需要导入类的包;
- 第三种方法已经有了 Student 对象,不再需要反射。
-
通过这三种方式获取到的 Class 对象是同一个,也就是说 Java 运行时,每一个类只会生成一个 Class 对象
使用获取到的Class对象获取该类的属性和方法信息
//1.使用最常用的获取某类的Class类对象的方法
Class clazz = Class.forName('reflection.Persson');
//2.使用Class对象获取Person类的所有方法信息
Method[] methods = clazz.getDeclareMethods();
for(Method m:method){
System.out.println(m.toString());
}
//3.获取Person类的所有成员属性信息(包括私有属性)
Field[] field = clazz.getDeclareFields();
for(Field f:fiedl){
System.out.println(f.toString());
}
//4.获取 Person 类的所有构造方法信息
Constructor[]constructor=clazz.getDeclaredConstructors();
for(Constructor c:constructor){
System.out.println(c.toString());
}
综合实例
//获得类完整的名字
String className = c2.getName();
System.out.println(className);//输出com.ys.reflex.Person
//获得类的public类型的属性。
Field[] fields = c2.getFields();
for(Field field : fields){
System.out.println(field.getName());//age
}
//获得类的所有属性。包括私有的
Field [] allFields = c2.getDeclaredFields();
for(Field field : allFields){
System.out.println(field.getName());//name age
}
//获得类的public类型的方法。这里包括 Object 类的一些方法
Method [] methods = c2.getMethods();
for(Method method : methods){
System.out.println(method.getName());//work waid equls toString hashCode等
}
//获得类的所有方法。
Method [] allMethods = c2.getDeclaredMethods();
for(Method method : allMethods){
System.out.println(method.getName());//work say
}
//获得指定的属性
Field f1 = c2.getField("age");
System.out.println(f1);
//获得指定的私有属性
Field f2 = c2.getDeclaredField("name");
//启用和禁用访问安全检查的开关,值为 true,则表示反射的对象在使用时应该取消 java 语言的访问检查;反之不取消
f2.setAccessible(true);
System.out.println(f2);
//创建这个类的一个对象
Object p2 = c2.newInstance();
//将 p2 对象的 f2 属性赋值为 Bob,f2 属性即为 私有属性 name
f2.set(p2,"Bob");
//使用反射机制可以打破封装性,导致了java对象的属性不安全。
System.out.println(f2.get(p2)); //Bob
//获取构造方法
Constructor [] constructors = c2.getConstructors();
for(Constructor constructor : constructors){
System.out.println(constructor.toString());//public com.ys.reflex.Person()
}
实践
package com.test.reflection;
public class Student {
private String studentName;
public int studentAge;
public Student() {
}
private Student(String studentName) {
this.studentName = studentName;
}
public void setStudentAge(int studentAge) {
this.studentAge = studentAge;
}
private String show(String message) {
System.out.println("show: " + studentName + "," + studentAge + "," + message);
return "testReturnValue";
}
}
// 1.通过字符串获取Class对象,这个字符串必须带上完整路径名
Class studentClass = Class.forName("com.test.reflection.Student");
// 2.获取声明的构造方法,传入所需参数的类名,如果有多个参数,用','连接即可
Constructor studentConstructor = studentClass.getDeclaredConstructor(String.class);
// 如果是私有的构造方法,需要调用下面这一行代码使其可使用,公有的构造方法则不需要下面这一行代码
studentConstructor.setAccessible(true);
// 使用构造方法的newInstance方法创建对象,传入构造方法所需参数,如果有多个参数,用','连接即可
Object student = studentConstructor.newInstance("NameA");
// 3.获取声明的字段,传入字段名
Field studentAgeField = studentClass.getDeclaredField("studentAge");
// 如果是私有的字段,需要调用下面这一行代码使其可使用,公有的字段则不需要下面这一行代码
// studentAgeField.setAccessible(true);
// 使用字段的set方法设置字段值,传入此对象以及参数值
studentAgeField.set(student,10);
// 4.获取声明的函数,传入所需参数的类名,如果有多个参数,用','连接即可
Method studentShowMethod = studentClass.getDeclaredMethod("show",String.class);
// 如果是私有的函数,需要调用下面这一行代码使其可使用,公有的函数则不需要下面这一行代码
studentShowMethod.setAccessible(true);
// 使用函数的invoke方法调用此函数,传入此对象以及函数所需参数,如果有多个参数,用','连接即可。函数会返回一个Object对象,使用强制类型转换转成实际类型即可
Object result = studentShowMethod.invoke(student,"message");
System.out.println("result: " + result);
总结
灵活使用反射能让我们代码更加灵活,这里比如JDBC原生代码注册驱动,hibernate 的实体类,Spring 的 AOP等等都有反射的实现。但是凡事都有两面性,反射也会消耗系统的性能,增加复杂性等,合理使用才是真!