1. 反射概述
引言
本篇对反射基础进行了讲解。
可以带着以下问题进行学习:
- 什么是Java语言的反射机制?
- 反射都提供了什么功能?
- 反射有什么作用?
- 反射如何获取Class对象?
- 反射如何创建类对象?
- 反射如何获取类的成员变量和类的成员方法?
1.1 反射是什么?
Java语言的反射机制: 在Java中,运行状态时,只要给定类的名字,就能知道这个类的所有信息,可以构造出指定对象,可以调用它的任意一个属性和方法。这种动态获取信息以及动态调用对象的方法的功能是Java语言的反射机制。
一般,我们使用某个类时一定知道这个类是什么类,有什么作用,对这个类进行实例化,使用这个类对象进行操作。
反射则是在一开始并不知道我们要初始化的类对象是什么,自然无法new出对象,这时候需要进行反射调用。
反射允许对封装类的字段、方法和构造函数的信息进行编程访问。
1.2 反射提供的功能
Java反射机制主要提供以下功能:
-
在运行时判断任意一个对象所属的类。
-
在运行时构造任意一个类的对象。
-
在运行时判断任意一个类所具有的成员变量和方法。
-
在运行时调用任意一个对象的方法。
1.3 反射的作用
反射的作用:
-
动态的加载类,动态获取类的信息(属性、方法、构造器)
-
动态的构造对象
-
动态调用类和对象的任意方法、构造器
-
获取泛型信息
-
处理注解
-
动态代理
-
…
2. 获取类的信息
2.1 获取反射中的Class对象
反射始于Class,Class是一个类,是一个用来描述类的类,封装了当前对象所对应类的信息。对于每个类,JRE都为其保留一个不变的Class类型的对象。一个类在JVM中只会有一个Class实例。
获取Class类对象三种方法:
- Class.forNam(全类名) 静态方法。源代码阶段(.java -> .class)
- 类名.class() 方法(基本类型只可该方法获得Class对象)。加载阶段(.class加载到内存中)
- 对象.getClass() 方法。运行阶段(内存中运行 A a = new A();)
三种方法实现代码如下:
//第一种,使用 Class.forName 静态方法。
Class clazz1 = Class.forName("reflect.Person");
//第二种,使用 .class 方法。
Class clazz2 = Person.class;
//第三种,使用类对象的 getClass() 方法。
Person person = new Person("浅浅",18);
Class clazz3 = person.getClass();
判断是否为某个类的实例
一般,使用instanceof关键字来判断是否为某个类的实例:
public native boolean isInstance(Object obj);
也可以使用反射中Class对象的isInstance()方法来判断是否为某个类的实例:
public boolean isAssignableFrom(Class<?> cls)
2.2 通过反射创建类对象
主要两种方式:
1.通过 Class对象的newInstance() 方法(只能使用默认的无参数构造方法)。
2.通过 Constructor对象的newInstance() 方法(可以选择特定构造方法)。
其实, class.newInstance() 内部实现也是通过 Constructor。而且现在已经标记为 @Deprecated(since="9")
,建议使用 Constructor 。
//获取class对象
Class clazz = Class.forName("reflect.Person");
//如果有默认构造方法,就使用这个,先通过Class对象获取指定的Constructor对象
Person p1 = (Person) clazz.getConstructor().newInstance();
//指定构造方法
Person p2 = (Person) clazz.getDeclaredConstructor(String.class, int.class).newInstance("dr", 1);
2.3 通过反射获取类的成员变量
获取成员变量的两个方法:
Field getField(String name)
: 获得命名的公共字段,只能获取 public 修饰的变量,如果是私有变量,会报错NoSuchField。Field[] getFields()
: 获得类的所有公共字段。Field getDeclaredField(String name)
: 获得类声明的字段,在不知道变量的权限修饰符的时候,建议使用, 比较保险。Field[] getDeclaredFields()
: 获得类声明的所有字段。
修改变量的值的两类方法:
- 引用类型:只有一个方法
set(Object obj, Object value)
- 基本类型:为每个基本类型,都提供了一个方法,例如
setInt(Object obj, int i)
,setLong(Object obj, long l)
大家使用时,根据不同的类型,选择不同的方法。
注:若修改的属性为私有属性,则需要先设置
field.setAccessible(true)
,设置之后才可以修改值。
//获取class对象
Class clazz = Class.forName("reflect.Person");
//构造对象
Person p = (Person) clazz.getDeclaredConstructor(String.class, int.class).newInstance("dr", 18);
//获取指定变量 name
Field nameField = clazz.getField("name");
//修改变量的值
nameField.set(p,"张三");
//获取指定变量 age
Field ageField = clazz.getDeclaredField("age");
//age 是private变量,需要先设置可用
ageField.setAccessible(true);
//修改变量的值
ageField.setInt(p,17);
2.4 通过反射获取类的方法
获取类方法的两个方法:
Method getMethod(String name, Class[] params)
: 使用特定的参数类型,获得命名的公共方法。Method[] getMethods()
: 获得类的所有公共方法。Method getDeclaredMethod(String name, Class[] params)
: 使用特写的参数类型,获得类声明的命名的方法。Method[] getDeclaredMethods()
: 获得类声明的所有方法。
//获取class对象
Class clazz = Class.forName("reflect.Person");
//构造对象
Person p = (Person) clazz.getDeclaredConstructor(String.class, int.class).newInstance("dr", 18);
//获取指定方法 "addFavorite"
Method method = clazz.getMethod("addFavorite", String.class);
//调用对象方法
method.invoke(p, "篮球");
//获取指定方法 "changeAge"
Method privateMethod = clazz.getDeclaredMethod("changeAge", int.class);
//changeAge 是private方法,需要先设置可用
privateMethod.setAccessible(true);
privateMethod.invoke(p, 17);
注:当我们从类中获取了一个方法后,我们就可以用 invoke() 方法来调用这个方法。
public Object invoke(Object obj, Object... args)
3. 反射进阶
本文介绍了反射的基本使用方法,深入进阶使用请看后续文章…