为什么要学习反射?
从语言的设计角度来考虑,反射使 Java 具备动态性,我们可以让程序在运行期才确定执行的结果,在不修改源码的情况下来扩展功能或者是控制程序
Java
是一门面向对象的语言,封装
是面向对象的一个特性,它允许抽象的类把自己的数据和方法只让可信的类或者对象进行操作,对不可信的进行信息隐藏(private
修饰),而反射能够使我们去操作这些私有的变量和方法
反射是 Java
的灵魂,没有反射,甚至 Spring
的那些框架也不复存在,学习反射是为了之后去读一些框架的底层源码更容易理解
比如有这里有一个需求,要求你通过读取配置文件的方式实现调用指定类的方法,你会如何去实现:
如果不使用反射,那么在编写代码的时候就会发现, config.properties
文件中的内容不确定的情况下,我们无法通过 new
的方式去创建具体的某一个对象,从而调用该对象的指定方法,但是通过反射就能很轻易的解决这一问题,代码如下:
public class Test {
public static void main(String[] args) throws Exception {
Properties properties = new Properties();
// 读取配置文件
properties.load(new FileInputStream("src\\config.properties"));
// 获取类路径
String classpath = properties.getProperty("classpath");
// 获取类方法
String method = properties.getProperty("method");
// 通过反射获取类的字节码对象
Class<?> clazz = Class.forName(classpath);
// 通过字节码对象获取对象实例
Object instance = clazz.newInstance();
// 通过类的字节码对象获取方法对象
Method m = clazz.getMethod(method);
// 调用方法
m.invoke(instance);
}
}
测试如下:
这样就能做到只需要改变外部的配置文件 config.properties
就能在不改变源码的情况下,去控制我们想要执行的程序,完成了解耦,同时这也符合 开闭原则
。
以上内容简单介绍了一下我们为什么要学习反射,并且通过一个简单的案例入门,接下来就是正文部分了。
Java 的反射(reflection)机制 是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言
的关键
。
对于一个字节码文件 .class
,它里面记录着许多的信息,Java
在将 .class
字节码文件载入时,JVM
将产生一个 java.lang.Class
对象代表 .class
字节码文件,从 Class
对象中可以获得类的完整结构信息(构造方法、成员变量、成员方法等等),这就是反射机制的核心。
反射原理图:
反射机制是 Java 实现动态语言的关键,也就是通过反射实现类动态加载的。
我们利用反射可以完成:
尽管反射机制带来了极大的灵活性,但也是有缺点的,不能滥用:
反射机制所需的类主要有 java.lang
包中的 Class
类 和 java.lang.reflect
包中的 Constructor
类、Field
类、Method
类和Parameter
类。
Class 类是一个比较特殊的类,它是反射机制的基础,Class 类的对象表示正在运行的 Java 程序中的类或接口,每个类被加载之后,系统都会为该类生成一个对应的 Class 对象。Class 类没有公共构造方法,其对象是 JVM 在加载类时通过调用类加载器中的 defineClass()
方法创建的,因此不能显式地创建一个 Class 对象。通过 Class 对象就可以访问到 JVM 中该类的信息,一旦类被加载到 JVM 中,同一个类将不会被再次载入。被载入 JVM 的类都有一个唯一标识就是该类的全名,即包括包名和类名。
类图:
在 Java 中程序获得 Class 对象有如下 5 种方式:
String className = "com.xxx.xxx.Xxx";
Class<?> clazz = Class.forName(className);
类名.class
Class<Dog> dogClass = Dog.class;
Class<Integer> integerClass = int.class;
对象.getClass()
Dog dog = new Dog();
Class<?> dogClass = dog.getClass();
Dog dog = new Dog();
ClassLoader classLoader = dog.getClass().getClassLoader();
Class<?> aClass = classLoader.loadClass("com.xxx.xxx.Xxx");
.TYPE
得到 Class 类对象Class<Integer> integerClass = Integer.TYPE;
常用方法:
常用方法 | 功能说明 |
---|---|
public Package getPackage() | 返回 Class 对象所对应类的存放路径 |
public static Class> forName(String className) | 返回名称为 className 的类或接口的 Class 对象 |
public String getName() | 返回 Class 对象所对应类的 包.类名 形式的全名 |
public String getSimpleName() | 获取简单类名 |
public native Class super T> getSuperclass() | 返回 Class 对象所对应的父类的 Class 对象 |
public Class>[] getInterfaces() | 返回 Class 对象所对应类实现的所有接口 |
public Annotation[] getAnnotations() | 以数组的形式返回该程序元素上的所有注解 |
public Constructor>[] getConstructors() | 返回 Class 对象所对应类的指定参数列表的 public 构造方法 |
public Constructor getDeclaredConstructor(Class>… parameterTypes) | 返回 Class 对象所对应类的指定参数列表的构造方法,与访问权限无关 |
public Constructor>[] getDeclaredConstructors() | 返回 Class 对象所对应类的所有构造方法,与访问权限无关 |
public Field getField(String name) | 返回 Class 对象所对应类的名为 name 的 public 成员变量 |
public Field[] getFields() | 返回 Class 对象所对应类的所有 public 成员变量 |
public Field[] getDeclaredFields() | 返回 Class 对象所对应类的所有成员变量,与访问权限无关 |
public Method getMethod(String name, Class>… parameterTypes) | 返回 Class 对象所对应类的指定参数列表的 public 方法 |
public Method[] getMethods() | 返回 Class 对象所对应类的所有 public 成员方法 |
public Method[] getDeclaredMethods() | 返回 Class 对象所对应类的所有成员方法,与访问权限无关 |
public T newInstance() | 创建 Class 类对应的实例类对象,已过时 |
因为反射是建立在 Class 对象上的,所以对于类的加载还是需要了解的,这里我就不再赘述了,可以借鉴JVM-类的加载机制 这篇博客简单的认识一下。
反射机制中除了上面介绍的 java.lang
包中的 Class
类之外,还需要 java.lang.reflect
包中的 Executable
类、Field
类和Parameter
类。Executable 抽象类派生了 Constructor
和 Method
两个子类。
java.lang.reflect.Executable
类提供了大量方法用来获取参数、修饰符或注解等信息,其常用方法如下表所示:
常用方法 | 功能说明 |
---|---|
public Parameter[] getParameters() | 返回所有形参存入数组 Parameter[ ] 中 |
public int getParameterCount() | 返回参数的个数 |
public abstract Class>[] getParameterTypes() | 按声明顺序以 Class 数组的形式返回各参数的类型 |
public abstract int getModifiers() | 返回整数表示的修饰符所对应的常量,默认修饰符(0)、public(1)、private(2)、protected(4)、static(8)、final(16) |
public boolean isVarArgs() | 判断是否包含数量可变的参数 |
java.lang.reflect.Constructor
类是 java.lang.reflect.Executable
类的直接子类,用于表示类的构造方法
。通过 Class 对象的getConstructors()
方法可以获得当前运行时类的构造方法,其常用方法如下表所示:
常用方法 | 功能说明 |
---|---|
public String getName() | 返回构造方法的名字 |
public T newInstance(Object … initargs) | 使用由此 Constructor 对象表示的构造函数,使用指定的初始化参数来创建和初始化构造函数的声明类的新实例 |
public void setAccessible(boolean flag) | 如果该构造方法的权限为 private,默认不允许通过反射 newInstance() 方法创建对象,如果先执行该方法,并将入口参数设置为 true,则允许创建 |
public Annotation[] getAnnotations() | 以数组的形式返回该程序元素上的所有注解 |
java.lang.reflect.Method
类是 java.lang.reflect.Executable
类的直接子类,用于封装成员方法的信息,调用 Class对象的getMethod()
方法或 getMethods()
方法可以获得当前运行时类的指定方法或所有方法,其常用方法如下表所示:
常用方法 | 功能说明 |
---|---|
public String getName() | 返回方法的名称 |
public Class> getReturnType() | 以 Class 对象的形式返回当前方法的返回值类型 |
public Object invoke(Object obj, Object… args) | 利用给定参数列表执行指定对象 obj 中的方法 |
public void setAccessible(boolean flag) | 启动或者禁用安全检查的开关,参数为 true 时表示取消访问检查,提高反射的效率 |
public int getModifiers() | 获取该属性对应的修饰符值:默认修饰符(0)、public(1)、private(2)、protected(4)、static(8)、final(16) |
public Class>[] getParameterTypes() | 返回一个 Class 对象的数组,表示由该对象表示的可执行文件的声明顺序的形式参数类型 |
public Annotation[] getAnnotations() | 以数组的形式返回该程序元素上的所有注解 |
public Parameter[] getParameters() | 返回一个 Parameter对象的数组,表示由该对象表示的底层可执行文件的所有参数 |
public Type[] getGenericParameterTypes() | 返回一个 Type对象的数组, Type以声明顺序表示由该对象表示的可执行文件的形式参数类型 |
public Type getGenericReturnType() | 返回一个 Type对象,它表示由该表示的方法的正式返回类型 方法对象 |
java.lang.reflect.Field
类用于封装成员变量信息,调用 Class对象的 getField()
方法或 getFields()
可以获得当前运行时类的指定成员变量或所有成员变量,其常用方法如下表所示:
常用方法 | 功能说明 |
---|---|
public String getName() | 返回成员变量的名称 |
public Xxx getXxx() | 返回成员变量的值,其中 Xxx 代表基本类型,如果成员变量是引用类型,则直接使用 get(Object obj) 方法 |
void setXxx(Object obj,Xxx val) | 设置成员变量的值,其中 Xxx 代表基本类型,如果成员变量是引用类型,则直接使用 set(Object obj,Object val) 方法 |
public Class> getType | 返回当前成员变量的类型 |
public void setAccessible(boolean flag) | 启动或者禁用安全检查的开关,参数为 true 时表示取消访问检查,提高反射的效率 |
public int getModifiers() | 获取该属性对应的修饰符值:默认修饰符(0)、public(1)、private(2)、protected(4)、static(8)、final(16) |
public Annotation[] getAnnotations() | 以数组的形式返回该程序元素上的所有注解 |
java.lang.reflect.Parameter
类是参数类,每个 Parameter
对象代表方法的一个参数,该类中提供了许多方法来获取参数信息,下表给出了该类的常用方法:
常用方法 | 功能说明 |
---|---|
public int getModifiers() | 返回参数的修饰符 |
public String getName() | 返回参数的形参名 |
public Type getParametcrizedType() | 返回带泛型的形参类型 |
public Class >getType() | 返回形参类型 |
public boolean isVarArgs() | 判断该参数是否为可变参数 |
public boolean isNamePresent() | 判断.class文件中是否包含方法的形参名信息 |
Describe.java
import java.lang.annotation.*;
/**
* 自定义描述注解
*/
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER,ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Describe {
String desc();
}
Person.java
@Describe(desc="人类")
public class Person {
@Describe(desc="姓名")
private String name;
@Describe(desc="年龄")
protected int age;
@Describe(desc="性别")
boolean grand;
@Describe(desc="身高")
public double high;
@Describe(desc="无参构造")
public Person() {}
@Describe(desc="有参构造")
public Person(String name,int age) {
this.name = name;
this.age = age;
}
@Describe(desc="走路")
private void walk() {
System.out.println("正在走路...");
}
@Describe(desc="睡觉")
protected void sleep() {
System.out.println("正在睡觉...");
}
@Describe(desc="说话")
public void talk() {
System.out.println("正在说话...");
}
@Describe(desc="吃饭")
void eat() {
System.out.println("正在吃饭...");
}
}
Employee.java
import java.math.BigDecimal;
/**
* 员工类
*/
@Describe(desc="员工类")
public class Employee extends Person {
@Describe(desc="公司名称")
public String companyName;
@Describe(desc="公司地址")
public String companyAddress;
@Describe(desc="薪资")
BigDecimal salary;
@Describe(desc="职位")
private String post;
@Describe(desc="部门")
protected String dept;
@Describe(desc="无参构造")
public Employee() {}
@Describe(desc="有参构造")
public Employee(String companyName,String companyAddress) {
this.companyName = companyName;
this.companyAddress = companyAddress;
}
@Describe(desc="私有有参构造")
private Employee(String post) {
this.post = post;
}
@Describe(desc="工作")
private void work() {
System.out.println("正在工作...");
}
@Describe(desc="摸鱼")
public void touchFish() {
System.out.println("正在摸鱼...");
}
@Describe(desc="加班")
protected void extraWork() {
System.out.println("正在加班...");
}
@Describe(desc="开会")
void meeting(String withWho) {
System.out.println("正在和"+withWho+"开会...");
}
@Describe(desc="上交工资")
public String handInSalary() {
System.out.println("上交工资...");
return salary==null?"0":salary.toString();
}
@Override
public String toString() {
return "Employee{" +
"companyName='" + companyName + '\'' +
", companyAddress='" + companyAddress + '\'' +
", salary=" + salary +
", post='" + post + '\'' +
", dept='" + dept + '\'' +
'}';
}
}
Demo.java
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.math.BigDecimal;
public class Demo {
public static void main(String[] args) throws Exception {
classApi();
constructorApi();
fieldApi();
methodApi();
parameterApi();
}
public static void classApi() throws Exception {
// 获取 Class 对象
Class<?> clazz = Class.forName("com.mike.reflection.Employee");
// getPackage:获取 Class 对象所对应类的包信息
Package aPackage = clazz.getPackage();
System.out.println("package = " + aPackage.getName());
// getName:返回 Class 对象所对应类的 `包.类名` 形式的全名
String name = clazz.getName();
System.out.println("name = " + name);
// getSimpleName:获取简单类名
String simpleName = clazz.getSimpleName();
System.out.println("simpleName = " + simpleName);
// getConstructors:获取所有 public 修饰的构造器
Constructor<?>[] constructors = clazz.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println("constructor = " + constructor.getName());
}
// getConstructor:获取指定 public 修饰的构造
Constructor<?> appointConstructor = clazz.getConstructor();
System.out.println("appointConstructor = " + appointConstructor.getName());
// getDeclaredConstructors:获取本类所有的构造器
Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println("declaredConstructor = " + declaredConstructor.getName());
}
// getFields:获取所有 public 修饰的属性,包含本类及父类的
Field[] fields = clazz.getFields();
for (Field field : fields) {
System.out.println("field = " + field.getName());
}
// getField:获取指定 public 修饰的属性
Field appointField = clazz.getField("companyName");
System.out.println("appointField = " + appointField.getName());
// getDeclaredFields:获取本类所有属性
Field[] declaredFields = clazz.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println("declaredField = " + declaredField.getName());
}
// getDeclaredField:获取本类指定的属性
Field appointDeclareField = clazz.getDeclaredField("salary");
System.out.println("appointDeclareField = " + appointDeclareField.getName());
// getMethods:获取所有 public 修饰的方法,包含本类以及父类的
Method[] methods = clazz.getMethods();
for (Method method : methods) {
System.out.println("method = " + method.getName());
}
// getMethod:获取指定 public 修饰的方法
Method appointMethod = clazz.getMethod("talk");
System.out.println("appointMethod = " + appointMethod.getName());
// getDeclaredMethods:获取本类中所有的方法
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println("declaredMethod = " + declaredMethod.getName());
}
// getDeclaredMethod:指定获取本类的某个方法
Method appointDeclaredMethod = clazz.getDeclaredMethod("extraWork");
System.out.println("appointDeclaredMethod = " + appointDeclaredMethod.getName());
// getSuperclass:返回 Class 对象所对应的父类的 Class 对象
Class<?> superclass = clazz.getSuperclass();
System.out.println("superclass = " + superclass.getName());
// getAnnotations:以数组的形式返回该程序元素上的所有注解
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println("annotation = " + annotation);
}
// getAnnotation:获取该类上的指定注解
Describe appointAnnotation = clazz.getAnnotation(Describe.class);
System.out.println("annotation = " + appointAnnotation.toString());
// getInterfaces:以 Class[] 的形式返回接口的信息
Class<?>[] interfaces = clazz.getInterfaces();
for (Class<?> anInterface : interfaces) {
System.out.println("anInterface = " + anInterface.getName());
}
// newInstance:创建 Class 类对应的实例类对象,已过时
Object instance = clazz.newInstance();
System.out.println("instance = " + instance);
}
public static void constructorApi() throws Exception {
// 获取 Class 对象
Class<?> clazz = Class.forName("com.mike.reflection.Employee");
// 获取无参构造方法 --> public Employee()
Constructor<?> noReferenceConstructor = clazz.getConstructor();
// 获取构造方法的名称
String noReferenceConstructorName = noReferenceConstructor.getName();
// 通过无参构造创建实例 ==> Employee employee = new Employee();
Object noReferenceInstance = noReferenceConstructor.newInstance();
// 获取有参构造方法 --> public Employee(String companyName,String companyAddress)
Constructor<?> withReferenceConstructor = clazz.getConstructor(String.class, String.class);
// 通过有参构造方法创建实例 ==> Employee employee = new Employee("多加辣", "广州");
Object withReferenceInstance = withReferenceConstructor.newInstance("多加辣", "广州");
// 获取构造方法上的注解
Annotation[] annotations = noReferenceConstructor.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println("annotation = " + annotation);
}
// 获取私有有参构造方法 --> private Employee(String post)
Constructor<?> privateWRConstructor = clazz.getDeclaredConstructor(String.class);
// 如果构造方法的访问权限是 private,是不能直接使用其构造方法的,会抛出 java.lang.IllegalAccessException
// 需要关闭其安全检查的开关
privateWRConstructor.setAccessible(true);
// 通过有参构造方法创建实例 ==> Employee employee = new Employee("项目经理");
Object privateWRInstance = privateWRConstructor.newInstance("项目经理");
System.out.println("privateWRInstance.toString() = " + privateWRInstance.toString());
}
public static void fieldApi() throws Exception {
// 获取 Class 对象
Class<?> clazz = Class.forName("com.mike.reflection.Employee");
// 创建实例对象
Object instance = clazz.newInstance();
// 获取本类指定的属性
Field companyNameField = clazz.getField("companyName");// public 修饰
Field salaryFiled = clazz.getDeclaredField("salary");// 默认修饰符 修饰
Field deptField = clazz.getDeclaredField("dept");// protected 修饰
Field postField = clazz.getDeclaredField("post");// private 修饰
// 获取属性的名称
String name = deptField.getName();
// 获取该属性对应的修饰符值:默认修饰符(0)、public(1)、private(2)、protected(4)、static(8)、final(16)
// 如果是组合关系,则相加起来,例如:public static = 1 + 8 = 9
int modifiers = deptField.getModifiers();
System.out.println("modifiers = " + modifiers);
// 获取当前成员变量的类型
Class<?> typeClazz = deptField.getType();
System.out.println("typeClazz = " + typeClazz.getName());
// 获取成员变量上的注解
Annotation[] annotations = postField.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println("annotation = " + annotation);
}
// 设置值
companyNameField.set(instance,"多加辣科技");
salaryFiled.set(instance,new BigDecimal(18000));
deptField.set(instance,"开发部");
// 如果成员变量是 private 修饰的,则需要关闭其安全检查的开关,否则会抛出 java.lang.IllegalAccessException
postField.setAccessible(true);
postField.set(instance,"项目经理");
// 获取属性对应的值
Object fieldValue = postField.get(instance);
System.out.println("fieldValue = " + fieldValue);
System.out.println("instance.toString() = " + instance.toString());
}
public static void methodApi() throws Exception {
// 获取 Class 对象
Class<?> clazz = Class.forName("com.mike.reflection.Employee");
// 创建实例对象
Object instance = clazz.newInstance();
// 获取指定 public 修饰的方法,本类及父类
Method touchFishMethod = clazz.getMethod("touchFish");// 本类 public 修饰的方法
Method talkMethod = clazz.getMethod("talk");// 父类 public 修饰的方法
Method workMethod = clazz.getDeclaredMethod("work");// 本类 private 修饰的方法
Method extraWorkMethod = clazz.getDeclaredMethod("extraWork");// 本类 protected 修饰的方法
Method meetingMethod = clazz.getDeclaredMethod("meeting",String.class);// 本类 默认修饰符 修饰且带参的方法
Method handInSalaryMethod = clazz.getMethod("handInSalary");// 本类 public 修饰且有返回值的方法
// 获取方法名称
String touchFishMethodName = touchFishMethod.getName();
System.out.println("touchFishMethodName = " + touchFishMethodName);
// 获取该方法对应的修饰符值:默认修饰符(0)、public(1)、private(2)、protected(4)、static(8)、final(16)
// 如果是组合关系,则相加起来,例如:public static = 1 + 8 = 9
int modifiers = workMethod.getModifiers();
System.out.println("modifiers = " + modifiers);
// 获取方法的形参类型
Class<?>[] extraWorkParameterTypes = extraWorkMethod.getParameterTypes();
for (Class<?> extraWorkParameterType : extraWorkParameterTypes) {
System.out.println("extraWorkParameterType = " + extraWorkParameterType.getName());
}
Class<?>[] meetingParameterTypes = meetingMethod.getParameterTypes();
for (Class<?> meetingParameterType : meetingParameterTypes) {
System.out.println("meetingParameterType = " + meetingParameterType.getName());
}
// 获取方法返回对象的类对象
Class<?> talkReturnType = talkMethod.getReturnType();
System.out.println("talkReturnType = " + talkReturnType.getName());
Class<?> handInSalaryReturnType = handInSalaryMethod.getReturnType();
System.out.println("handInSalaryReturnType = " + handInSalaryReturnType);
// 获取方法上的注解
Annotation[] annotations = talkMethod.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println("annotation = " + annotation);
}
// 方法调用
// 如果方法没有返回值(void),默认返回 null
Object touchFishInvoke = touchFishMethod.invoke(instance);
Object talkInvoke = talkMethod.invoke(instance);
// 如果方法是被 private 修饰,调用前需要关闭其安全检查的开关,否则会抛出 java.lang.IllegalAccessException
workMethod.setAccessible(true);
Object workInvoke = workMethod.invoke(instance);
// 有形参的方法调用
Object meetingInvoke = meetingMethod.invoke(instance, "小张");
// 有返回值的方法调用
Object handInSalaryInvoke = handInSalaryMethod.invoke(instance);
System.out.println("handInSalaryInvoke = " + handInSalaryInvoke);
}
public static void parameterApi() throws Exception {
// 获取 Class 对象
Class<?> clazz = Class.forName("com.mike.reflection.Employee");
// 创建实例对象
Object instance = clazz.newInstance();
// 获取本类 默认修饰符 修饰且带参的方法
Method meetingMethod = clazz.getDeclaredMethod("meeting",String.class);
// 获取方法的形参
Parameter[] parameters = meetingMethod.getParameters();
for (Parameter parameter : parameters) {
// 获取参数名称
String name = parameter.getName();
System.out.println("name = " + name);
// 获取参数的修饰符
int modifiers = parameter.getModifiers();
System.out.println("modifiers = " + modifiers);
// 获取参数类型
Class<?> parameterType = parameter.getType();
System.out.println("parameterType = " + parameterType.getName());
// 判断该参数是否为可变参
boolean varArgs = parameter.isVarArgs();
System.out.println("varArgs = " + varArgs);
}
}
}