---
反射
---

#### 目录

1. 思维导图
2. 定义
3. 简单示例
4. Class
   - 如何获取 Class
   - 通过 Class 获取类修饰符和类型
5. Member
   - Field
   - Method
   - Constructor
6. 数组和枚举
7. 反射优缺点
8. 参考

#### 思维导图

![](https://i.loli.net/2019/01/01/5c2ad008a6bdc.png)

#### 定义

1. 对于任意一个类，都能知道这个类的所有属性和方法
2. 对于任意一个对象，都能知道调用它的任意一个属性和方法

这种动态获取信息以及动态调用对象的方法的功能成为 Java 语言的反射机制。

反射机制主要设计两个类：Class 和 Member。

#### 简单示例

```java
public class UserInfo {
    private String name;
    private int age;

    private UserInfo(String name, int age) {
        this.name = name;
        this.age = age;
//        try {
//            throw new Exception("SingleTon can't ref");
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
    }

    @Override
    public String toString() {
        return "UserInfo{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
```

既然构造方法都私有化了，怎么获取类实例呢？

```java
public class RefDemo {
    public static void main(String[] args) throws Exception {
        Class c = UserInfo.class;
        Constructor constructor = c.getDeclaredConstructor(String.class, int.class);
        constructor.setAccessible(true);
        UserInfo userInfo = (UserInfo) constructor.newInstance("Omooo", 18);
        System.out.println(userInfo.toString());
    }
}

输出：UserInfo{name='Omooo', age=18}
```

#### Class

Class 是反射操作的基础，每个 class 类，无论创建多少个实例对象，在 JVM 中都对应同一个 Class 对象。

##### 如何获取 Class

Java 反射包 java.lang.reflect 中的所有类都没有 public 构造方法，想要获取这些类的实例，只能通过 Class 类获取，所以说如何想使用反射，必须先获取的 Class 对象。

获取 Class 对象有以下几种方法：

1. Object.getClass()

   通过对象实例获取对应的 Class 对象。

   ```java
   Class c = "Omooo".getClass();
   ```

2. The.Class

   ```java
   Class c = String.class;
   ```

3. Class.forName()

   ```java
   Class c = Class.forName("java.lang.String");
   //对于数组比较特殊 String[]：
   Class cStringArray = Class.forName("[[Ljava.lang.String;")	
   ```

4. The.TYPE

   ```java
   Class c = Double.TYPE;
   ```

5. Class.getSuperclass()

##### 通过 Class 获取类修饰符和类型

```java
    public static void main(String[] args) throws Exception {
        Class c = HashMap.class;
        //获取类名 UserInfo
        String className = c.getName();
        System.out.println("类名: " + className);
        //获取类限定符 public
        String modifier = Modifier.toString(c.getModifiers());
        System.out.println("类限定符: " + modifier);
        //获取类泛型参数
        TypeVariable[] typeParameters = c.getTypeParameters();
        if (typeParameters.length > 0) {
            StringBuilder sb = new StringBuilder("泛型参数：");
            for (TypeVariable typeVariable : typeParameters) {
                sb.append(typeVariable.getName());
                sb.append("___");
            }
            System.out.println(sb.toString());
        } else {
            System.out.println("类泛型参数为空！");
        }
        //获取类所实现的接口
        Type[] interfaces = c.getGenericInterfaces();
        if (interfaces.length > 0) {
            StringBuilder sb = new StringBuilder("接口信息：");
            for (Type type : interfaces) {
                sb.append(type.toString());
                sb.append("___");
            }
            System.out.println(sb.toString());
        } else {
            System.out.println("类接口信息为空！");
        }
        //获取继承的父类信息
        List<Class> superClazzs = new ArrayList<>();
        getSuperClassName(c, (ArrayList<Class>) superClazzs);
        if (superClazzs.size() != 0) {
            StringBuilder sb = new StringBuilder("父类信息：");
            for (Class clazz : superClazzs) {
                sb.append(clazz.getName());
                sb.append("___");
            }
            System.out.println(sb.toString());
        }
        //获取类注解信息
        Annotation[] annotations = c.getAnnotations();
        if (annotations.length != 0) {
            StringBuilder sb = new StringBuilder("类注解信息：");
            for (Annotation annotation : annotations) {
                sb.append(annotation.toString());
                sb.append("___");
            }
            System.out.println(sb.toString());
        } else {
            System.out.println("类注解信息为空！");
        }
    }

输出：
类名: java.util.HashMap
类限定符: public
泛型参数：K___V___
接口信息：java.util.Map<K, V>___interface java.lang.Cloneable___interface java.io.Serializable___
父类信息：java.util.AbstractMap___java.lang.Object___
类注解信息为空！
```

#### Member

Member 有三个实现类：

1. Field 对应类变量
2. Method 对应类方法
3. Constructor 对应类构造方法

##### Field

通过 Field 你可以访问给定对象的类变量，包括获取变量的类型、修饰符、注解、变量名、变量值等等，即使变量是 private 的。

- 获取 Field

  Class 提供了四种方法获得给定类的 Field。

  - getDeclaredField(String name)	获取指定的变量，包括 private 的
  - getField(String name) 获取指定的变量，只能是 public 的
  - getDweclaredFields()  获取所有的变量，包括 private 的
  - getFields() 获取所有的变量，只能是 public 的

- 获取变量类型、修饰符、注解

  ```java
      public static void main(String[] args) {
          Class clazz = UserInfo.class;
          Field[] fields = clazz.getDeclaredFields();
          if (fields.length > 0) {
              for (Field field : fields) {
                  StringBuilder sb = new StringBuilder();
                  //变量名
                  String name = field.getName();
                  sb.append("变量名：");
                  sb.append(name);
                  //变量类型
                  String type = field.getType().getName();
                  sb.append("变量类型：");
                  sb.append(type);
                  //变量修饰符
                  String modifer = Modifier.toString(field.getModifiers());
                  sb.append("变量修饰符：");
                  sb.append(modifer);
                  //变量上的注解
                  Annotation[] annotations = field.getDeclaredAnnotations();
                  if (annotations.length > 0) {
                      for (Annotation annotation : annotations) {
                          String annName = annotation.toString();
                          sb.append("注解：");
                          sb.append(annName);
                      }
                  }
                  System.out.println(sb);
              }
          }
      }
  ```

- 获取、设置变量值

  ```java
      public static void main(String[] args) throws Exception {
          UserInfo userInfo = new UserInfo("Omooo", 18);
          System.out.println("Before: " + userInfo.toString());
          Class clazz = userInfo.getClass();
          Field nameField = clazz.getDeclaredField("name");
          nameField.setAccessible(true);
          Field ageField = clazz.getField("age");
          nameField.set(userInfo, "Tom");
          ageField.setInt(userInfo, 19);
          System.out.println("After: " + userInfo.toString());
      }
  ```

  其中，对于 private 的属性、方法或者构造函数，比如要有：

  ```java
  xxx.setAccessible(true);
  ```

  这个方法是 AccessibleObject 中的一个方法，Field、Method、Constructor 都是其子类，该方法的作用就是可以取消 Java 语言访问权限检查。

##### Method

- 获取 Method

  Class 依然提供了四种方法获取 Method：

  ```
  getDeclaredMethod(String name, Class<?>... parameterTypes)
  getMethod(Sting name, Class<?>... parameterTypes)
  getDeclaredMethods()
  getMethods()
  ```

  获取带参数方法时，如果参数类型错误会报 NoSuchMethodException，对于参数是泛型的情况下，泛型必须当成 Object 处理。

- 获取方法返回类型

  ```java
  getReturnType() 获取目标方法返回类型对应的 Class 对象
  getGenericReturnType() 获取目标方法返回类型对应的 Type 对象
  ```

  对于返回值是普通类型如 Object、int、String 等，两者返回值一样。但是有两种特殊情况：

  ```java
  //1.返回值是泛型
  public T function()
  getReturnType() : class java.lang.Object
  getGenericReturnType() : T
  //2.返回值为参数化类型
  public Class<String> function()
  getReturnType(): class java.lang.Class
  getGenericReturnType(): java.lang.Class<java.lang.String>
  ```

- 获取方法参数类型

  ```java
  getParameterTypes()	获取目标方法各参数类型对应的 Class 对象
  getGenericParameterTypes() 获取目标方法各参数类型对应的 Type 对象
  ```

  返回值为数组。

- 获取方法声明抛出的异常的类型

  ```java
  getExceptionTypes() 获取目标方法抛出的异常类型对应的 Class 对象
  getGenericExceptionTypes() 获取目标方法抛出的异常类型对应的 Type 对象
  ```

  返回值为数组。

- 获取方法参数名称

  .class 文件中默认不存储方法参数名称，如果想要获取方法参数名称，需要在编译的时候加上 -parameters 参数。构造方法的参数获取同样。

  ```java
      public static void main(String[] args) throws Exception {
          UserInfo userInfo = new UserInfo("Omooo", 18);
          Class clazz = userInfo.getClass();
          Method nameMethod = clazz.getMethod("setName", String.class);
          Parameter[] parameters = nameMethod.getParameters();
          if (parameters.length > 0) {
              for (Parameter p : parameters) {
                  StringBuilder sb = new StringBuilder();
                  sb.append("参数类型: ");
                  sb.append(p.getType());
                  sb.append("参数名称: ");
                  sb.append(p.getName());
                  System.out.println(sb.toString());
              }
          }
      }
  输出：参数类型: class java.lang.String参数名称: arg0
  ```

  如果编译未加上 -parameters 参数，返回的参数名则形如 ”argX“，X 代表参数的位置。

- 获取方法修饰符

  ```java
  method.getModifiers()
  ```

- 通过反射调用方法

  反射通过 Method 的 invoke() 方法来调用目标方法。第一个参数为需要调用的目标类对象，如果方法是 static 的，则该参数为 null，后面的参数都为目标方法的参数值，顺序与目标方法声明中的参数顺序一致。

  需要注意的是，被调用的方法本身所抛出的异常在反射中都会以 InvocationTargetException 抛出。换句话说，反射调用过程中如果异常 InvocationTargetException 抛出，说明反射调用本身是成功，因为这个异常是目标方法本身所抛出的异常。

##### Constructor

通过反射访问构造方法并通过构造方法构建新的对象。

- 获取构造方法

  和 Method 一样，Class 也提供了四种方法获取：

  ```java
  getDeclaredConstructor(Class<?>... parameterTypes)
  getConstructor(Class<?>... parameterTypes)
  getDeclaredConstructors()
  getConstructors()
  ```

  构造方法的名称、限定符、参数、声明的异常等获取方法和 Method 一致。

- 创建对象

  创建对象有两种方法：

  1. java.lang.reflect.Constructor.newInstance()
  2. Class.newInstance()

  一般来讲，优先使用第一种方法，第二种方法已经被废弃了。它们之间的区别有：

  - 方法一可以调用任意参数的构造方法，而方法二只能调用无参的构造方法
  - 方法一会将原方法抛出的异常都包装成 InvocationTaregtException 抛出，而方法二会将原方法中的异常不做处理原样抛出
  - 方法一不需要方法权限，方法二只能调用 public 的构造方法

  需要注意的是：反射不支持自动装箱，传入参数时要小心。自动装箱是在编译期间的，而反射是在运行期间。

#### 数组和枚举

数组和枚举也是对象，但是在反射中，对数组和枚举的创建、访问和普通对象有点不同，Java 反射为数组和枚举提供了一些特定的 API 接口。

##### 数组

- 数组类型

  数组本质上也是一个对象，所以它也有自己的类型。例如对于 int[] array，数组类型为 class [I。数组类型中的 [ 个数表示数组的维度，其后面的字母代表数组元素类型，即 int，一般为类型的首字母大小。（ long 类型例外，为 J，因为 L 被引用对象类型占用了）

- 创建和初始化数组

  Java 反射为我们提供了 java.lang.reflect.Array 类用来创建和初始化数组。

  ```java
  Array.newInstance(Class<?> componentType, int... dimensions)
  Array.set(Object array, int index, int value)
  Array.get(Object array, int index)
  ```

  例如，用反射创建 int[] array = new int[]{1, 2}：

  ```java
      public static void main(String[] args) throws Exception {
          Object array = Array.newInstance(int.class, 2);
          Array.set(array, 0, 1);
          Array.set(array, 1, 2);
          System.out.println(Array.get(array, 1));
      }
  ```

- 多维数组

  Java 反射没有提供能够直接访问多维数组元素的 API，但你可以把多维数组当成数组的数组处理。

  ```java
      public static void main(String[] args) throws Exception {
          Object matrix = Array.newInstance(int.class, 2, 2);
          Object row1 = Array.get(matrix, 0);
          Object row2 = Array.get(matrix, 1);
          Array.set(row1, 0, 1);
          Array.set(row1, 1, 2);
          Array.set(row2, 0, 3);
          Array.set(row2, 1, 4);
      }
  ```

##### 枚举

枚举隐式继承自 java.lang.Enum，Enum 继承自 Object，所以枚举本质上也是一个类，也可以有成员变量、构造方法、方法等。对于普通类所能使用的反射方法，枚举都能使用，另外，Java 反射额外提供了几个方法。

```java
Class.isEnum()
Class.getEnumConstants()
java.lang.reflect.Field.isEnumConstant()
```

#### 反射优缺点

优点：动态改变类行为

缺点：

1. 性能开销

   反射涉及类型动态解析，所以 JVM 无法对这些代码进行优化。因此，反射操作的效率低下，尽量避免在高性能要求下使用反射。

2. 安全限制

   使用反射要求程序必须在一个没有安全限制的环境中运行。

3. 内部曝光

   由于反射允许代码执行一些在正常情况下不被允许的操作，所以使用反射可能会导致意料之外的副作用。

#### 参考

[Java 反射完全解析](https://www.jianshu.com/p/607ff4e79a13)