1. 基本概念
本节内容参考 Oracle 官方文档
通过反射,Java 代码可以发现有关一家在类的字段,方法和构造函数的信息,并可以在安全限制内对这些字段,方法和构造函数进行操作
- 对于类:可以知道这个类的所有方法和字段
- 对于对象,可以调用任意方法和获取任意字段
下面会逐个说明上述的操作是如何使用的
1.1 Class
这里面的 Class 是指的 [java.lang.Class](https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html) ,可以说其是反射的起点, Class 浅析 这篇文章简单分析了下 Class 类。
通过 Class 对象可以获取此类的方法(java.lang.reflect.Method)、字段(java.lang.reflect.Field)、构造方法(java.lang.reflect.Constructor),可以看到这三个类都是属于 reflect 包下的,皆是反射的成员,而它们又都实现了 java.lang.reflect.Member 接口。所以接下来会分析下 Member 接口的定义与行为。
1.2 Member
Member 是定义获取 Method、Field 以及 Construtor 标识信息的接口。标识信息都有什么则通过它定义的方法就可知了,下面列出了它的方法和说明
| 方法 | 说明 |
|---|---|
| Class<?> _getDeclaringClass()_ | 获取定义此成员的 Class |
| String getName() | 获取成员名称 |
| int getModifiers() | 获取成员的修饰符 此方法大多和 java.lang.reflect.Modifier 配合使用,通过 Modifier 的静态方法与字段进行逻辑判断 |
| boolean isSynthetic() | 获取成员是否是由编译器生成的 |
1.2.1 Fields
类所定义的字段,获取方式如下有两类
- 获取 public 声明的字段,包括父类:
getFields()、getField(String name) - 获取非 public 声明的字段,不包括父类:
getDeclaredFields()、getDeclaredField(String name)
1.2.1.1 获取字段类型
通过 getType() 和 getGenericType() 方法可以获取到字段的类型和泛型类型
如果对 Type 和 GenericType 有不了解的,可以参考下Type 浅析 这篇文章,这里就不多说了
1.2.1.2 获取和修改字段值
用法比较简单,注意一点就是在获取和修改私有字段的时候要首先调用 Field 的 setAccessible(true) 方法,还有就是给 static 字段赋值时,首个参数传空即可,示例:
enum Tweedle { DEE, DUM }public class Book {private long chapters = 0;public String[] characters = { "Alice", "White Rabbit" };public Tweedle twin = Tweedle.DEE;public static void main(String... args) {Book book = new Book();String fmt = "%6S: %-12s = %s%n";try {Class<?> c = book.getClass();Field chap = c.getDeclaredField("chapters");chap.setAccessible(true);out.format(fmt, "before", "chapters", book.chapters);chap.setLong(book, 12);out.format(fmt, "after", "chapters", chap.getLong(book));Field chars = c.getDeclaredField("characters");out.format(fmt, "before", "characters",Arrays.asList(book.characters));String[] newChars = { "Queen", "King" };chars.set(book, newChars);out.format(fmt, "after", "characters",Arrays.asList(book.characters));Field t = c.getDeclaredField("twin");out.format(fmt, "before", "twin", book.twin);t.set(book, Tweedle.DUM);out.format(fmt, "after", "twin", t.get(book));// production code should handle these exceptions more gracefully} catch (NoSuchFieldException x) {x.printStackTrace();} catch (IllegalAccessException x) {x.printStackTrace();}}}// 输出结果/*** BEFORE: chapters = 0* AFTER: chapters = 12* BEFORE: characters = [Alice, White Rabbit]* AFTER: characters = [Queen, King]* BEFORE: twin = DEE* AFTER: twin = DUM*/
1.2.2 Methods
类所定义的方法,获取方式如下有三类
- 获取 public 声明的方法,包括父类:
getMethods()、getMethod(String name,Class<?>... parameterTypes) - 获取非 public 声明的方法,不包括父类:
getDeclaredMethods()、getDeclaredtMethod(String name,Class<?>... parameterTypes) 还有一种比较特殊
getEnclosingMethod(),是返回定义局部类的方法,例如下面的 ```java class Human {public Object body() {
class Body {}return new Body();
} }
public class Main extends Human {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {Human human = new Human();System.out.println( human.body().getClass().getEnclosingMethod()); // 返回:public java.lang.Object classtest.Human.body()}
}
<a name="9MtIs"></a>#### <br /><a name="88APT"></a>#### 1.2.2.1 获取方法类型下面的图是获取类型的所有方法代码示例:```javapublic class Main {private static final String fmt = "%s: %s%n";<E extends RuntimeException> List<String> genericThrow(String... some) throws E { return null; }public static void main(String[] args) throws NoSuchMethodException {Method m = Main.class.getDeclaredMethod("genericThrow", String[].class);System.out.format("%s%n", m.toGenericString());// 返回值类型System.out.format(fmt, "ReturnType", m.getReturnType());System.out.format(fmt, "GenericReturnType", m.getGenericReturnType());// 参数类型Class<?>[] pType = m.getParameterTypes();Type[] gpType = m.getGenericParameterTypes();for (int i = 0; i < pType.length; i++) {System.out.format(fmt, "ParameterType", pType[i]);System.out.format(fmt, "GenericParameterType", gpType[i]);}// 抛出异常类型Class<?>[] xType = m.getExceptionTypes();Type[] gxType = m.getGenericExceptionTypes();for (int i = 0; i < xType.length; i++) {System.out.format(fmt, "ExceptionType", xType[i]);System.out.format(fmt, "GenericExceptionType", gxType[i]);}}}// 输出结果/*** <E> java.util.List<java.lang.String> classtest.Main.genericThrow(java.lang.String...) throws E* ReturnType: interface java.util.List* GenericReturnType: java.util.List<java.lang.String>* ParameterType: class [Ljava.lang.String;* GenericParameterType: class [Ljava.lang.String;* ExceptionType: class java.lang.RuntimeException* GenericExceptionType: E*/
1.2.2.2 调用方法
使用 invoke 调用,首参数是实例对象,如果是静态方法则为传 null,跟字段同样,如果是非 public 方法的话,需要事先调用setAccessible(true) ,下面是例子,一个静态私有可变参数的方法
public class Main {static class MethodTest {private static String splice(String interval,String ...args){StringBuilder returnValue = new StringBuilder();for (int i = 0; i < args.length; i++) {if (i!=0){returnValue.append(interval);}returnValue.append(args[i]);}return returnValue.toString();}}public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {Method spliceMethod = MethodTest.class.getDeclaredMethod("splice",String.class,String[].class);spliceMethod.setAccessible(true);String result = (String) spliceMethod.invoke(null,",",new String[]{"1","2","3"});System.out.println(result);}}
1.2.3 Constructors
类所定义的构造方法,获取方式如下有三类
- 获取 public 声明的构造方法,包括父类:
getConstructors()、getConstructor(Class<?>... parameterTypes) - 获取非 public 声明的方法,不包括父类:
getDeclaredConstructors()、getDeclaredtConstructor(Class<?>... parameterTypes) 还有一种比较特殊
getEnclosingConstructor(),是返回定义局部类的方法,例如下面的 ```java class ConstructorTest{ public Object object; ConstructorTest(T message,long…list) {
class ConstructorLocalClass {}object = new ConstructorLocalClass();
} }
public class Main {
public static void main(String[] args) {
ConstructorTest
<a name="9To64"></a>#### <br /><a name="wEm3V"></a>#### 1.2.3.1 创建类实例有两种反射方法可以创建类的实例,[`java.lang.reflect.Constructor.newInstance()`](https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Constructor.html#newInstance-java.lang.Object...-) 和 [`Class.newInstance()`](https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#newInstance--) 。建议首选 [`Constructor.newInstance()`](https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Constructor.html#newInstance-java.lang.Object...-) 这种方式,原因如下几点:- [`Class.newInstance()`](https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#newInstance--) 只能调用无参的构造函数,[`Constructor.newInstance()`](https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Constructor.html#newInstance-java.lang.Object...-) 则可以调用任意参数的构造函数- [`Class.newInstance()`](https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#newInstance--) 抛出的异常是不可预知的,[`Constructor.newInstance()`](https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Constructor.html#newInstance-java.lang.Object...-) 则只会抛出 [`InvocationTargetException`](https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/InvocationTargetException.html) 异常- [`Class.newInstance()`](https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#newInstance--) 只能调用可见的构造函数,[`Constructor.newInstance()`](https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Constructor.html#newInstance-java.lang.Object...-) 则可以调用私有构造函数下面是例子:```javaclass ConstructorTest<T> {public T message;private ConstructorTest(T message, long... list) {this.message = message;System.out.println(Arrays.toString(list));}}public class Main {public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {Constructor<?> constructor = ConstructorTest.class.getDeclaredConstructor(Object.class,long[].class);constructor.setAccessible(true);ConstructorTest<?> t = (ConstructorTest<?>) constructor.newInstance("message",new long[]{1,2});System.out.println(t.message);}}
1.3 Arrays and Enumerated Types
根据 Java 虚拟机的规定,数组和枚举都属于 classes。大多 Class 的方法适用于它们。反射为数组和枚举提供了一些特殊的 API
1.3.1 Arrays
通过 Class.isArray() 判断类是否是 Array 类型
只能通过 java.lang.reflect.Array.newInstance() 的方式反射实例化对象,下面的例子会展示一个一维数组和二维数组的创建:
public class Main {public static void main(String[] args) {// 创建一维数组Object object = Array.newInstance(String.class, 3);Array.set(object, 2, "2");System.out.println("一维数组:");System.out.println(Arrays.toString((Object[]) object));System.out.println("二维数组:");// 创建二维数组Object matrix = Array.newInstance(int.class, 2, 2);Object row0 = Array.get(matrix, 0);Object row1 = Array.get(matrix, 1);Array.setInt(row0, 0, 1);Array.setInt(row0, 1, 2);Array.setInt(row1, 0, 3);Array.setInt(row1, 1, 4);for (int i = 0; i < 2; i++)for (int j = 0; j < 2; j++)System.out.format("matrix[%d][%d] = %d%n", i, j, ((int[][]) matrix)[i][j]);}}// 输出/*** 一维数组:* [null, null, 2]* 二维数组:* matrix[0][0] = 1* matrix[0][1] = 2* matrix[1][0] = 3* matrix[1][1] = 4*/
1.3.2 Enumerated Types
枚举类的用法跟一般类的类很像,其中 Class.isEnum() 方法时判断类是否是枚举类,Class.getEnumConstants() 则会返回枚举中定义的枚举。对于字段,则有 java.lang.reflect.Field.isEnumConstant() 返回是否是枚举字段
2. 反射的实现原理
2.1 类成员信息的获取
反射无论是基于类还是基于对象,都绕不开 Class 类,而 Class 类本质上是 .class 文件在 JVM 中的映射实例。
.class 的文件分析在这里不会多叙,可以看《Java 虚拟机规范》,后面我也会专门写一篇文章分析 class,下面贴了一张书的截图,可以看出文件里包含了类中所定义的所有成员与属性
2.2 Field 的获取和赋值源码分析
获取方法包含 getField 和 getDeclaredField 两类,所以获取的就看分析这两个方法即可,而其中 getField 又包含了 getDeclaredField 的代码,所以下面只看 getField
2.3 Method 的获取和 invoke 源码分析
3. 反射的性能损耗与优化
标签
