浅谈 Java 反射
反射
引入
在项目中有 People 类,包含 name 和age属性。并生成 getter/setter 和 toString 方法。
public class People { |
如果我们想给 name 赋值 ’张三‘ ,age 赋值 0 的话:
public static void main(String[] args) { |
问:如果现在给 People 中添加 n 个属性。并让程序支持分别对 n 个属性赋值,需要如何完成?
答:在 switch 中添加 n 个 case 分支。无论添加多少个属性,就可以添加多少个分支。
是不是很麻烦?
可以使用 Java 中的反射技术,解决程序在运行过程中改变属性值。
介绍
反射(Reflect):Java 中提供一种可以在运行时操作任意类中的属性和方法的技术。
反射在运行之前是不需要类中结构的,运行过程中,只要能够获取该类的字节码文件,就可以随意修改类中属性的值,随意调用类中方法。让程序变得非常灵活。
Class 类
java.lang.Class 表示类的类型。
一个 Class 类对象就代表了某个类的字节码对象。获取到这个类的字节码对象后该类中所有的内容都会被知道,然后就可以对这个类进行操作。
Class 类是 Java 反射机制的起源和入口 / 用于获取与类相关的各种信息 /提供了获取类信息的相关方法/ Class 类继承自 Object 类。
前置代码
src/Teacher
import java.util.List; |
创建 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 对象常用方法
获取一个类的结构信息(类对象 Class 对象)
泛型可省略
Class clazz = Class.forName("why.Dog"); |
从类对象中获取类的各种结构信息
获取基本结构信息:
System.out.println(clazz.getName()); // 获得包名+类名 |
输出:
Teacher |
使用 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"); |
getFields()
getFields()
获取类中的所有属性(成员变量),包括子类和父类。
只能获取属性访问权限修饰符是可以访问的。
源码中的方法:
public Field[] getFields() throws SecurityException { |
代码示例:
Class clazz = Class.forName("Teacher"); |
getDeclaredField(String)
getDeclaredField(String)
根据名称获取属性(某一个成员变量),包括子类和父类。
没有访问权限修饰符的限制。
源码中的方法:
public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException { |
代码示例:
Class clazz = Class.forName("Teacher"); |
getDeclaredFields()
getDeclaredFields()
获取类中全部属性(成员变量),包括子类和父类。
没有访问权限修饰符的限制。
源码中的方法:
public Field[] getDeclaredFields() throws SecurityException { |
代码示例:
Class clazz = Class.forName("Teacher"); |
实例化对象
通过 Class 的 newInstance 方法
该方法要求该 Class 对象的对应类有无参构造方法。
执行 newInstance 实际上就是执行无参构造方法来创建该类的实例。
但已经过时。
public class TestConstructor1 { |
通过 Constructor 的 newInstance 方法
先使用 Class 对象获取指定的 Constructor 对象。
再调用 Constructor 对象的 newInstance 创建 Class 对象对应类的对象。
通过该方法可选择使用指定构造方法来创建对象。
调用无参数构造方法创建对象
import java.lang.reflect.Constructor; |
调用有参数构造方法创建对象
import java.lang.reflect.Constructor; |
操作对象属性(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; |
万一…
setAccessible(boolean)
setAccessible(boolean)
如果一个属性是 private,即使获得到了这个属性对象,也不允许被赋值的。如果在赋值之前,设置 setAccessible(true)
。表示即使为 private,也能赋值。
这点太变态了。完全破坏了 Java 中封装特性。也就是说对于反射,类中没有任何秘密而言。
Class<Teacher> clazz = Teacher.class; |
get(Object)
get(Object)
方法参数是一个对象,返回值类型是 Object。结果是类中这个属性的值。
还支持 getInt(Object)
、getFloat(Object)
、getChar(Object)
等多种返回具体值类型的方法。
Class<Teacher> clazz = Teacher.class; |
输出:
无参构造执行 |
整合一下
import java.lang.reflect.Field; |
输出:
无参构造执行 |
操作方法对象(Method)
介绍
java.lang.reflect.Method 是 Java 提供的方法对象。类中每个方法对应一个 Method 对象。
通过 Class 对象获取方法对象,返回值都是 Method
或 Method[]
。
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) |
代码示例:
import java.lang.reflect.Method; |
输出:
无参构造执行 |
使用反射操作泛型
操作泛型
没有出现泛型之前,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 类中的类型但是又和原始类型齐名的类型。
代码实现
import java.lang.reflect.Method; |
输出:
class java.lang.Integer |
使用反射突破泛型的限制
public class TestGeneric { |