1 什么是反射
Reflection(反射) 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说“自审”,也有称作“自省”。
反射非常强大,它甚至能直接操作程序的私有属性。我们前面学习都有一个概念,被private封装的资源只能类内部访问,外部是不行的,但这个规定被反射赤裸裸的打破了。
反射就像一面镜子,它可以在运行时获取一个类的所有信息,可以获取到任何定义的信息(包括成员变量,成员方法,构造器等),并且可以操纵类的字段、方法、构造器等部分。
2 为什么需要反射
如果想创建对象,我们直接new User(); 不是很方便嘛,为什么要去通过反射创建对象呢?
那我要先问你个问题了,你为什么要去餐馆吃饭呢?
例如:我们要吃个牛排大餐,如果我们自己创建,就什么都得管理。
好处是,每一步做什么我都很清晰,坏处是什么都得自己实现,那不是累死了。牛接生你管,吃什么你管,屠宰你管,运输你管,冷藏你管,烹饪你管,上桌你管。就拿做菜来说,你能有特级厨师做的好?
那怎么办呢?有句话说的好,专业的事情交给专业的人做,饲养交给农场主,屠宰交给刽子手,烹饪交给特级厨师。那我们干嘛呢?
我们翘起二郎腿直接拿过来吃就好了。
再者,饭店把东西做好,不能扔到地上,我们去捡着吃吧,那不是都成原始人了。那怎么办呢?很简单,把做好的东西放在一个容器中吧,如把牛排放在盘子里。
我们在后面的学习中,会学习框架,有一个框架Spring就是一个非常专业且功能强大的产品,它可以帮我们创建对象,管理对象。以后我无需手动new对象,直接从Spring提供的容器中的Beans获取即可。Beans底层其实就是一个Map<String,Object>,最终通过getBean(“user”)来获取。而这其中最核心的实现就是利用反射技术。
总结一句,类不是你创建的,是你同事或者直接是第三方公司,此时你要或得这个类的底层功能调用,就需要反射技术实现。有点抽象,别着急,我们做个案例,你就立马清晰。
3 反射需要用到的API
3.1 获取字节码对象
Class.forName(“类的全路径”);
类名.class
对象.getClass();
3.2 常用方法
获取包名 类名
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(666,”海绵宝宝”);//执行含参构造创建对象
clazz.getConstructor(int.class,String.class)//获取构造方法反射调用成员变量
clazz.getDeclaredField(变量名);//获取变量
clazz.setAccessible(true);//使私有成员允许访问
f.set(实例,值);//为指定实例的变量赋值,静态变量,第一参数给null
f.get(实例);//访问指定实例变量的值,静态变量,第一参数给null反射调用成员方法
Method m = Clazz.getDeclaredMethod(方法名,参数类型列表);
m.setAccessible(true);//使私有方法允许被调用
m.invoke(实例,参数数据);//让指定实例来执行该方法
4 反射的应用
4.1 创建 : 测试物料类
package cn.reflection;
/*本类用作测试反射的物科类,假装这是别人写的代码
* 反射的前提:获取字节码对象,因为字节码对象中有这个类所有的关键信息*/
public class Student {
//1.定义本类的成员变量
private String name;
int age;
//2.给私有属性name提供get与set方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//3.添加本类的无参与全参构造
public Student(){ }
public Student(String name, int age) {
this.name = name;
this.age = age;
}
//4.添加普通方法
public void eat(int n){
System.out.println("今天要吃"+n+"碗大米饭?");
}
}
4.2 练习 : 获取类对象
package cn.reflection;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import org.junit.Test;
/**本类用来测试反射*/
public class TestReflect {
//1.创建入口函数main()--不用
/**单元测试方法:是java测试的最小单位,使用灵活,推荐使用
* 语法要求: @Test + void + 没有参数 + public
* 注意使用时需要导包:Add JUnit 4 library to the build path:import org.junit.Test;
* 单元测试方法执行方式:选中方法名-->右键运行(Run As-->JUnit Test)-->出现小绿条说明执行成功
*/
//2.通过单元测试来测试如何获取类对象
@Test
public void getClazz() throws Exception {
/**右键要获取字节码对象的类名,选择Copy Quailfied Name复制类的全路径名*/
Class<?> student1 = Class.forName("cn.tedu.reflection.Student");//此处的参数是类的全路径名[包名+类名]
Class<?> student2 = Student.class;
Class<?> student3 = new Student().getClass();//先创建匿名对象,匿名对象没有名字,然后对象的字节码对象
System.out.println(student1);//反射得到的字节码Class对象
System.out.println(student2.getName());//获取类的全路径名[包名+类名]
System.out.println(student3.getSimpleName());//只获取类名
System.out.println(student3.getPackage().getName());//获取包名
}
}
4.3 练习 : 类获取构造方法
package cn.reflection;
import java.lang.reflect.Method;
import java.util.Arrays;
import org.junit.Test;
/*本类用于反射的测试*/
public class TestReflect {
//1.创建程序的入口函数main---不用
/*单元测试方法:是java中最小的测试单元,使用灵活,推荐使用
*语法要求:@Test + public + void + 没有参数
* 注意:使用时需要导包:Add JUnit 4 Library to the build path
* 导包后的效果:import org.junit.Test
* */
//2.通过单元测试方法,获取类的字节码对象
@Test
public void getClazz() throws ClassNotFoundException {//注意,为避免重名,是Clazz
//此方法的参数是类的全路径名:包名,类名,注意抛出异常
//以下三种方式都可以使用,根据使用环境中可以获得的资源决定使用哪种方式
Class<?> Clazz1 = Class.forName("cn.reflection.Student");
Class<?> Clazz2 = Student.class;
Class<?> Clazz3 = new Student().getClass();
System.out.println(Clazz1);
//class cn.reflection.Student 打印的是刚刚反射获取到的字节码对象
System.out.println(Clazz2.getName());
//cn.reflection.Student 打印的是当前字节码对象的全路径名
System.out.println(Clazz3.getSimpleName());
//Student 只获取类名
System.out.println(Clazz1.getPackage());
//package cn.reflection 获取的是包对象
System.out.println(Clazz1.getPackage().getName());
//cn.reflection 根据获取到的包对象,获取包的名字
}
//3.通过单元测试方法获取Student类中的构造方法
@Test
public void getFunction() throws ClassNotFoundException {
//1.获取字节码对象
Class<?> Clazz = Class.forName("cn.reflection.Student");//三种方式选择一种,注意forname需要抛出异常
//2.通过字节码对象,获取目标类的成员方法
Constructor<?>[] c = clazz.getConstructors();
//3.查看每个方法信息
//System.out.println(cs);//[Ljava.lang.reflect.Method;@1b9e1916
System.out.println(Arrays.toString(cs));//已经获取到了方法,但是想遍历
//4.把拿到的方法对象数组进行遍历
for(Constructor c : cs){
//通过每轮循环遍历到的方法对象,获取方法的各种信息
System.out.println(c.getName());
Class<?>[] types = c.getParameterTypes();
System.out.println(Arrays.toString(types));
//获取Student中的方法信息及其默认父类Object的方法信息
}
}
}
4.4 练习 : 获取成员方法
package cn.reflection;
import java.lang.reflect.Method;
import java.util.Arrays;
import org.junit.Test;
/**本类用来测试反射*/
public class TestReflect {
//4.通过单元测试来测试获取成员方法
@Test
public void getFunction() throws Exception {
//1.获取Class字节码对象
Class<?> clazz = Class.forName("cn.tedu.reflection.Student");
//2.获取所有成员方法
Method[] ms = clazz.getMethods();
//3.遍历数组,获取每个方法的信息
for (Method m : ms) {
System.out.println(m.getName());//获取方法名
Class<?>[] pt = m.getParameterTypes();//获取方法参数类型
System.out.println(Arrays.toString(pt));
}
}
}
4.5 练习:获取成员变量
package cn.reflection;
import java.lang.reflect.Field;
import org.junit.Test;
/**本类用来测试反射*/
public class TestReflect {
//5.通过单元测试来测试获取成员变量
@Test
public void getFields(){
//1.获取Class字节码对象
/** Class<?>中的"?"是泛型约束的通配符,类似于"*" */
Class<?> clazz = Student.class;
//2.获取所有的成员变量,公共的!!!
/**!!!注意目前成员变量的修饰符必须是public才能获取到,采用默认修饰符就反射不到*/
Field[] fs = clazz.getFields();
//3.遍历数组,获取每个成员变量的信息
for (Field f: fs) {
System.out.println(f.getName());//获取变量名
System.out.println(f.getType().getName());//获取变量类型
}
}
}
4.6 练习 : 创建对象
import java.lang.reflect.Constructor;
import org.junit.Test;
/**本类用来测试反射*/
public class TestReflect {
/*6.本方法用于练习通过反射创建指定类Student的对象*/
/*方式一:通过字节码对象之间调用newInstance(),触发目标类的无参构造来创建对象
* 方式二:先获取指定参数类型的构造函数对象
* 再通过获取到的这个构造函数对象调用newInsatnce(参数列表)
* 来创建Student类对象*/
@Test
public void getObject() throws Exception {
//1.获取字节码对象
Class<?> clazz = Student.class;
//2.通过反射创建对象
Object o = clazz.newInstance();//这样已经创建对象了
System.out.println(o);
/*以上我们newInstance()触发的是Student类中的无参构造创建对象
* 所以仅仅能创建对象,但不能给对象的属性赋值
* 所以,如果需要触发其他的构造函数来创建对象的话
* 需要先获取指定的构造函数对象*/
//3.想尝试通过其他的构造函数来创建对象
//3.1想用其他构造,先得获取,怎么获取?指定参数列表来获取
/*本方法用于获取指定参数列表的构造函数,获取的是一个构造函数对象
* 注意,这个方法的参数是目标类Student中对应构造函数的参数类型
* 而且参入的是字节码对象,不是普通的类型*/
Constructor<?> c = clazz.getConstructor(String.class,int.class);
//3.2通过刚刚获取到的构造函数对象来帮我们创建Student类的对象
Object o2 = c.newInstance("海绵宝宝", 18);
/*向下转型:之前转成父类类型的子类对象
* 如果想要使用子类的特有功能,需要重新转回成子类类型
* 因为父类对象无法使用子类的特有功能*/
//4.将多态对象转回子类对象--向下转型
Student s = (Student) o2;
System.out.println(s.getName());
System.out.println(s.age);
s.eat(999);
}
}
4.7 熟悉API
自己创建类练习,获取类中的所有资源,熟悉反射中涉及的API
4.7.1 创建物科类
package cn.review;
/* 本类用于复习反射的物科类*/
public class Student {
//1.定义成员变量
private String name;
public int age;
//2.
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//3.生成本类的无参构造和全参构造
public Student(){}
public Student(String name,int age){
this.name=name;
this.age=age;
}
//4.提供本类的普通方法/成员方法
public void play(){//无参的普通方法
System.out.println("今天大结局");
}
public void sunDay(int n){
System.out.println("国庆一共放"+n+"天");
}
//5.为了查看学生对象的具体属性和属性值,重写toString()
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
4.7.2 物科类测试
package cn.review;
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
/*本类用于反射的测试类*/
public class TestReflection {
//单元测试方法:public + void + 没有参数 + @Test
//1.通过单元测试方法,获取目标类Student对应的字节码对象
@Test
public void getClazz() throws ClassNotFoundException {
//练习获取字节码对象的3中方式
Class<?> clazz1 = Class.forName("cn.review.Student");
Class<?> clazz2 = Student.class;
Class<?> clazz3 = new Student().getClass();
System.out.println(clazz1);
//class cn.review.Student Student类对应的字节码对象
System.out.println(clazz1.getName());
//cn.review.Student 通过字节码对象获取到的Student的全路径名:包名+类名
System.out.println(clazz1.getSimpleName());
//Student 通过字节码对象获取到的Student的类名
System.out.println(clazz2.getPackage());
//package cn.review 通过字节码对象获取到的Student的包对象
System.out.println(clazz2.getPackage().getName());
//cn.review 通过包对象获取到的包名
}
//2.通过单元测试方法练习引用类型数组的定义与遍历
@Test
public void getStu(){
//1.创建Student类的三个对象
Student s1 = new Student("张三",3);
Student s2 = new Student("李四",4);
Student s3 = new Student("王麻子", 5);
//2.创建数组将刚刚的3个对象存入数组中
Student[] s = {s1,s2,s3};
//3.直接打印数组,查看数组中的元素
System.out.println(Arrays.toString(s));
//4.遍历学生数组,拿到每一个学生对象,做进一步操作
for(Student stu:s){
System.out.println(stu);
stu.play();//通过遍历到的对象执行play方法
System.out.println(stu.age);//通过遍历到的对象,打印age属性
}
}
//3.通过单元测试方法,获取Student类中的成员方法
@Test
public void getFunction() {
//1.获取字节码对象
Class<?> clazz = Student.class;
//2.通过字节码对象,获取目标类中的成员方法们
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));//打印方法参数的数组
}
}
//4.通过单元测试方法,获取Student类中的构造方法
@Test
public void getCons(){
//1.获取字节码对象
Class<?> clazz = new Student().getClass();
//2.通过字节码对象获取目标类Student的构造方法们
Constructor<?>[] cs = clazz.getConstructors();
//3.通过高效for循环遍历数组
for(Constructor c: cs){
System.out.println(c.getParameterTypes());//打印本轮遍历到的构造方法的名字
Class[] pt = c.getParameterTypes();//通过本轮遍历到的构造函数对象获取构造函数的参数类型
System.out.println(Arrays.toString(pt));
}
}
//5.通过单元测试方法,获取Student类中的成员变量
@Test
public void getFie() throws ClassNotFoundException{
//1.获取字节码对象
Class<?> clazz = Class.forName("cn.review.Student");
//2.通过字节码对象获取成员变量们
Field[] fs = clazz.getFields();
//3.遍历数组,获取每个成员变量的具体信息
/*注意:目前成员变量的修饰符必须是public的才能获取到,默认修饰符也是获取不到的*/
for(Field f : fs){
System.out.println(f.getName());//通过本轮循环到的字段对象获取字段名
System.out.println(f.getType());//通过本轮循环到的字段对象获取字段的类型
}
}
//6.通过单元测试方法,创建Student目标类的对象
@Test
public void getObject() throws Exception {
//1.获取字节码对象
Class<?> clazz = Student.class;
//2.通过反射技术,创建目标类的对象
/*反射创建对象方案1:通过触发目标类的无参构造创建对象*/
Object o = clazz.newInstance();//会抛出异常,需要throws
System.out.println(o);//这一步已经获取大了对象Student{name='null', age=0}
/*反射创建对象方案2:通过触发全参构造创建对象
* 思路:1.先获取指定的构造函数对象,需要指定构造函数的参数,传入的是.class字节码对象
* 2.通过刚刚获取到的获取到的构造函数对象,创建Student目标类的对象,并且给对象的属性赋值*/
Constructor<?> c = clazz.getConstructor(String.class,int.class);
//System.out.println(c);//public cn.review.Student(java.lang.String,int)
Object o2 = c.newInstance("赵六", 6);
System.out.println(o2);//Student{name='赵六', age=6}
}
}
5 暴力反射
指可以将程序中的私有的属性或者方法通过反射技术,暴力的获取到资源。需要使用的常见方法如下:
5.1 创建 : 测试物料类
package cn.review;
/*本类用作暴力反射测试物料类*/
public class Person {
//1.提供私有属性
private String name;
private int age;
//2.提供私有方法
private void save(int n,String s){
System.out.println("save()..."+n+s);
}
private void update(){
System.out.println("update()...");
}
}
5.2 练习 : 创建测试类
package cn.review;
import org.junit.Test;
import cn.review.Person;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/*本类用于练习暴力反射*/
public class TestReflection2 {
/*通过暴力反射获取与操作属性*/
@Test
public void getFields() throws Exception {
//1.获取字节码对象
Class<?> clazz = Person.class;
//2.获取指定的私有属性,传入的是属性名,注意抛出异常
clazz.getDeclaredField("name");
//3.根据刚刚获取到的属性对象,查看属性的信息
Field field = clazz.getDeclaredField("name");
System.out.println(field);//private java.lang.String cn.review.Person.name
// 直接打印获取到的字段对象
System.out.println(field.getType().getName());//java.lang.String
System.out.println(field.getType());//class java.lang.String
//4.设置属性的值
//4.1需要指定到底是哪个对象的name属性设置值,没有对象就创建对象
Object obj = clazz.newInstance(); //触发无参构造利用反射创建对象
//4.2暴力反射需要设置私有可见权限
field.setAccessible(true);
//4.3通过字段对象给刚刚创建好的对象obj设置属性值为海绵宝宝
//field就是我们刚刚捕获的name属性
//set(m,n)--m是给哪个对象的name属性设置值,n是设置的值是什么
field.set(obj,"海绵宝宝");
//4.4打印查看刚刚设置的属性值
//field.get(m)--field代表的就是Person类的name属性,m是查看哪个对象的这个属性值
System.out.println(field.get(obj));//海绵宝宝
}
@Test
public void getFie3() throws Exception {
//1.获取字节码对象
Class<?> clazz = Person.class;
//2.根据获取到的属性对象,查看相关信息,比如属性的类型
Field f = clazz.getDeclaredField("age");
//3.根据获取到的属性对象,查看相关信息,比如属性的类型
System.out.println(f.getType().getName());//int
//4.操作:设置属性的值:一共需要三个元素:给哪个对象【1】的哪个属性【2】设置一个什么值【3】
//4.1需要先指定给哪个对象的这个age属性设置值
Object obj = clazz.newInstance();
//4.2在给属性设置值之前,需要设置权限私有可见,否则报错
f.setAccessible(true);
//4.3通过刚刚获取到的age属性对象,给obj对象设置值
f.set(obj,17);
//4.4打印查看刚刚的属性值是否设置成功
System.out.println(f.get(obj));//17
}
//3.创建单元测试方法:通过暴力反射获取与执行Person类的私有方法
@Test
public void getFunction2() throws Exception {
//1.获取字节码对象
Class<?> clazz = Person.class;
//2.可以通过字节码对象获取某一个指定的私有方法对象
//如何确定要找哪一个方法?方法名+参数列表
Method method = clazz.getDeclaredMethod("save", int.class, String.class);
/*在执行私有的方法之前,需要设置私有可见的权限*/
method.setAccessible(true);
//3.在执行获取到的方法前,需要先指定给哪个对象做这个save()操作
//3.1没有对象就创建对象
Object obj = clazz.newInstance();
//3.2通过刚刚获取到的方法对象method给指定的对象obj做操作,注意传参
method.invoke(obj,666,"哈哈哈");
System.out.println(method);//save()...666哈哈哈
}
}