Java基础——反射是什么?

288 阅读6分钟

一、概述


根据虚拟机的工作原理,一般情况下,类需要经过:加载->验证->准备->解析->初始化->使用->卸载这个过程,如果需要反射的类没有在内存中,那么首先会经过加载这个过程,并在在内存中生成一个class对象,有了这个class对象的引用,就可以发挥开发者的想象力,做自己想做的事情了。

反射的作用

  • 需要访问隐藏属性或者调用方法改变程序原来的逻辑,这个在开发中很常见的,由于一些原因,系统并没有开放一些接口出来,这个时候利用反射是一个有效的解决方法
  • 自定义注解,注解就是在运行时利用反射机制来获取的。
  • 在开发中动态加载类,比如在Android中的动态加载解决65k问题等等,模块化和插件化都离不开反射,离开了反射寸步难行。

二、具体API


1、java.lang.Class 类

java.lang.Class 类是Java中的反射中心。

Class类的一个对象表示运行时程序中的一个类。 我们可以使用 Class 类在运行时查找类的信息。

我们可以通过以下方式获取类的Class对象的引用:

  • 使用类文字
    Class<Test> testClass = Test.class;

int.class 和 Integer.TYPE 指的是同一个类对象。

Class c = boolean.class;
    c = Boolean.TYPE;
  • 使用 Object 类的 getClass() 方法
    class Test{
  
}
public class Main {
  public static void main(String[] args) {
    Test   testRef = new Test();
    Class testClass = testRef.getClass();

  }
}
  • 使用Class类的forName()方法
//源码:
Class<?>   forName(String  className)
Class<?>   forName(String name,  boolean initialize, ClassLoader loader)

//示例
class MyClass {
  static {
    System.out.println("Loading class MyClass...");
  }
}

public class Main {
  public static void main(String[] args) {
    try {
      String className = "MyClass";
      boolean initialize = false;

      ClassLoader cLoader = Main.class.getClassLoader();
      Class c = Class.forName(className, initialize, cLoader);
      className = "MyClass";
      System.out.println("about to load");
      // Will load and initialize the class
      c = Class.forName(className);
    } catch (ClassNotFoundException e) {
      System.out.println(e.getMessage());
    }
  }
}

2、Java类反射

我们可以使用Java反射来获取关于类的信息,例如作为其包名称,其访问修饰符等,详细如下:

  • 获取类名
String simpleName = boolean.class.getSimpleName();
  • 获取类的修饰符

类的修饰符是关键字之前的关键字类在类声明中,如 abstract , public Class 中的 getModifiers()方法返回类的所有修饰符 getModifiers()方法返回一个整数。我们必须调用 java.lang.reflect.Modifier.toString(int modifiers)以获得修饰符的文本形式(待写代码测试)

  • 要获取超类(父类)的名称

请使用 Class 中的 getSuperclass()方法。 如果对Object类调用getSuperclass()方法,它将返回null,因为它没有超类。

  • 要获取类实现的所有接口的名称
Class[] interfaces = c.getInterfaces();

3、Java字段反射

我们可以使用java.lang.reflect.Field类来获取关于类中的字段的信息。在Class类返回关于字段的 Field 对象有以下四种:

  1. getFields()方法返回所有可访问的公共字段在类中声明或继承自超类。
Field[] getFields()
  1. getDeclaredFields()方法返回所有字段只出现在类的声明中(不是从继承的字段)。
Field[] getDeclaredFields()
  1. getField(String name)通过字段名获取 Field 对象,包括父类以及实现的接口类。
Field getField(String name)
  1. getDeclaredField(String name)通过(本类中)字段名获取 Field 对象。
Field getDeclaredField(String name)

4、Java 方法反射

方法反射关键字如下:

  1. 方法:java.lang.reflect.Method 类的实例表示一个方法,继承自一个通用的抽象超类可执行—— Executable
  2. 参数:方法中的参数由 Parameter 类的对象表示

getParameters()方法获取所有参数作为 Parameter 的数组 getTypeParameters()方法返回一个TypeVariable数组,该数组表示通用方法或构造函数的类型参数

  1. 修饰符:方法中的修饰符getModifiers()方法将修饰符作为int返回

5、Java 构造函数反射

Class 类获取有关构造函数的信息有如下四种:

  1. getConstructors()方法返回当前和超类的所有公共构造函数。
Constructor[] getConstructors()
  1. getDeclaredConstructors()方法返回当前类的所有声明的构造函数。
Constructor[]  getDeclaredConstructors()
  1. getConstructor(Class ... parameterTypes)通过参数类型获取构造函数对象。
Constructor<T> getConstructor(Class...  parameterTypes)
  1. getDeclaredConstructor(Class ... parameterTypes)通过参数类型获取构造函数对象。
Constructor<T> getDeclaredConstructor(Class...  parameterTypes)

6、Java反射对象创建

我们可以使用反射动态创建类的对象。通过调用其中一个构造函数。然后我们可以访问对象的字段的值,设置它们的值,并调用它们的方法。

  1. 创建反射对象有两种方法:
  • 使用no-args构造函数
  • 使用带参数的构造函数
  1. 无参数构造函数

如果你有一个 Class 对象的引用,你可以创建一个对象该类对Class类使用 newInstance()方法。此方法不使用参数,并且是等效的使用new运算符的类的no-args构造函数。

String s = String.class.newInstance();
  1. 带参数的构造函数

通过调用特定的构造函数使用反射创建对象。示例如下:

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

class MyClass {
  public MyClass(int i, String s) {
    System.out.println("called");
    System.out.println(i);
    System.out.println(s);
  }
}
public class Main {
  public static void main(String[] args) {
  //第一步
    Class<MyClass> myClass = MyClass.class;
    try {
    //第二步
      Constructor<MyClass> cons = myClass.getConstructor(int.class,
          String.class);
          //第三步
      MyClass chris = cons.newInstance(1, "abc");
      System.out.println(chris);
    } catch (NoSuchMethodException | SecurityException | InstantiationException
        | IllegalAccessException | IllegalArgumentException
        | InvocationTargetException e) {
      System.out.println(e.getMessage());
    }
  }
}
  1. 调用方法

通过上面的构造方法拿到对象之后,就可以开始调用方法了,直接看示例:

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

class MyClass {
  public MyClass() {
  }

  public void setName(String n) {
    System.out.println(n);
  }
}

public class Main {
  public static void main(String[] args) {
  //第一步
    Class<MyClass> myClass = MyClass.class;
    try {
    //第二步
      MyClass p = myClass.newInstance();
      //第三步
      Method setName = myClass.getMethod("setName", String.class);
      //第四步,调用方法(静态方法第一个参数可为null,待测试)
      setName.invoke(p, "abc");
    } catch (InstantiationException | IllegalAccessException
        | NoSuchMethodException | SecurityException | IllegalArgumentException
        | InvocationTargetException e) {
      System.out.println(e.getMessage());
    }
  }
}

7、Java 反射字段访问

反射字段访问分为读取字段的值和设置字段的值,而且字段访问不分静态和非静态(都一样),具体看示例:

import java.lang.reflect.Field;

class MyClass {
  public String name = "Unknown";
  public MyClass() {
  }
  public String toString() {
    return "name=" + this.name;
  }
}
public class Main {
  public static void main(String[] args) {
  //第一步
    Class<MyClass> ppClass = MyClass.class;
    try {
    //第二步
      MyClass p = ppClass.newInstance();
      //第三步 获取字段
      Field name = ppClass.getField("name");
      //如果是私有字段,需要添加下面注释的这一步
      //nameField.setAccessible(true);
      //第四步 取字段的值
      String nameValue = (String) name.get(p);
      System.out.println("Current name is " + nameValue);
      //第四步 设置字段的值
      name.set(p, "abc");
      nameValue = (String) name.get(p);
      System.out.println("New  name is " + nameValue);
    } catch (InstantiationException | IllegalAccessException
        | NoSuchFieldException | SecurityException | IllegalArgumentException e) {
      System.out.println(e.getMessage());
    }
  }
}

8、Java数组反射

  1. 通过反射创建数组并操作器元素
import java.lang.reflect.Array;

public class Main {
  public static void main(String[] args) {
    try {
    //int[] my = new int[2];
      Object my = Array.newInstance(int.class, 2);

      int n1 = Array.getInt(my, 0);
      int n2 = Array.getInt(my, 1);
      System.out.println("n1 = " + n1 + ", n2=" + n2);

      Array.set(my, 0, 11);
      Array.set(my, 1, 12);

      n1 = Array.getInt(my, 0);
      n2 = Array.getInt(my, 1);
      System.out.println("n1 = " + n1 + ", n2=" + n2);
    } catch (NegativeArraySizeException | IllegalArgumentException
        | ArrayIndexOutOfBoundsException e) {
      System.out.println(e.getMessage());
    }
  }
}
  1. 通过反射获取数组的维度,如三维数组
public class Main {
  public static void main(String[] args) {
    int[][][] intArray = new int[1][2][3];

    System.out.println("int[][][] dimension is " + getArrayDimension(intArray));
  }

  public static int getArrayDimension(Object array) {
    int dimension = 0;
    Class c = array.getClass();
    if (!c.isArray()) {
      throw new IllegalArgumentException("Object is not  an  array");
    }
    while (c.isArray()) {
      dimension++;
      //数组元素的类型
      c = c.getComponentType();
    }
    //最终的数组维度
    return dimension;
  }
}
  1. 通过反射动态扩展数组

Java数组是一个固定长度的数据结构。要放大数组,我们可以创建一个更大尺寸的数组,并将旧数组元素复制到新数组元素。

import java.lang.reflect.Array;
import java.util.Arrays;

public class Main {
  public static void main(String[] args) {
    int[] ids = new int[2];
    System.out.println(ids.length);
    System.out.println(Arrays.toString(ids));

    ids = (int[]) expandBy(ids, 2);

    ids[2] = 3;
    System.out.println(ids.length);
    System.out.println(Arrays.toString(ids));
  }

  public static Object expandBy(Object oldArray, int increment) {
    Object newArray = null;
    int oldLength = Array.getLength(oldArray);
    int newLength = oldLength + increment;
    Class<?> c = oldArray.getClass();
    newArray = Array.newInstance(c.getComponentType(), newLength);
    System.arraycopy(oldArray, 0, newArray, 0, oldLength);
    return newArray;
  }
}

三、推荐反射工具类(源码为kotlin)


EasyReflect 优雅的反射功能库使用方法

EasyReflect 优雅的反射功能库源码

四、结束语


本文为学习笔记,全文来自Java反射

文章图片以及概述来自Java反射以及在Android中的特殊应用

最后感谢阅读!