反射
1、概述
- 反射是指对于任何一个Class类,在 “运行的时候” 都可以直接得到这个类的全部成分。
- 在运行时:
- 可以直接得到这个类的构造器对象:Constructor
- 可以直接得到这个类的成员变量对象:Field
- 可以直接得到这个类的成员方法对象:Method
- 这种运行时动态获取类信息自己动态调用类中成分的能力称为:Java语言的反射机制。
(1)反射的关键
-
反射的第一步都是先得到编译后的Class类对象,然后就可以得到Class的全部成分。
总结
- 反射的基本作用、关键?
- 反射是在运行时获取类的字节码文件对象:然后可以解析类中的全部成分。
- 反射的核心思想和关键是:得到编译以后的class文件对象。
2、获取类对象
-
反射的第一步:获取Class类的对象
(1)范例
package com.app.d2_reflect_class;
public class Student {
}
package com.app.d2_reflect_class;
/**
目标:学习反射第一步:获取Class类的对象的方式
*/
public class Test {
public static void main(String[] args) throws Exception {
// 1、Class类中的一个静态方法:forName(全限名:包名 + 类名)
Class c1 = Class.forName("com.app.d2_reflect_class.Student");
System.out.println("c1 = " + c1);
// 2、类名.class
Class c2 = Student.class;
System.out.println("c2 = " + c2);
// 3、对象.getClass():获取对象对应类的Class对象
Student stu = new Student();
Class c3 = stu.getClass();
System.out.println("c3 = " + c3);
}
}
c1 = class com.app.d2_reflect_class.Student
c2 = class com.app.d2_reflect_class.Student
c3 = class com.app.d2_reflect_class.Student
Process finished with exit code 0
总结
- 反射的第一步是啥?
- 获取Class类对象,如此才可以解析类的全部成分。
- 获取Class类的对象的三种方式是啥?
- 方式一:Class c1 = Class.forName(“全类名”);
- 方式二:Class c2 = 类名.class;
- 方式三:Class c3 = 对象.getClass();
3、获取构造器对象
-
使用反射技术获取构造器对象并使用:
- 反射第一步是先得到类对象,然后从类对象中获取类的成分对象。
(1)Class类中用于获取构造器的方法
方法名称 | 说明 |
---|---|
Constructor< ? >[] getConstructors() | 返回所有构造器对象的数组(只能拿public的) |
Constructor< ? >[] getDeclaredConstructors() | 返回所有构造器对象的数组,存在就能拿到 |
Constructor< T > getConstructor(Class< ? >… parameterTypes) | 返回单个构造器对象(只能拿public的) |
Constructor< T > getDeclaredConstructor(Class< ? >… parameterTypes) | 返回单个构造器对象,存在就能拿到 |
范例
package com.app.d3_reflect_constructor;
/**
学生类
*/
public class Student {
private String name;
private int age;
public Student() {
System.out.println("无参构造器执行");
}
public Student(String name, int age) {
System.out.println("有参数构造器执行");
this.name = name;
this.age = age;
}
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;
}
}
package com.app.d3_reflect_constructor;
import org.junit.Test;
import java.lang.reflect.Constructor;
/**
目标:学习使用反射技术获取构造器对象并使用
*/
public class TestStudent01 {
// 第一步:获取类对象
public static Class c = Student.class;
/**
getConstructors:
获取所有public修饰的构造器对象(只能拿有public修饰的)
*/
@Test
public void getConstructors() {
System.out.println("1. 获取所有public修饰的构造器对象:");
// 第二步:返回所有public修饰的构造器对象的数组
Constructor[] constructors = c.getConstructors();
// 第三步:遍历所有public修饰的构造器对象的数组
for (Constructor constructor : constructors) {
System.out.println(constructor.getName() + " ---> " + constructor.getParameterCount());
}
System.out.println();
}
/**
getDeclaredConstructors:
获取所有构造器对象
*/
@Test
public void getDeclaredConstructors() {
System.out.println("2. 获取所有构造器对象:");
// 第二步:返回所有构造器对象的数组
Constructor[] declaredConstructors = c.getDeclaredConstructors();
// 第三步:遍历所有构造器对象的数组
for (Constructor declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor.getName() + " ---> " + declaredConstructor.getParameterCount());
}
System.out.println();
}
/**
getConstructor:
获取某个有public修饰的构造器对象(只能拿public修饰的)
*/
@Test
public void getConstructor() throws Exception {
System.out.println("3. 获取某个有public修饰的构造器对象:");
// 第二步:获取某个有public修饰的无参数构造器对象
System.out.println("获取有public修饰的无参数构造器对象:");
Constructor constructor = c.getConstructor();
System.out.println(constructor.getName() + " ---> " + constructor.getParameterCount());
// 第三步:获取某个有public修饰的有参数构造器对象
System.out.println("获取有public修饰的有参数构造器对象:");
Constructor constructor1 = c.getConstructor(String.class, int.class);
System.out.println(constructor1.getName() + " ---> " + constructor1.getParameterCount());
System.out.println();
}
/**
getDeclaredConstructor:
获取某个构造器对象
*/
@Test
public void getDeclaredConstructor() throws Exception {
System.out.println("4. 获取某个构造器对象:");
// 第二步:获取无参数构造器对象
System.out.println("获取无参数构造器对象:");
Constructor declaredConstructor = c.getDeclaredConstructor();
System.out.println(declaredConstructor.getName() + " ---> " + declaredConstructor.getParameterCount());
// 第三步:获取有参数构造器对象
System.out.println("获取有参数构造器对象:");
Constructor declaredConstructor1 = c.getDeclaredConstructor(String.class, int.class);
System.out.println(declaredConstructor1.getName() + " ---> " + declaredConstructor1.getParameterCount());
System.out.println();
}
}
(3)Constructor类中用于创建对象的API
- 获取构造器的作用依然是初始化一个对象返回。
方法名称 | 说明 |
---|---|
T newInstance(Object… initargs) | 根据指定的构造器创建对象 |
public void setAccessible(boolean flag) | 设置为true,表示取消访问检查,进行暴力反射 |
范例
package com.app.d3_reflect_constructor;
/**
老师类
*/
public class Teacher {
private String name;
private int age;
private Teacher() {
System.out.println("无参构造器被执行");
}
public Teacher(String name, int age) {
System.out.println("有参构造器被执行");
this.name = name;
this.age = age;
}
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;
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
package com.app.d3_reflect_constructor;
import org.junit.Test;
import java.lang.reflect.Constructor;
/**
目标:学习使用反射技术获取构造器对象并使用
*/
public class TestTeacher02 {
@Test
public void getDeclaredConstructor() throws Exception {
// a. 获取类对象
Class c = Teacher.class;
// b. 获取无参数构造器对象
Constructor constructor = c.getDeclaredConstructor();
System.out.println(constructor.getName() + " ---> " + constructor.getParameterCount());
// c. 根据指定的构造器创建对象
constructor.setAccessible(true); // 权限被打开
Teacher t = (Teacher) constructor.newInstance(); // 如果指定的构造器是private修饰的,请使用上一行代码进行暴力反射(暴力拆箱)
System.out.println(t);
System.out.println("-----------------------------");
// d. 获取有参数构造器对象
Constructor constructor1 = c.getDeclaredConstructor(String.class, int.class);
// e. 根据指定的构造器创建对象
Teacher t1 = (Teacher) constructor1.newInstance("薰悟空", 2000);
System.out.println(t1);
}
}
总结
- 利用反射技术获取构造器对象的方式?
- getDeclaredConstructors()
- getDeclaredConstructor(Class<?>… parameterTypes)
- 反射得到的构造器可以做什么?
- 依然是创建对象的
- public newInstance(Object… initargs)
- 如果是非public的构造器,需要打开权限(暴力反射),然后再创建对象
- setAccessible(boolean)
- 反射可以破坏封装性,私有的也可以执行了。
- 依然是创建对象的
4、获取成员变量对象
-
使用反射技术获取成员变量对象并使用:
- 反射的第一步是先得到类对象,然后从类对象中获取类的成分对象。
(1)Class类中用于获取成员变量的API
方法名称 | 说明 |
---|---|
Field[] getFields() | 返回所有成员变量对象的数组(只能拿public的) |
Field[] getDeclaredFields() | 返回所有成员变量对象的数组,存在就能拿到 |
Field getField(String name) | 返回单个成员变量对象(只能拿public的) |
Field getDeclaredField(String name) | 返回单个成员变量对象,存在就能拿到 |
(2)Field类中用于取值、赋值的API
方法名称 | 说明 |
---|---|
void set(Object obj, Object value) | 赋值 |
Object get(Object obj) | 取值 |
范例
package com.app.d4_reflect_field;
public class Student {
private String name;
private int age;
public static String schoolName;
public static final String COUNTRY = "中国";
public Student() {
System.out.println("无参构造器被执行");
}
public Student(String name, int age) {
System.out.println("有参构造器被执行");
this.name = name;
this.age = age;
}
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 static String getSchoolName() {
return schoolName;
}
public static void setSchoolName(String schoolName) {
Student.schoolName = schoolName;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
package com.app.d4_reflect_field;
import org.junit.Test;
import java.lang.reflect.Field;
/**
目标:使用反射技术获取成员变量:取值和赋值
Field的方法:给成员变量赋值和取值
void set(Object obj, Object value): 给对象注入某个成员变量的值
Object get(Object obj): 获取对象的成员变量的值
void setAccessible(true): 暴力反射,设置为可以直接访问私有类型的属性
Class getType(): 获取属性的类型,返回class对象
String getName(): 获取属性的名称。
*/
public class TestStudent01 {
@Test
public void setField() throws Exception {
// 1、先获取类对象
Class c = Student.class;
// 2、提取所有成员变量对象的数组
Field[] fields = c.getDeclaredFields();
// 3、遍历一下
for (Field field : fields) {
System.out.println(field.getName() + " ===> " + field.getType());
}
System.out.println("-------------------------");
// 4、提取某个成员变量
Field country = c.getDeclaredField("COUNTRY");
// 5、获取对象的成员变量的值
String coun = (String) country.get(c);
System.out.println(coun);
Field nameF = c.getDeclaredField("name");
// 暴力反射,设置为可以直接访问私有类型的属性
nameF.setAccessible(true);
System.out.println("-------------------------");
// 6、给对象注入某个成员变量的值: 赋值
Student s = new Student(); // 创建对象
nameF.set(s, "薰悟空");
System.out.println(s);
System.out.println("-------------------------");
// 7、获取对象的成员变量的值: 取值
String name = (String) nameF.get(s);
System.out.println(name);
}
}
总结
- 利用反射技术获取成员变量的方式?
- 获取类中成员变量对象的方法
- getDeclaredFields()
- getDeclaredField(String name)
- 获取类中成员变量对象的方法
- 反射得到成员变量可以做啥?
- 依然是在某个对象中取值和赋值
- void set(Object obj, Object value)
- Object get(Object obj)
- 如果某成员变量是非public的,需要打开权限(暴力反射),然后再取值、赋值
- setAccessible(boolean)
- 依然是在某个对象中取值和赋值
5、获取方法对象
-
使用反射技术获取方法对象并使用:
- 反射的第一步是先得到类对象,然后从类对象中获取类的成分对象。
(1)Class类中用于获取成员方法的API
方法名称 | 说明 |
---|---|
Method[] getMethods() | 返回所有成员方法对象的数组(只能拿public的) |
Method[] getDeclaredMethods() | 返回所有成员方法对象的数组,存在就能拿到 |
Method getMethod(String name, Class< ? >… parameterTypes) | 返回单个成员方法对象(只能拿到public的) |
Method getDeclaredMethod(String name, Class< ? >… parameterTypes) | 返回单个成员方法对象,存在就能拿到 |
(2)Method类中用于触发执行的方法
方法名称 | 说明 |
---|---|
Object invoke(Object obj, Object… args) | 运行方法 参数一:用obj对象调用该方法 参数二:调用方法的传递的参数(如果没有就不写) 返回值:方法的返回值(如果没有就不写) |
范例
package com.app.d5_reflect_method;
public class Cat {
private String name;
public Cat() {
}
public void run() {
System.out.println("猫跑得飞快~~");
}
// 私有方法
private void eat() {
System.out.println("猫喜欢吃鱼!");
}
private String eat(String food) {
System.out.println("猫吃" + food);
return "吃得非常开心~~";
}
public static void inAdd() {
System.out.println("我们正在学习Java!");
}
public Cat(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
}
package com.app.d5_reflect_method;
import org.junit.Test;
import java.lang.reflect.Method;
/**
目标:使用反射技术获取方法对象并触发执行
*/
public class TestCat01 {
// a.先获取对象类
public static Class c = Cat.class;
/**
* 1、获取所有方法对象的数组
*/
@Test
public void getDeclaredMethods() {
System.out.println("1、获取所有方法对象的数组:");
// b.获取所有方法对象的数组
Method[] methods = c.getDeclaredMethods();
// c.遍历一下
for (Method method : methods) {
System.out.println("方法名称:" + method.getName() +
",返回值类型:" + method.getReturnType() +
",参数个数:" + method.getParameterCount());
}
System.out.println();
}
/**
* 2、获取某个方法对象
*/
@Test
public void getDeclaredMethod() throws Exception {
System.out.println("2、获取某个方法对象:");
// b.获取某个方法对象
Method method = c.getDeclaredMethod("eat");
Method method1 = c.getDeclaredMethod("eat", String.class);
Method method2 = c.getDeclaredMethod("inAdd");
// 如果方法是private修饰的,就暴力打开权限
method.setAccessible(true);
method1.setAccessible(true);
// c.触发方法的执行
Cat cat = new Cat();
// 注意:方法如果是无返回值的,那么返回的是null
Object result = method.invoke(cat);
System.out.println(result);
System.out.println("-----------------");
Object result1 = method1.invoke(cat, "猫粮");
System.out.println(result1);
System.out.println("-----------------");
method2.invoke(cat);
System.out.println();
}
}
总结
- 利用反射技术获取成员方法对象的方式?
- 获取类中成员方法对象
- getDeclaredMethods()
- getDeclaredMethod(String name, Class<?>… parameterTypes)
- 获取类中成员方法对象
- 反射得到成员方法对象可以做啥?
- 依然是在某个对象中触发该方法执行
- Object invoke(Object obj, Object… args)
- 如果某成员方法是非public修饰的,需要打开权限(暴力反射),然后再触发执行
- setAccessible(boolean)
- 依然是在某个对象中触发该方法执行
6、反射的作用[拓展]
- 目前只是拓展一下反射的基本作用,要是真想知道反射的真正强大之处,还需学到高级框架的底层源码才会知道!!
- 所以不着急,慢慢来!!
(1)绕过编译阶段为集合添加数据
-
反射是作用在运行时的技术,此时集合的泛型将不能产生约束了,此时是可以 为集合存入其他任意类型的元素的。
-
之前在创建集合时会声明泛型,比如以下程序中声明的泛型是Integer类型,那么此集合只能添加Int类型的数据,一旦添加其他类型的数据就会在编译时报错,但是如果用反射技术,就可以强行往声明了泛型的集合添加其他类型的数据:
ArrayList<Integer> list = new ArrayList<>(); list.add(100); // list.add("中国"); // 报错 list.add(99);
-
原因:
- 泛型只是在编译阶段可以约束集合只能操作某种数据类型,在 编译成Class文件进入运行阶段时,其真实类型都是 ArrayList 了,泛型相当于被擦除了。
package com.app.d6_reflect_genericity; import java.lang.reflect.Method; import java.util.ArrayList; /** 目标:反射实现泛型擦除后,加入其他类型的元素 */ public class ReflectDemo1 { public static void main(String[] args) throws Exception { // 1、创建两个不同泛型的集合 ArrayList<String> list1 = new ArrayList<>(); ArrayList<Integer> list2 = new ArrayList<>(); // 2、得到两个集合的class文件 System.out.println(list1.getClass()); System.out.println(list2.getClass()); // 3、判断两个集合的class文件是否相同 System.out.println(list1.getClass() == list2.getClass()); // true System.out.println("--------------------------"); // 4、创建一个泛型的集合,用于实现泛型擦除后,加入不同类型的数据 ArrayList<Integer> list3 = new ArrayList<>(); list3.add(123); list3.add(24); // list3.add("我爱中国!"); // 报错 // list3.add(123.33); // 报错 // 5、得到泛型集合list3的Class对象 Class c = list3.getClass(); // 6、得到c类中的add方法 Method add = c.getDeclaredMethod("add", Object.class); // 7、添加任意类型的数据到list3集合中 boolean rs1 = (boolean) add.invoke(list3, "我爱中国!"); boolean rs2 = (boolean) add.invoke(list3, 123.33); System.out.println(rs1); System.out.println(rs2); System.out.println(list3); System.out.println("--------------------------"); // 另一种方式实现泛型擦除后,加入其他类型的元素 // a.将泛型集合list3赋值给没有约定泛型的集合list4 ArrayList list4 = list3; // b.添加其他类型的元素 list4.add("敖德彪"); list4.add(false); list4.add(66.6); System.out.println(list3); } }
class java.util.ArrayList class java.util.ArrayList true -------------------------- true true [123, 24, 我爱中国!, 123.33] -------------------------- [123, 24, 我爱中国!, 123.33, 敖德彪, false, 66.6] Process finished with exit code 0
-
总结
- 反射为何可以给约定了泛型的集合存入其他类型的元素?
- 编译成Class文件进入运行阶段时,泛型会自动擦除。
- 反射是作用在运行时的技术,此时已经不存在泛型了。
(2)通用框架的底层原理
-
案例:反射做通用框架
- 需求:
- 给你任意一个对象,在不清楚对象字段的情况,可以把对象的字段名称和对应值存储到文件中去。
- 分析实现:
- 定义一个方法,可以接收任意类的对象
- 每次收到一个对象后,需要解析这个对象的全部成员变量名称
- 这个对象可能是任意的,那么怎么样才可以知道这个对象的全部成员变量名称呢?
- 使用反射技术获取对象的Class类对象
- 获取全部成员变量信息
- 遍历全部成员变量信息
- 提取该成员变量在对象中的具体值
- 将成员变量名称和值存入到文件中去即可
package com.app.d7_reflect_framework; /** 1、创建对象:学生类 */ public class Student { /** 学生的属性:姓名、性别、年龄、班级、爱好 */ private String name; private char sex; private int age; private String className; private String hobby; /** 无参数构造器 */ public Student() { } /** * 有参数构造器 * @param name 姓名 * @param sex 性别 * @param age 年龄 * @param className 班级 * @param hobby 爱好 */ public Student(String name, char sex, int age, String className, String hobby) { this.name = name; this.sex = sex; this.age = age; this.className = className; this.hobby = hobby; } /** * 提供全套get、set方法:暴露其取值和赋值 */ public String getName() { return name; } public void setName(String name) { this.name = name; } public char getSex() { return sex; } public void setSex(char sex) { this.sex = sex; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getClassName() { return className; } public void setClassName(String className) { this.className = className; } public String getHobby() { return hobby; } public void setHobby(String hobby) { this.hobby = hobby; } /** * 重写toString方法 */ @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", sex=" + sex + ", age=" + age + ", className='" + className + '\'' + ", hobby='" + hobby + '\'' + '}'; } }
package com.app.d7_reflect_framework; /** 1、创建对象:老师类 */ public class Teacher { /** 老师的属性:姓名、性别、薪水 */ private String name; private char sex; private double salary; /** 无参数构造器 */ public Teacher() { } /** 有参数构造器 * @param name 姓名 * @param sex 性别 * @param salary 薪水 */ public Teacher(String name, char sex, double salary) { this.name = name; this.sex = sex; this.salary = salary; } /** 提供get、set方法:暴露其取值和赋值 */ public String getName() { return name; } public void setName(String name) { this.name = name; } public char getSex() { return sex; } public void setSex(char sex) { this.sex = sex; } public double getSalary() { return salary; } public void setSalary(double salary) { this.salary = salary; } /** 重写toString方法 */ @Override public String toString() { return "Teacher{" + "name='" + name + '\'' + ", sex=" + sex + ", salary=" + salary + '}'; } }
package com.app.d7_reflect_framework; import java.io.FileOutputStream; import java.io.PrintStream; import java.lang.reflect.Field; /** 2、创建框架类 */ public class MyBatisUtil { /** * 3、定义保存所有类型对象的方法 * @param obj 传入的对象 */ public static void saveAllObject(Object obj) { try ( // d. 创建打印流,包装一个文件字节输出流:参数一是输出目标文件相对路径;参数二是每次输出都会在文件末尾追加数据。 PrintStream ps = new PrintStream(new FileOutputStream("day14-junit-reflect-annotation-proxy-app/src/data.txt", true)); ) { // a. 得到该对象的Class类对象 Class c = obj.getClass(); // e. 输出当前类名到文件中 // c.getSimpleName(): 获取当前类名;c.getName(): 获取类的全限名(包名+类名) ps.println("===========" + c.getSimpleName() + "==========="); // b. 提取该对象的全部成员变量 Field[] fields = c.getDeclaredFields(); // c. 遍历全部成员变量,得到信息 for (Field field : fields) { // (c-1) 获取成员变量的名称 String name = field.getName(); // (c-2) 由于对象的成员变量都是私有的,所以需要暴力反射 field.setAccessible(true); // (c-3) 获取该成员变量在obj对象中的具体值(取值) String value = field.get(obj) + ""; // 得到值后拼接一个空字符串:意思就是把任何值都转成字符串存入到文件中 // f. 将成员变量名称和具体值输出到目标文件中 ps.println(name + "=" + value); } System.out.println("保存成功!"); } catch (Exception e) { e.printStackTrace(); } } }
package com.app.d7_reflect_framework; /** 目标:通过使用反射做通用框架支持保存任意对象的具体信息,可以理解到反射的作用 需求: 给你任意一个对象,在不清楚对象字段的情况,可以把对象的字段名称和对应值存储到文件中。 */ public class ReflectDemo { public static void main(String[] args) { // 4、创建学生对象 Student stu1 = new Student(); stu1.setName("孙悟空"); stu1.setSex('男'); stu1.setAge(2000); stu1.setClassName("天宫舔狗99班"); stu1.setHobby("大闹天宫"); // a. 将此学生对象的信息保存到目标文件中 MyBatisUtil.saveAllObject(stu1); // 5、创建老师对象 Teacher tea1 = new Teacher(); tea1.setName("玉皇大帝"); tea1.setSex('男'); tea1.setSalary(10000); // a. 将此老师对象的信息保存到目标文件中 MyBatisUtil.saveAllObject(tea1); } }
- 需求:
测试
总结
- 反射的作用是?
- 可以在运行时得到一个类的全部成分,然后操作
- 可以破坏封装性。(很突出)
- 也可以破坏泛型的约束性。(很突出)
- 更重要的用途是适合做Java高级框架