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 字段赋值时,首个参数传空即可,示例:

  1. enum Tweedle { DEE, DUM }
  2. public class Book {
  3. private long chapters = 0;
  4. public String[] characters = { "Alice", "White Rabbit" };
  5. public Tweedle twin = Tweedle.DEE;
  6. public static void main(String... args) {
  7. Book book = new Book();
  8. String fmt = "%6S: %-12s = %s%n";
  9. try {
  10. Class<?> c = book.getClass();
  11. Field chap = c.getDeclaredField("chapters");
  12. chap.setAccessible(true);
  13. out.format(fmt, "before", "chapters", book.chapters);
  14. chap.setLong(book, 12);
  15. out.format(fmt, "after", "chapters", chap.getLong(book));
  16. Field chars = c.getDeclaredField("characters");
  17. out.format(fmt, "before", "characters",
  18. Arrays.asList(book.characters));
  19. String[] newChars = { "Queen", "King" };
  20. chars.set(book, newChars);
  21. out.format(fmt, "after", "characters",
  22. Arrays.asList(book.characters));
  23. Field t = c.getDeclaredField("twin");
  24. out.format(fmt, "before", "twin", book.twin);
  25. t.set(book, Tweedle.DUM);
  26. out.format(fmt, "after", "twin", t.get(book));
  27. // production code should handle these exceptions more gracefully
  28. } catch (NoSuchFieldException x) {
  29. x.printStackTrace();
  30. } catch (IllegalAccessException x) {
  31. x.printStackTrace();
  32. }
  33. }
  34. }
  35. // 输出结果
  36. /**
  37. * BEFORE: chapters = 0
  38. * AFTER: chapters = 12
  39. * BEFORE: characters = [Alice, White Rabbit]
  40. * AFTER: characters = [Queen, King]
  41. * BEFORE: twin = DEE
  42. * AFTER: twin = DUM
  43. */

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() {

    1. class Body {
    2. }
    3. return new Body();

    } }

public class Main extends Human {

  1. public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
  2. Human human = new Human();
  3. System.out.println( human.body().getClass().getEnclosingMethod()); // 返回:public java.lang.Object classtest.Human.body()
  4. }

}

  1. <a name="9MtIs"></a>
  2. #### <br />
  3. <a name="88APT"></a>
  4. #### 1.2.2.1 获取方法类型
  5. 下面的图是获取类型的所有方法
  6. ![](https://cdn.nlark.com/yuque/0/2020/png/699419/1606392577090-bcbf373b-53db-4b31-b4e8-fec0a134e12f.png#align=left&display=inline&height=208&margin=%5Bobject%20Object%5D&originHeight=538&originWidth=1470&size=0&status=done&style=none&width=567)
  7. 代码示例:
  8. ```java
  9. public class Main {
  10. private static final String fmt = "%s: %s%n";
  11. <E extends RuntimeException> List<String> genericThrow(String... some) throws E { return null; }
  12. public static void main(String[] args) throws NoSuchMethodException {
  13. Method m = Main.class.getDeclaredMethod("genericThrow", String[].class);
  14. System.out.format("%s%n", m.toGenericString());
  15. // 返回值类型
  16. System.out.format(fmt, "ReturnType", m.getReturnType());
  17. System.out.format(fmt, "GenericReturnType", m.getGenericReturnType());
  18. // 参数类型
  19. Class<?>[] pType = m.getParameterTypes();
  20. Type[] gpType = m.getGenericParameterTypes();
  21. for (int i = 0; i < pType.length; i++) {
  22. System.out.format(fmt, "ParameterType", pType[i]);
  23. System.out.format(fmt, "GenericParameterType", gpType[i]);
  24. }
  25. // 抛出异常类型
  26. Class<?>[] xType = m.getExceptionTypes();
  27. Type[] gxType = m.getGenericExceptionTypes();
  28. for (int i = 0; i < xType.length; i++) {
  29. System.out.format(fmt, "ExceptionType", xType[i]);
  30. System.out.format(fmt, "GenericExceptionType", gxType[i]);
  31. }
  32. }
  33. }
  34. // 输出结果
  35. /**
  36. * <E> java.util.List<java.lang.String> classtest.Main.genericThrow(java.lang.String...) throws E
  37. * ReturnType: interface java.util.List
  38. * GenericReturnType: java.util.List<java.lang.String>
  39. * ParameterType: class [Ljava.lang.String;
  40. * GenericParameterType: class [Ljava.lang.String;
  41. * ExceptionType: class java.lang.RuntimeException
  42. * GenericExceptionType: E
  43. */


1.2.2.2 调用方法

使用 invoke 调用,首参数是实例对象,如果是静态方法则为传 null,跟字段同样,如果是非 public 方法的话,需要事先调用setAccessible(true) ,下面是例子,一个静态私有可变参数的方法

  1. public class Main {
  2. static class MethodTest {
  3. private static String splice(String interval,String ...args){
  4. StringBuilder returnValue = new StringBuilder();
  5. for (int i = 0; i < args.length; i++) {
  6. if (i!=0){
  7. returnValue.append(interval);
  8. }
  9. returnValue.append(args[i]);
  10. }
  11. return returnValue.toString();
  12. }
  13. }
  14. public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
  15. Method spliceMethod = MethodTest.class.getDeclaredMethod("splice",String.class,String[].class);
  16. spliceMethod.setAccessible(true);
  17. String result = (String) spliceMethod.invoke(null,",",new String[]{"1","2","3"});
  18. System.out.println(result);
  19. }
  20. }

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) {

    1. class ConstructorLocalClass {
    2. }
    3. object = new ConstructorLocalClass();

    } }

public class Main { public static void main(String[] args) { ConstructorTest constructorTest = new ConstructorTest<>(“”,1,2); Constructor<?> constructor = constructorTest.object.getClass().getEnclosingConstructor(); System.out.println(constructor.toGenericString()); } } //输出:classtest.ConstructorTest(T,long…)

  1. <a name="9To64"></a>
  2. #### <br />
  3. <a name="wEm3V"></a>
  4. #### 1.2.3.1 创建类实例
  5. 有两种反射方法可以创建类的实例,[`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...-) 这种方式,原因如下几点:
  6. - [`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...-) 则可以调用任意参数的构造函数
  7. - [`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) 异常
  8. - [`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...-) 则可以调用私有构造函数
  9. 下面是例子:
  10. ```java
  11. class ConstructorTest<T> {
  12. public T message;
  13. private ConstructorTest(T message, long... list) {
  14. this.message = message;
  15. System.out.println(Arrays.toString(list));
  16. }
  17. }
  18. public class Main {
  19. public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
  20. Constructor<?> constructor = ConstructorTest.class.getDeclaredConstructor(Object.class,long[].class);
  21. constructor.setAccessible(true);
  22. ConstructorTest<?> t = (ConstructorTest<?>) constructor.newInstance("message",new long[]{1,2});
  23. System.out.println(t.message);
  24. }
  25. }

1.3 Arrays and Enumerated Types

根据 Java 虚拟机的规定,数组和枚举都属于 classes。大多 Class 的方法适用于它们。反射为数组和枚举提供了一些特殊的 API

1.3.1 Arrays

通过 Class.isArray() 判断类是否是 Array 类型
只能通过 java.lang.reflect.Array.newInstance() 的方式反射实例化对象,下面的例子会展示一个一维数组和二维数组的创建:

  1. public class Main {
  2. public static void main(String[] args) {
  3. // 创建一维数组
  4. Object object = Array.newInstance(String.class, 3);
  5. Array.set(object, 2, "2");
  6. System.out.println("一维数组:");
  7. System.out.println(Arrays.toString((Object[]) object));
  8. System.out.println("二维数组:");
  9. // 创建二维数组
  10. Object matrix = Array.newInstance(int.class, 2, 2);
  11. Object row0 = Array.get(matrix, 0);
  12. Object row1 = Array.get(matrix, 1);
  13. Array.setInt(row0, 0, 1);
  14. Array.setInt(row0, 1, 2);
  15. Array.setInt(row1, 0, 3);
  16. Array.setInt(row1, 1, 4);
  17. for (int i = 0; i < 2; i++)
  18. for (int j = 0; j < 2; j++)
  19. System.out.format("matrix[%d][%d] = %d%n", i, j, ((int[][]) matrix)[i][j]);
  20. }
  21. }
  22. // 输出
  23. /**
  24. * 一维数组:
  25. * [null, null, 2]
  26. * 二维数组:
  27. * matrix[0][0] = 1
  28. * matrix[0][1] = 2
  29. * matrix[1][0] = 3
  30. * matrix[1][1] = 4
  31. */

1.3.2 Enumerated Types

枚举类的用法跟一般类的类很像,其中 Class.isEnum() 方法时判断类是否是枚举类,Class.getEnumConstants() 则会返回枚举中定义的枚举。对于字段,则有 java.lang.reflect.Field.isEnumConstant() 返回是否是枚举字段

2. 反射的实现原理

2.1 类成员信息的获取

反射无论是基于类还是基于对象,都绕不开 Class 类,而 Class 类本质上是 .class 文件在 JVM 中的映射实例。
.class 的文件分析在这里不会多叙,可以看《Java 虚拟机规范》,后面我也会专门写一篇文章分析 class,下面贴了一张书的截图,可以看出文件里包含了类中所定义的所有成员与属性
image.png

2.2 Field 的获取和赋值源码分析

获取方法包含 getFieldgetDeclaredField 两类,所以获取的就看分析这两个方法即可,而其中 getField 又包含了 getDeclaredField 的代码,所以下面只看 getField

2.3 Method 的获取和 invoke 源码分析

3. 反射的性能损耗与优化

标签