1.什么是反射?
- Reflection(反射) 是 Java 程序开发语言的特征之一
- 它允许运行中的 Java 程序对自身进行检查,或者说“自审”,也有称作“自省”。
- 反射非常强大,它甚至能直接操作程序的私有属性,被private封装的资源只能类内部访问,外部是不行的,但这个规定被反射赤裸裸的打破了。
- 反射就像一面镜子,它可以在运行时获取一个类的所有信息,可以获取到任何定义的信息(包括成员变量,成员方法,构造器等),并且可以操纵类的字段、方法、构造器等部分。
2.为什么需要反射?
在后面的学习中,会学习框架,有一个框架Spring就是一个非常专业且功能强大的产品,它可以帮我们创建对象,管理对象。以后我无需手动new对象,直接从Spring提供的容器中的Beans获取即可。Beans底层其实就是一个Map,最终通过getBean(“user”)来获取。而这其中最核心的实现就是利用反射技术。
总结:反射前提--类不是你创建的,而是你同事或者直接是第三方公司,此时你要获得这个类的底层功能调用,就需要反射技术实现。
2.1单元测试方法
- 我们进行开发测试常用的一种手段
- 由于是局部测试,互不干扰,写法简单,灵活性高,所以经常使用
- 格式:@Test + public + void + getXxx(没有参数){ }
- 引入单元测试的时候注意导包:org.junit.Test
3.反射需要使用API
反射实现思路:首先需要获取字节码对象(保存了目标类的所有关键信息),后面再调用常用方法实现反射
3.1获取字节码对象的三种方式
1)Class.forName("类的全路径包名.类名");
2)类名.class;
3)new对象.getClass();
3.2常用方法
clazz.getName() | 获取完整类名 |
clazz.getPackage().getName() | 获取包名 |
clazz.getSimpleName() | 获取类名 |
getFields() | 获取所有公开的成员变量,包括继承变量 |
getDeclaredFields() | 获取本类定义的成员变量,包括私有,但不包括继承的变量 |
getField(变量名) | 获取单个成员变量 |
getDeclaredField(变量名) | 获取本类定义的单个成员变量,包括私有,但不包括继承的变量 |
getConstructor(参数类型列表) | 获取公开的构造方法 |
getConstructors() | 获取所有的公开的构造方法 |
getDeclaredConstructors() | 获取所有的构造方法,包括私有 |
etDeclaredConstructor(int.class,String.class) | 获取指定的构造方法,包括私有 |
getMethods() | 获取所有可见的方法,包括继承的方法 |
getMethod(方法名,参数类型列表) | 获取指定可见的方法,包括继承的方法 |
getDeclaredMethods() | 获取本类定义的的方法,包括私有,不包括继承的方法 |
getDeclaredMethod(方法名,int.class,String.class) | 获取本类定义的指定方法,包括私有,不包括继承的方法 |
clazz.newInstance(); | 执行无参构造创建对象 |
clazz.getConstructor(int.class,String.class) | 获取构造方法 |
clazz.newInstance(666,”海绵宝宝”); | 执行含参构造创建对象 |
clazz.getDeclaredField(变量名); | 获取变量 |
clazz.setAccessible(true); | 使私有成员允许访问 |
f.set(实例,值); | 为指定实例的变量赋值,静态变量,第一参数给nul |
f.get(实例); | 访问指定实例变量的值,静态变量,第一参数给null |
Method m = clazz.getDeclaredMethod(方法名,参数类型列表); | |
m.setAccessible(true); | 使私有方法允许被调用 |
m.invoke(实例,参数数据); | 让指定实例来执行该方法 |
4.反射的应用
4.1创建:测试物料类
package cn.tedu.reflection;
/*本类用于测试反射预先准备的物料类*/
public class Student {
//1.定义成员变量--属性
String name;
int age;
//2.定义构造方法
public Student() {//注意手动添加无参构造,防止被后续添加的含参构造覆盖
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
//3.定义普通方法--功能
public void eat(){
System.out.println("晚饭");
}
public void play(int n) {
System.out.println("一天学习"+n+"个小时");
}
}
4.2练习:获取类对象(类的字节码对象)
package cn.tedu.reflection;
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.util.Arrays;
/*本类用于测试反射*/
public class TestReflect1 {
//1.创建程序的入口函数main()方法--不用
/*单元测试方法:是java测试的最小单位,使用灵活,推荐使用
* 语法要求:@Test + void + 没有参数 + public
* 注意:使用时需要导包:Add“jUnit4 ”to classPath
* 导的包名:import org.junit.Test;*/
//2.通过单元测试方法获取类的字节码对象
@Test
public void getClazz() throws Exception {
//本方法的参数是目标类的全路径名(包名.类名)
//Copy-->CopyPath-->cn.tedu.reflection.Student
Class<?> student1 = Class.forName("cn.tedu.reflection.Student");//抛出异常
Class<Student> student2 = Student.class;
//本方法创建的是一个匿名对象,没有名字
Class<?> student3 = new Student().getClass();
System.out.println(student1);//class cn.tedu.reflection.Student
System.out.println(student1.getName());//cn.tedu.reflection.Student,获取完整类名
System.out.println(student1.getPackage().getName());//cn.tedu.reflection,获取包名
System.out.println(student1.getSimpleName());//Student//获取类名
}
}
4.3练习:获取类的构造方法
通过字节码对象+反射技术获取目标类的所有构造方法
getConstructors();--获取构造方法
getName()--获取方法名
getParameterTypes()--获取构造方法参数列表中参数的类型
ackage cn.tedu.reflection;
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.util.Arrays;
/*本类用于测试反射*/
public class TestReflect1 {
//3.通过单元测试获取构造方法
@Test
public void getConstruct(){
//3.1需要获取字节码对象Class<?>对象
Class<?> clazz = Student.class;
//3.2获取多个构造方法
//clazz.getConstructor();//获取单个构造方法
Constructor<?>[] cs = clazz.getConstructors();//获取多个构造方法,需要数组
//3.3通过循环获取每个构造函数
//for( 遍历得到的每个元素的类型 名字: 要遍历的元素){}
for(Constructor c : cs){
//本轮循环中拿到的构造函数对象
System.out.println(c.getName());//获取构造函数的名字--完整的类名
Class[] cp = c.getParameterTypes();//获取构造函数参数的类型,可能有多个,使用数组来保存
System.out.println(Arrays.toString(cp));//查看数组
}
}
}
4.4练习:单元测试获取成员方法(普通方法)
getMethods();--获取成员方法
getName()--获取方法名
getParameterTypes()--获取成员方法参数列表中参数的类型
package cn.tedu.reflection;
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;
public class TestReflection {
//单元测试获取成员方法(普通方法)
@Test
public void getFunction(){
//1.获取字节码对象方式三取一
Class<Student> clazz = Student.class;
//2.获取所有成员方法,存储到数组中
Method[] ms = clazz.getMethods();
//3.遍历数组,获取每个方法的信息
/*注意遍历成员方法也会遍历出父类的成员方法及类型,没有父类默认顶级父类Object*/
for (Method m :ms){
System.out.println(m.getName());//获取方法名
Class<?>[] pt = m.getParameterTypes();//获取参数类型
System.out.println(Arrays.toString(pt));//查看参数类型
}
}
}
4.5练习:单元测试获取成员变量
getFields();--获取成员变量
get.Name();--获取变量名
getType().getName();--获取变量类型
package cn.tedu.reflection;
import org.junit.Test;
import java.lang.reflect.Field;
public class TestReflection {
//单元测试获取成员变量
@Test
public void getFields() throws ClassNotFoundException {
//获取字节码对象
Class<?> clazz = Class.forName("cn.tedu.reflection.Student");
//获取所有成员变量,存到数组中
/*注意:目前成员变量的权限修饰符必须是public才能获取到,
*而默认的修饰符获取不到成员变量*/
Field[] fs = clazz.getFields();
//遍历数组得到每个成员变量
for (Field f:fs){
System.out.println(f.getName());//获取变量名
System.out.println(f.getType().getName());//获取变量类型
}
}
}
4.6反射创建对象
两种方式:
方式一:通过字节码对象直接调用newInstance(),触发无参构造来创建对象
方式二:先获取指定的构造函数,再通过这个构造函数对象来创建对象
clazz.getConstructor(int.class,String.class) | 获取构造方法 |
clazz.newInstance(666,”海绵宝宝”); | 执行含参构造创建对象 |
package cn.tedu.reflection;
import org.junit.Test;
import java.lang.reflect.Constructor;
public class TestReflection {
//通过反射创建实例(创建对象)
/*方式一:通过字节码对象直接调用newInstance(),触发无参构造来创建对象
* 方式二:先获取指定的构造函数,再通过这个构造函数对象来创建对象*/
@Test
public void getObject() throws Exception{
//1.获取目标资源对应的字节码对象
Class<Student> clazz = Student.class;
//2.创建对象
//Student obj = clazz.newInstance();
Object obj = clazz.newInstance();/*这是无参构造触发创建的对象*/
//System.out.println(obj);//cn.tedu.reflection.Student@78e03bb5
/*注意:创建对象默认查看的是地址值,想查看具体内容,需要在Student类中重写toString()*/
System.out.println(obj);//Student{name='null', age=0}
System.out.println("方式二:");
//Class<Student> clazz = Student.class;
//3.要指定调用哪个构造方法来创建对象,首先要获取其指定的含参构造,参数类型要一一对应
/*注意:这个方法参数类型是构造方法参数类型对应的字节码对象*/
Constructor<Student> c = clazz.getConstructor(String.class, int.class);
//Student obj2 = c.newInstance("张三", 18);
Object obj2 = c.newInstance("张三", 18);
System.out.println(obj2);
/*注意:此处需要把父类型Object类型的obj强制转换成子类型Student的s
* 为什么要强转?因为此处我想要使用子类的特有功能,而父类无法使用子类的特有功能
* Object父类中是没有Student子类自己的属性与功能的
* 所以,我们把之前看作是父类型的子类对象转回成子类型,去调用子类的特有功能
* 这个现象叫作:向下造型
* 【注意:向下造型之前必须先向上造型,纯纯的父类对象不能向下造型】*/
Student s = (Student) obj2;
System.out.println(s.name);
System.out.println(s.age);
s.study(666);
}
}
5.暴力反射
概念:指可以将程序中的私有的属性或者方法通过反射技术,暴力的获取到资源。需要使用的常见方法如下:
5.1创建:测试物料类
package cn.tedu.reflection;
public class Person {
//1.提供私有属性
private String name;
private int age;
//2.提供私有方法
private void add(String n,int a){
System.out.println("添加的学员"+n+"今年"+a+"岁了");
}
private void eat(){
System.out.println("坚持一会儿就干饭了!");
}
}
5.2练习:创建测试类
package cn.tedu.reflection;
import org.junit.Test;
import java.lang.reflect.Field;
/*本类用于测试暴力反射*/
public class TestReflect2 {
//单元测试方法的格式:@Test+public+void+无参
/*单元测试1:暴力反射获取并设置私有属性值*/
@Test
public void getFields() throws Exception{
//1.获取目标资源对应的字节码对象
Class<?> clazz = Person.class;
//2.获取指定名称的私有属性
Field field = clazz.getDeclaredField("name");
//3.根据获取到的属性对象拿到它的类型
System.out.println(field.getType().getName());//java.lang.String,名字
System.out.println(field.getType());//class java.lang.String,获取对象
//4.设置属性值
//4.1没有对象就通过反射的方式创建对象
Object obj = clazz.newInstance();
//4.2暴力反射!!!注意:要设置私有可见,不然访问不了
field.setAccessible(true);
//4.3刚刚获取到的name属性对象设置值
/*注意:需要设置两个参数:哪个对象的属性值,以及要设置一个什么值
* set(m,n) m--给哪个对象设置值 n--给这个对象的属性设置一个什么值*/
field.set(obj,"林冲");
//4.4打印给属性设置的值
/*注意:需要指定获取的是哪个对象的属性值*/
System.out.println(field.get(obj));
}
/*单元测试2:通过暴力反射获取与使用方法*/
@Test
public void getFunction() throws Exception {
//1.获取字节码对象
Class<Person> clazz = Person.class;
//2.通过暴力反射获取私有方法
/*本方法的参数列表是getDeclaredMethod(name,x,y,z...)
* name--要获取的方法的名字
* x,y,z...--可变参数,值得是要获取方法的参数类型,注意是字节码对象"class"*/
Method method = clazz.getDeclaredMethod("add", String.class, int.class);
//3.1没有对象就通过反射创建对象
Person obj = clazz.newInstance();
//3.2如果想要执行私有方法,必须设置为私有资源可见
method.setAccessible(true);
//3.3执行获取到的私有方法
/*invoke()用来调用目标方法,参数1是执行哪个对象的这个方法
* 后续的参数是执行目标方法时传入的参数,这个参数是可变的参数,根据目标方法的具体情况来写*/
method.invoke(obj,"李四",18);
}
}