目录
反射
1.什么是反射
Reflection(反射) 是 Java 程序开发语言的特征之一,是一种根据类的元数据创建对象,访问对象成员的技术。它允许运行中的 Java 程序对自身进行检查,可以获取任意类中的方法和属性;也可以调用任意对象中的方法和属性。
2.反射可以做什么
1.创建对象
2.访问对象的属性
3.访问对象的方法
3 反射中的常用类及常用方法
类 | 用途 |
---|---|
Class | 描述类的类 |
ConStructot | 描述构造函数的类 |
Field | 描述字段(属性)的类 |
Method | 描述方法的类 |
Parameter | 描述方法参数的类 |
常用方法:
//获取包名、类名
clazz.getPackage().getName()//包名
clazz.getSimpleName()//类名
clazz.getName()//完整类名
//获取成员变量定义信息
getFields()//获取所有公开的成员变量,包括继承变量
getDeclaredFields()//获取本类定义的成员变量,包括私有,但不包括继承的变量
getField(变量名)
getDeclaredField(变量名)
//获取构造方法定义信息
getConstructor(参数类型列表)//获取公开的构造方法
getConstructors()//获取所有的公开的构造方法
getDeclaredConstructors()//获取所有的构造方法,包括私有
getDeclaredConstructor(int.class,String.class)
//获取方法定义信息
getMethods()//获取所有可见的方法,包括继承的方法
getMethod(方法名,参数类型列表)
getDeclaredMethods()//获取本类定义的的方法,包括私有,不包括继承的方法
getDeclaredMethod(方法名,int.class,String.class)
//反射新建实例
clazz.newInstance();//执行无参构造创建对象
clazz.newInstance("10086","李明",18);//执行有参构造创建对象
clazz.getConstructor(String.class, String.class, int.class)//获取构造方法
//反射调用成员变量
clazz.getDeclaredField(变量名);//获取变量
clazz.setAccessible(true);//使私有成员允许访问
f.set(实例,值);//为指定实例的变量赋值,静态变量,第一参数给null
f.get(实例);//访问指定实例变量的值,静态变量,第一参数给null
//反射调用成员方法
Method m = Clazz.getDeclaredMethod(方法名,参数类型列表);
m.setAccessible(true);//使私有方法允许被调用
m.invoke(实例,参数数据);//让指定实例来执行该方法
4. 通过反射实例化对象
4.1 获取类对应的字节码的对象
要想解剖一个类,必须先要获取到该类的字节码文件对象。解剖使用的是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.
Class类对象有三种实例化模式:
方式 | 备注 |
---|---|
对象.getClass() | |
类.class | |
Class.forName(包名.类名) | 静态 |
以下分别举例实现:
-
对象.getClass()
调用某个类的对象的getClass()方法,即:对象.getClass()public final native Class<?> getClass();
示例:
Student stu = new Student();
class<?> clazz = stu.getClass(); //?代表通配符,当不清楚获取到的对象的类型可使用,此处也可换成Student
- 类.class
调用类的class属性来获取该类对应的Class对象
示例:
Class<?> clazz1 = Student.class;
- Class.forName(包名加类名)
使用Class类中的forName()静态方法来获取该对应的Class对象。此方法最安全,性能最好。
public static Class<?> forName(String className) throws ClassNotFoundException
示例:
Class<?> clazz = null;
try {
clazz = Class.forName("包名.类名");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
通常采用第三种方式。
注:在运行期间,一个类只有一个Class对象产生。
4.2 实例化对象
通过三种创建Class对象的方式,我们可以发现只有getClass()方法会实例化对象之外,其他的两种不会产生实例化对象,所以取得Class类对象的一个最直接的好处就是通过反射实例化对象。有两种方法可以实例化对象。第一种是使用Class类对象.newInstance,但在java 9后便不建议使用。第二种是使用构造函数来创建对象。
注:newInstance()方法内部实际上调用了无参数构造方法,必须保证无参构造存在才可以。
否则会抛出java.lang.InstantiationException异常。
示例:
1.创建一个学生类
创建包: com.reflection
创建类: Student.java*
package com.reflection;
public class Student {
private String id;
private String name;
private int age;
public Student() {}
public Student(String id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Student{" +
"id='" + this.id + '\'' +
"name='" + this.name + '\'' +
", age=" + this.age +
'}';
}
}
创建测试类
由于经常使用的是第三种方法,所以以下使用第三种。
创建包: com.reflection
创建类: TestReflect.java
public class test {
public static void main(String[] args) throws Exception{
//1.获取字节码对象
Class<?> clazz = Class.forName("com.reflection.Student");
//2.通过反射技术创建目标类的对象,注意抛出异常
/*反射创建对象方案1:
使用 目标类 的 无参构造 创建对象
*/
Object o = clazz.newInstance();
System.out.println(o);//这一步已经获取到了对象Student{id='null',name='null', age=0}
/*反射创建对象方案2:
使用 目标类 的 全参构造 创建对象
* 思路:
* 1.先获取指定的构造函数对象,注意需要指定构造函数的参数,传入的是.class字节码对象
* 2.通过刚刚获取到的构造函数对象创建Student目标类的对象,并且给对象的属性赋值
* */
//3.获取目标类中指定的全参构造
Constructor<?> c = clazz.getConstructor(String.class,String.class, int.class);
//System.out.println(c);
//3.打印数组,查看数组中的元素
System.out.println(Arrays.toString(s));
//4.通过获取到的构造函数:创建对象 + 给对象的属性赋值
Object o2 = c.newInstance("10086", "赵六", 26);
System.out.println(o2);
}
}
}
5. 获取对象的属性和方法
5.1获取成员变量
public class TestReflect {
public void getStu() {
//1.创建Student类的3个对象
Student s1 = new Student("001","张三", 3);
Student s2 = new Student("001","李四", 4);
Student s3 = new Student("001","王五", 5);
//2.创建数组将刚刚的3个对象存入数组中
Student[] s = {s1, s2, s3};
//3.直接打印数组,查看数组中的元素
System.out.println(Arrays.toString(s));
//4.遍历学生数组,拿到每一个学生对象,做进一步的操作
for (Student stu : s) {
stu.print();
}
}
//获取Student类中的成员变量
public void getFie() throws ClassNotFoundException {
//1.获取Student类对应的字节码对象
Class<?> clazz = Class.forName("com.reflection.Student");
//2.通过Student类对应的字节码对象获取Student类中的成员变量们
Field[] fs = clazz.getFields();
//3.遍历数组,获取Student类中的每个成员变量的具体信息
/*注意!目前成员变量的修饰符必须是public的才能获取到*/
for(Field f : fs){
System.out.println(f.getName());//通过本轮循环到的字段对象获取字段名
System.out.println(f.getType());//通过本轮循环到的字段对象获取字段的类型
}
}
}
5.2 获取类的成员方法
public class TestReflect {
//获取Student类中的成员方法
public void getFunction() {
//1.获取Student类对应的字节码对象
Class<?> clazz = Class.forName("com.reflection.Student");
//2.通过Student类对应的字节码对象获取Student类中的成员方法们
Method[] ms = clazz.getMethods();
//3.通过高效for循环遍历数组,拿到每一个方法对象
for (Method m : ms) {
System.out.println(m);//直接打印遍历到的方法对象
System.out.println(m.getName());//通过方法对象获取方法名
Class<?>[] pt = m.getParameterTypes();//通过方法对象获取方法所有参数的数组
System.out.println(Arrays.toString(pt));//打印方法参数的数组
}
}
}
5.3 获取类的构造方法
public class TestReflect {
//获取Student类中的构造方法
public void getCons() {
//1.获取字节码对象
Class<?> clazz = Class.forName("com.reflection.Student");
//2.通过字节码对象获取目标类Student的构造方法们
Constructor<?>[] cs = clazz.getConstructors();
//3.通过高效for循环遍历数组
for(Constructor c : cs){
System.out.println(c.getName());//打印本轮遍历到的构造方法的名字
Class[] pt = c.getParameterTypes();//通过本轮遍历到的构造函数对象获取构造函数的参数类型
System.out.println(Arrays.toString(pt));//打印参数类型
}
}
}
6. 反射的优缺点
6.1 优点
1、增加程序的灵活性,可以在运行的过程中动态对类进行修改和操作。
2、提高代码的复用率,比如动态代理,就是用到了反射来实现。
3、可以在运行时轻松获取任意一个类的方法、属性、并且还能通过反射进行动态调用。
6.2 缺点
1、反射会涉及到动态类型的解析,所以JVM无法对这些代码进行优化,导致性能要比非反射调用更低。
2、使用反射后,代码的可读性会下降
3、反射可以绕过一些限制访问的属性或是方法,可能会导致破坏了代码本身的抽象性,而且可能会导致一些安全性问题。