反射

引入

在项目中有 People 类,包含 name 和age属性。并生成 getter/setter 和 toString 方法。

public class People {
private String name;
private int age;

//...
}

如果我们想给 name 赋值 ’张三‘ ,age 赋值 0 的话:

public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入赋值的属性名称");
String filed = sc.next();
System.out.println("请给这个属性赋值");
String value = sc.next();
People people = new People();
switch (filed) {
case "name":
people.setName(value);
break;
case "age":
people.setAge(Integer.parseInt(value));
break;
}
System.out.println(people);
}

问:如果现在给 People 中添加 n 个属性。并让程序支持分别对 n 个属性赋值,需要如何完成?

答:在 switch 中添加 n 个 case 分支。无论添加多少个属性,就可以添加多少个分支。

是不是很麻烦?

可以使用 Java 中的反射技术,解决程序在运行过程中改变属性值。

介绍

反射(Reflect):Java 中提供一种可以在运行时操作任意类中的属性和方法的技术。

反射在运行之前是不需要类中结构的,运行过程中,只要能够获取该类的字节码文件,就可以随意修改类中属性的值,随意调用类中方法。让程序变得非常灵活。

Class 类

java.lang.Class 表示类的类型。

一个 Class 类对象就代表了某个类的字节码对象。获取到这个类的字节码对象后该类中所有的内容都会被知道,然后就可以对这个类进行操作。

Class 类是 Java 反射机制的起源和入口 / 用于获取与类相关的各种信息 /提供了获取类信息的相关方法/ Class 类继承自 Object 类。

image-20240824213154602

前置代码

src/Teacher

import java.util.List;

public class Teacher {

private int age;
private String name;

public Teacher(){
System.out.println("无参构造执行");
}

private Teacher(int age, String name) {
System.out.println("有参构造执行");
this.age = age;
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
System.out.println("执行了赋值操作");
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int add(List<String> list, String str2){
System.out.println(list+"--"+str2);
return 100;
}
}

创建 Class 对象

通过 class 关键字

可以通过 类名.class 获取到这个类的字节码对象。

Class<Teacher> clazz = Teacher.class;

通过 forName 方法

可以通过 Class 类中静态方法 forName 获取到类的字节码对象。

forName 方法抛出了 Checked 异常 ClassNotFoundException。

如果加载类时,没有发现这个类的字节码文件,就会出现这个异常。

public static Class<?> forName(String className) throws ClassNotFoundException {
//...
}

代码示例:

forName(String) 参数是类的全限定路径(包名+类名)。

Class<?> clazz = Class.forName("Teacher");

通过 getClass 方法

Teacher teacher = new Teacher();
Class<? extends Teacher> clazz = teacher.getClass();

Class 对象常用方法

获取一个类的结构信息(类对象 Class 对象)

泛型可省略

Class clazz = Class.forName("why.Dog");
从类对象中获取类的各种结构信息

获取基本结构信息:

System.out.println(clazz.getName()); // 获得包名+类名
System.out.println(clazz.getSimpleName()); // 获得类名
System.out.println(clazz.getSuperclass()); // 获得父类包名+类名
System.out.println(Arrays.toString(clazz.getInterfaces()));

输出:

Teacher
Teacher
class java.lang.Object
[]

使用 Class 获得构造方法

批量获得构造方法集

只能得到 public 修饰的构造方法:

Constructor[] constructors = clazz.getConstructors();

得到所有的构造方法:

Constructor[] constructors = clazz.getDeclaredConstructors(); 

获取单个构造方法

真正获取到的方法与参数有关

获取无参数构造方法:

Constructor con = clazz.getDeclaredConstructor();

注意:getConstructor 的参数也必须是 class 类型,如下是获得两个参数都是 String 类型的构造方法

Constructor con = clazz.getDeclaredConstructor(String.class,String.class);

使用 Class 获得属性

getField(String)

getField(String) 参数名称为类中属性名称(成员变量),包括子类和父类。

要求这个属性访问权限修饰符必须是可以访问的。

如果没有这个属性会抛出 NoSuchFieldException。如果权限不足会抛出 SecurityException。

源码中的方法:

public Field getField(String name)throws NoSuchFieldException, SecurityException { 
//...
}

代码示例:

Class clazz = Class.forName("Teacher");
Field field = clazz.getField("age");

getFields()

getFields() 获取类中的所有属性(成员变量),包括子类和父类。

只能获取属性访问权限修饰符是可以访问的。

源码中的方法:

public Field[] getFields() throws SecurityException {
//...
}

代码示例:

Class clazz = Class.forName("Teacher");
Field[] field = clazz.getFields();

getDeclaredField(String)

getDeclaredField(String) 根据名称获取属性(某一个成员变量),包括子类和父类。

没有访问权限修饰符的限制。

源码中的方法:

public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException {
//...
}

代码示例:

Class clazz = Class.forName("Teacher");
Field field = clazz.getDeclaredField("name");

getDeclaredFields()

getDeclaredFields() 获取类中全部属性(成员变量),包括子类和父类。

没有访问权限修饰符的限制。

源码中的方法:

public Field[] getDeclaredFields() throws SecurityException {
//...
}

代码示例:

Class clazz = Class.forName("Teacher");
Field[] fields = clazz.getDeclaredFields();

实例化对象

通过 Class 的 newInstance 方法

该方法要求该 Class 对象的对应类有无参构造方法

执行 newInstance 实际上就是执行无参构造方法来创建该类的实例。

但已经过时。

public class TestConstructor1 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
// 不使用反射创建对象
// Teacher teacher = new Teacher();
// 使用反射创建对象
// 1.获取类的完整路径字符串
String className = "Teacher";
// 2.根据完整路径字符串获取 Class 对象信息
Class clazz = Class.forName(className);
// 3.直接使用 Class 的方法创建对象
Object obj = clazz.newInstance();
System.out.println(obj.toString());
}
}

通过 Constructor 的 newInstance 方法

先使用 Class 对象获取指定的 Constructor 对象。

再调用 Constructor 对象的 newInstance 创建 Class 对象对应类的对象。

通过该方法可选择使用指定构造方法来创建对象。

调用无参数构造方法创建对象
import java.lang.reflect.Constructor;

public class TestConstructor2 {
public static void main(String[] args) throws Exception{
// 不使用反射创建对象
// Teacher teacher = new Teacher();
// 使用反射创建对象
// 1.获取类的完整路径字符串
String className = "Teacher";
// 2.根据完整路径字符串获取Class对象信息
Class clazz = Class.forName(className);
// 3.获取无参数构造方法
Constructor con = clazz.getConstructor();
// 4.使用无参数构造方法来创建对象
Object obj = con.newInstance();
System.out.println(obj);
}
}
调用有参数构造方法创建对象
import java.lang.reflect.Constructor;

public class ReflectionTest {
public static void main(String[] args) throws Exception {
// 不使用反射创建对象
// Teacher teacher = new Teacher(40,"张三");
// System.out.println(teacher);
// 使用反射创建对象
// 1.读取配置文件,或者类的完整路径字符串
String className = "Teacher";
// 2.根据类的完整路径字符串获取Class信息
Class clazz = Class.forName(className);
// 3.从Class信息中获取有参数构造方法
// Constructor con = clazz.getConstructor(int.class,String.class);
// 指定形参
Constructor con = clazz.getDeclaredConstructor(int.class,String.class);
// 4.使用反射创建对象
// 突破封装性的限制,即使是 private、默认 的也可以访问
con.setAccessible(true);
// 传递实参
Object obj = con.newInstance(40,"张三");
System.out.println(obj);
}
}

操作对象属性(Field)

介绍

java 中提供的对反射支持的类都在 java.lang.reflet 包中。

java.lang.reflect.Field 表示类中属性对象。Class 类中获取属性的四个方法的返回值都是这个类型或这个类型的数组类型。

每一个 Field 对象代表类中一个属性对象。

这个类是不能被实例化的,类中只提供了默认访问权限修饰符的构造方法。所以基本上都是通过 class 调用 getField(),返回值为 Field对象。

常用方法

set(Object,Object)

set(Object,Object) 中第一个参数表示给哪个对象的属性赋值。第二个参数表示属性的取值。

Class<Teacher> clazz = Teacher.class;
// 获取对象
Teacher teacher = clazz.getConstructor().newInstance();
// 获取属性对象
Field field = clazz.getDeclaredField("name");
// 设置属性值
field.set(teacher,"张三");

万一…

image-20240824222227570

setAccessible(boolean)

setAccessible(boolean) 如果一个属性是 private,即使获得到了这个属性对象,也不允许被赋值的。如果在赋值之前,设置 setAccessible(true)。表示即使为 private,也能赋值。

这点太变态了。完全破坏了 Java 中封装特性。也就是说对于反射,类中没有任何秘密而言。

Class<Teacher> clazz = Teacher.class;
Teacher teacher = clazz.getConstructor().newInstance();
Field field = clazz.getDeclaredField("name");
field.setAccessible(true);
field.set(teacher,"张三");

get(Object)

get(Object) 方法参数是一个对象,返回值类型是 Object。结果是类中这个属性的值。

还支持 getInt(Object)getFloat(Object)getChar(Object) 等多种返回具体值类型的方法。

Class<Teacher> clazz = Teacher.class;
Teacher teacher = clazz.getConstructor().newInstance();
Field field = clazz.getDeclaredField("name");
field.setAccessible(true);
field.set(teacher,"张三");
Object name = field.get(teacher);
System.out.println(name);

输出:

无参构造执行
张三

整合一下

import java.lang.reflect.Field;

public class ReflectionTest {
public static void main(String[] args) throws Exception {
// 1.得到类对象
Class<Teacher> clazz = Teacher.class;
// 2.使用反射创建对象
Teacher teacher = clazz.getConstructor().newInstance();
// 3.获取属性
Field f1 = clazz.getDeclaredField("name");
// Field f2 = clazz.getField("age");
Field f2 = clazz.getDeclaredField("age");
// 4.给属性赋值
// 突破权限的控制
f1.setAccessible(true);
f1.set(teacher,"张三"); // teacher.name ="张三";
f2.setAccessible(true);
f2.set(teacher,10);
// 5.输出给属性
System.out.println(f1.get(teacher)); //teacher.name
System.out.println(f2.get(teacher)); //teacher.age
System.out.println(teacher);
}
}

输出:

无参构造执行
张三
10
Teacher@36d64342

操作方法对象(Method)

介绍

java.lang.reflect.Method 是 Java 提供的方法对象。类中每个方法对应一个 Method 对象。

通过 Class 对象获取方法对象,返回值都是 MethodMethod[]

Method 是无法被外部实例化的。它只提供了默认访问权限修饰符的构造方法。所以基本上都是通过 Class 方法返回值而获取 Method 的对象。

常用方法

invoke

通过 Class 对象的 getMethods 方法可以获得该类所包括的全部 public 方法,返回值是 Method[]

通过 Class 对象的 getMethod 方法可以获得该类所包括的指定 public 方法,返回值是 Method。

每个 Method 对象对应一个方法,获得 Method 对象后,可以调用其 invoke 来调用对应方法。

Object invoke(Object obj,Object[] args):

obj 代表当前方法所属的对象的名字,args 代表当前方法的参数列表,

返回值 Object 是当前方法返回值,即执行当前方法的结果。

源码中的方法:

public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException{
//...
}

代码示例:

import java.lang.reflect.Method;

public class ReflectionTest {
public static void main(String[] args) throws Exception {
// 1.得到类对象
Class<Teacher> clazz = Teacher.class;
// 2.使用反射创建对象
Teacher teacher = clazz.getConstructor().newInstance();
// 4.获取方法
Method m1 = clazz.getMethod("getAge");
Method m2 = clazz.getMethod("setAge",int.class);
// 5.使用反射执行方法
m1.invoke(teacher); // 相当于执行 teacher.getAge();
Object result = m2.invoke(teacher,40); // 相当于执行 teacher.setAge(40);
System.out.println(result);
}
}

输出:

无参构造执行
执行了赋值操作
null

使用反射操作泛型

操作泛型

没有出现泛型之前,Java 中的所有数据类型包括:

  • primitive types:基本类型
  • raw type:原始类型。不仅仅指平常所指的类,还包括数组、接口、注解、枚举等结构。
  • Class 类的一个具体对象代表一个指定的原始类型和基本类型。

泛型出现之后,也就扩充了数据类型:

  • parameterized types(参数化类型):就是我们平常所用到的泛型 List<T>Map<K,V> 的 List 和 Map
  • type variables(类型变量):比如 List<T> 中的 T 等。(注意和参数化类型的区别)
  • array types(数组类型):并不是我们工作中所使用的数组 String[]byte[](这种都属于 Class 而是带泛型的数组,比如 List<T>[]T[]
  • WildcardType(泛型表达式类型通配符类型):例如 List< ? extends Number>

Java 采用泛型擦除机制来引入泛型。但是擦除的是方法体中局部变量上定义的泛型,在泛型类、泛型接口中定义的泛型,在成员变量、成员方法上定义的泛型,依旧会保存(可以理解为定义泛型信息保留,使用泛型信息擦除)。保留下来的信息可以通过反射获取。

另外一方面,Class 类的一个具体对象代表一个指定的原始类型和基本类型,和泛型相关的新扩充进来的类型不好被统一到 Class 类中,否则会涉及到 JVM 指令集的修改,是很致命的。

为了能通过反射操作泛型,但是实现扩展性而不影响之前操作,Java 就新增了 ParameterizedType,TypeVariable,GenericArrayType,WildcardType 几种类型来代表不能被归一到 Class 类中的类型但是又和原始类型齐名的类型。

image-20240824232601986

代码实现

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

public class ReflectionTest {

public void method1(Map<Integer, Teacher> map, List<Teacher> list, String str) { }
public Map<Integer, Teacher> method2() {
return null;
}

public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("ReflectionTest");

// 获得 method1 方法对象
Method method1 = clazz.getMethod("method1", Map.class, List.class, String.class);

// 获得参数类 这个类型中不含有泛型
Class[] typesNullParameter = method1.getParameterTypes();

// 获得参数类型 含有泛型
// 参数可以有多个,所以是 Type[]
Type[] types = method1.getGenericParameterTypes();
for (Type type : types) {
// System.out.println(type);
// 判断是否与 ParameterizedType 类等效
if(type instanceof ParameterizedType){
// 每一个参数泛型的数组
Type[] arguments = ((ParameterizedType) type).getActualTypeArguments();
for (Type argument : arguments) {
System.out.println(argument);
}
}
}
System.out.println("----------------------------------------");
Method method2 = clazz.getMethod("method2");

// 获得返回值类型 不含有泛型
Class returnType = method2.getReturnType();

// 获得返回值类型 含有泛型
// 返回值只有一个,所以不是 Type[]
Type type = method2.getGenericReturnType();
if(type instanceof ParameterizedType){
Type[] types2 = ((ParameterizedType) type).getActualTypeArguments();
for (Type t : types2) {
System.out.println(t);
}
}
}
}

输出:

class java.lang.Integer
class Teacher
class Teacher
----------------------------------------
class java.lang.Integer
class Teacher

使用反射突破泛型的限制

public class TestGeneric {
public static void main(String[] args) throws Exception {
// 不是反射 <String> 泛型让 list 数组中的变量限制在 String 类型
List<String> list = new ArrayList<String>();
list.add("Java");
list.add("MySQL");
list.add("MyBatis");
// list.add(new Date());
// list.add(100);
// 使用反射调用add
// 先得到 List 的结构信息 Class
// Class clazz = Class.forName("java.util.ArrayList");
// Class clazz = ArrayList.class;
Class clazz = list.getClass();
// 获取 add 方法
Method method = clazz.getMethod("add",Object.class);
// 使用反射调用 add 方法
// 就会突破泛型 String 的限制,可以往 list 数组中添加其他类型变量
method.invoke(list,100);
method.invoke(list,new Date());
System.out.println(list);
}
}