反射的原理:将一个类中的各部分封装成其他对象
反射的好处:
1.可以在程序运行中,操作这些对象
2.可以解耦,提高程序的可扩展性
下面用一副我画的图来简单解释一下Java程序在计算机中运行经历的阶段,以及各阶段我们用反射技术是如何创建对象的
上图我已经写出获取Class对象的三个方式:
1.class.forName(“全类名(包名.类名)”):将字节码文件加载进内存,返回class对象
2.类名.class;通过类名的属性class获取
3.对象.getClass():getClass()方法在Object类中定义
先做个实验来熟悉一下创建Class对象的方法
package reflect;
import person_reflect.Person;
public class reflect_01 {
public static void main(String[] args) throws ClassNotFoundException {
//1.Class.forName(全类名)
Class c1 = Class.forName("person_reflect.Person");
System.out.println(c1);
//2.类名.class
Class c2 = Person.class;
System.out.println(c2);
//3.对象.getClass();
Person p = new Person();
Class c3 = p.getClass();
System.out.println(c3);
//验证一下这三个对象一不一样
System.out.println(c1==c2);
System.out.println(c2==c3);
System.out.println(c1==c3);
//结论: 同一个字节码文件(*.class)在一次程序的运行过程中,只会被加载一次,不论通过哪种方法获取到的对象都是同一个
}
}
从上述代码中我们可以看出,同一个字节码文件在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个
下面我们来正式学习一下反射的实际用法
首先创建一个Person类
package person_reflect;
public class Person {
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", a=" + a + ", b=" + b + ", c=" + c + ", d=" + d + "]";
}
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;
}
private String name;
private int age;
public int a;
private int b;
int c;
private int d;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public Person() {}
public void eat () {
System.out.println("eat");
}
public void eat(String food) {
System.out.println("eat"+food);
}
}
一个类中首先我们能想到什么,肯定是成员变量,那么我们就说一下利用反射技术获取类成员变量的方法
* Field[] getFields():获取所有public修饰的成员变量
* Field getfield(String name):获取指定名称的成员变量
*
* Field[] getDeclaredField()
* Field getDeclaredField(String name)
首先我们要获取Person类的Class类对象
Class pc = Person.class;
然后我们就可以用到上面的四种方法了
先用Field[] getFields():举例
java.lang.reflect.Field[] fields = pc.getFields();
for(java.lang.reflect.Field field :fields){
System.out.println(field);
}
利用这个方法,我们可以获取所有用public修饰的成员变量
那么如果我们想要获取类中特定的变量怎么办呢?
java.lang.reflect.Field field = pc.getField("a");
System.out.println(field);
这时候我们就可以用到getField(String name);方法了,注意这个方法是要传进去一个变量名的字符串变量的。
那么我们获取变量的目的是什么呢,肯定是获得值或者给其赋值啦
和类中的get/set一样,Class类对象获得目标类的变量值和赋值时也有get/set方法
java.lang.reflect.Field a = pc.getField("a");
Person p = new Person();
Object value = a.get(p);//获取Person类中public修饰的成员变量a的值
System.out.println(value);
//给Person中public修饰的成员变量a赋值
a.set(p,1);
System.out.println(p);
上面这都是获取public修饰符修饰的变量,如果我想要调用private、protect、default修饰符修饰的变量怎么办?
那就要用到 * Field[] getDeclaredField()
- Field getDeclaredField(String name)方法了
/* Field[] getDeclaredField():获取类中全部的成员变量,不考虑修饰符
Field getDeclaredField(String name):获取类中指定名字的成员变量
*/
//获取全部变量
java.lang.reflect.Field[] fields_1 = pc.getDeclaredFields();
for(java.lang.reflect.Field field_1 : fields_1) {
System.out.println(field_1);
}
//获取d变量并为其赋值 2
java.lang.reflect.Field field_1 = pc.getDeclaredField("d");
Object value2 = field_1.get(p);
field_1.set(p, 2);
System.out.println(p);
如果运行了的小伙伴会发现,这段程序报错了,为什么呢?
/*
//错误提示
* Exception in thread "main" java.lang.IllegalAccessException: class reflect.reflect_02 cannot access a member of class person_reflect.Person with modifiers "private"
at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:385)
at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:693)
at java.base/java.lang.reflect.Field.checkAccess(Field.java:1096)
at java.base/java.lang.reflect.Field.get(Field.java:417)
at reflect.reflect_02.main(reflect_02.java:53)
*/
因为上面Person类的代码中我们定义了d是由private修饰符修饰的,现在我们知道,虽然getDeclaredField(String name)方法虽然可以获取所有修饰符修饰的变量,但是获取它的值和对其赋值可是不被允许的,那么怎么办呢?
这时我们就要加上一行代码了
java.lang.reflect.Field[] fields_1 = pc.getDeclaredFields();
for(java.lang.reflect.Field field_1 : fields_1) {
System.out.println(field_1);
}
java.lang.reflect.Field field_1 = pc.getDeclaredField("d");
field_1.setAccessible(true); //===================所以我们要加上一行代码 忽略访问权限修饰符的安全检查 **暴力反射**===========================
Object value2 = field_1.get(p);
field_1.set(p, 2);
System.out.println(p);
细心的小伙伴都发现了 上下两块代码有一行不一样,对了 就是这里
field_1.setAccessible(true);
这行代码是暴力反射 忽略访问权限
解释:private修饰的变量get或者set值是不能直接获取的 所以我们要加上一行
获取类的成员变量的对象名.setAccessible(true):暴力反射 忽略访问权限
到这获取类中成员变量的方法就基本结束了,那么接下来是什么呢?对了,就是类的构造方法
同获取类的成员变量一样,获取类的构造方法我们同样有四个方法
***/*
* 获取类的构造方法
*
* Constructor<?>[] getConstructors()
* Constructor<T> getConstructors(类<?>... parameterTypes)
*
* Constructor<?>[] getDeclaredConstructors()
* Constructor<T> getDeclaredConstructors(类<?>... parameterTypes)
*/***
相信有的小伙伴已经发现了,这不是和获取成员变量的方法一样吗?还真是,用法基本是一样的
同样我们先获得Class类对象pc
Class pc = Person.class;
因为和获取成员变量的方法基本一样嘛,所以这里我只举一个栗子,因为后面我们还有更重要的东西要说:
// Constructor<T> getConstructors(类<?>... parameterTypes)
Constructor constructor = pc.getConstructor(String.class,int.class);
//返回一个构造器
System.out.println(constructor);
这个方法就是获得Person类中有参数且参数类型为String和int的构造方法
返回的这个constructor就是一个构造器 ,那么重点来了,这个构造器能干什么?既然叫构造器了那么肯定能创建点东西吧,欸,它就是用来创建对象的。
T newInstance(Object… initargs)
下面我们举个例子,来解释一下用这个构造器怎么才能创建个对象出来,因为是一个对象,所以我们要用Object类型的person变量来接收这个值
//用构造器来创建对象
Object person = constructor.newInstance("张三",11);
System.out.println(person);
Constructor constructor1 = pc.getConstructor();
Object person1 = constructor1.newInstance();
System.out.println(person1);//用一个空参的构造方法创建对象
pc.newInstance(); // 可以直接创建无参新对象
这样我们就用这个构造器创建了一个对象,并且我们给这个对象传了两个参数,下面来看看运行结果
//Person [name=张三, age=11, a=0, b=0, c=0, d=0]
这样我们就用Class类的构造器constructor来反射创建了一个Person对象。我们也可以选择更简单的方法,直接用Class类对象pc反射建立一个Class类指向的类的无参对象,这里也就是Person
pc.newInstance();
只要这一行代码就建立好了,我们来看一下它的无参结果
//Person [name=null, age=0, a=0, b=0, c=0, d=0]
现在构造方法和变量的获取我们都说过了,那么一个类中还有什么呢?欸,对了,就是成员方法,那么成员方法怎么获取呢,没错,还是那四个方法。
**/*
* 获取类的成员方法
* Method[] getMethods()
* Method getMethod(String name,类<?>... parameterTypes)
*
* Method[] getDeclaredMethods()
* Method getDeclaredMethod(String name,类<?>... parameterTypes)
*/**
因为都一样,我只举一个例子:
Class pc = Person.class;
java.lang.reflect.Method eat_method = pc.getMethod("eat");
Person p = new Person();
//因为是空参 只添加一个对象名即可
eat_method.invoke(p);
java.lang.reflect.Method eat1_method = pc.getMethod("eat", String.class);
eat1_method.invoke(p,"shit");
//获取所有public修饰的方法 还有很多隐藏方法
java.lang.reflect.Method[] methods = pc.getMethods();
for(java.lang.reflect.Method method1s : methods ) {
System.out.println(method1s);
String name = method1s.getName();
System.out.println(name);//获取方法名
这里我们就用到了方法重载,两个eat方法但是参数不同,一个是无参,一个要传进一个String参数,上面提到过,获取带参数的成员方法或者构造方法要怎么样了,没错就是要用**数据类型.class;**这里我们是String.class
有细心的小伙伴应该发现了,成员方法是可以只获取名字的 ,没错就是这行代码
String name = method1s.getName();
System.out.println(name);//获取方法名
同样,被反射的类也是可以被获取到类名的
String name = pc.getName();
System.out.println(name);
运行后会发现,获取到的类名是
person_reflect.Person
这就回到了我们一开始创建的类,是在person_reflect包下创建的,所以可以知道Class类对象获取到的被反射的类的名字是它的包加上它的类名。