反射的原理:将一个类中的各部分封装成其他对象

反射的好处:
1.可以在程序运行中,操作这些对象
2.可以解耦,提高程序的可扩展性

下面用一副我画的图来简单解释一下Java程序在计算机中运行经历的阶段,以及各阶段我们用反射技术是如何创建对象的

java 反射static final 变量 java反射forname_成员变量

上图我已经写出获取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类对象获取到的被反射的类的名字是它的包加上它的类名。