Java 反射是一个比较重要的知识点,你会在不少地方见到反射。它提供了 Java 语言在运行期间加载、探知和使用编译期间彻底未知的类的能力。这种能力在框架的编写中很是常见,例如动态代理中、类扫描解析中。html
反射机制:即 Java 语言在运行时有一种自观的能力,可以了解自身的状况并为下一步的动做作准备。反应出来就是,在运行时,对于一个类,咱们可以知道该类有哪些方法和属性。对于一个对象,咱们可以调用其任意的一个方法。这是一种动态获取类的信息以及动态调用对象方法的能力。java
Java 提供反射机制,依赖于 Class 类和 java.lang.reflect 类库。其主要的类以下:api
Class 类是 Java 中用来表示运行时类型信息的对应类。实际上在 Java 中每一个类都有一个 Class 对象,每当咱们编写而且编译一个新建立的类就会将相关信息写到 .class 文件里。当咱们 new 一个新对象或者引用静态成员变量时,JVM 中的类加载器子系统会将对应 Class 对象加载到 JVM 中,而后 JVM 再根据这个类型信息相关的 Class 对象建立咱们须要实例对象或者提供静态变量的引用值。咱们能够将 Class 类,称为类类型,一个 Class 对象,称为类类型对象(参考 《Thinking in Java》)。数组
Class 类有如下的特色:oracle
.class 文件存储了一个 Class 的全部信息,好比全部的方法,全部的构造函数,全部的字段(成员属性)等等。JVM 启动的时候经过 .class 文件会将相关的类加载到内存中,过程以下: 框架
上面提到 Class 类只有一个私有的构造函数。因此没法经过 new 的方法获取 Class 实例。函数
/* * Constructor. Only the Java Virtual Machine creates Class * objects. */
private Class() {}
复制代码
能够经过 Class 的 forName 方法获取 Class 实例,其中类的名称要写类的完整路径。该方法只能用于获取引用类型的类类型对象。性能
// 这种方式会使用当前的类的加载器加载,而且会将 Class 类实例初始化
Class<?> clazz = Class.forName("java.lang.String");
// 上面的调用方式等价于
Class<?> clazz = Class.forName("java.lang.String", true, currentLoader);
复制代码
使用该方法可能会抛出 ClassNotFoundException 异常,这个异常发生在类的加载阶段,缘由以下:spa
若是咱们有一个类的对象,那么咱们能够经过 Object.getClass 方法得到该类的 Class 对象。插件
// String 对象的 getClass 方法
Class clazz1 = "hello".getClass();
// 数组对象的 getClass 方法
Class clazz2 = (new byte[1024]).getClass();
System.out.println(class2) // 会输出 [B, [ 表明是数组, B 表明是 byte。即 byte 数组的类类型
复制代码
若咱们知道要获取的类类型的名称时,咱们可使用 class 语法获取该类类型的对象。
// 类
Class clazz = Integer.class;
// 数组
Class clazz2 = int [][].class;
复制代码
对于基本类型和 void 都有对应的包装类。在包装类中有一个静态属性 TYPE,保存了该来的类类型。以 Integer 类为例,其源码中定义了以下的静态属性:
/** * The {@code Class} instance representing the primitive type * {@code int}. * * @since JDK1.1 */
@SuppressWarnings("unchecked")
public static final Class<Integer> TYPE = (Class<Integer>) Class.getPrimitiveClass("int");
复制代码
生成 Class 类实例的方法:
Class clazz1 = Integer.TYPE;
Class clazz2 = Void.TYPE;
复制代码
Class 中有获取其余 Class 的方法,列举以下:
在讲 Field、Method、Constructor 以前,先说说 Member 和 AccessibleObject。Member 是一个接口,表示 Class 的成员,前面的三个类都是其实现类。
AccessibleObject 是 Field、Method、Constructor 三个类共同继承的父类,它提供了将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力。经过 setAccessible 方法能够忽略访问级别,从而访问对应的内容。而且 AccessibleObject 实现了 AnnotatedElement 接口,提供了与获取注解相关的能力。
Field 提供了有关类或接口的单个属性的信息,以及对它的动态访问的能力。
能够经过 Class 提供的方法,获取 Field 对象,具体以下:
方法返回值 | 方法名称 | 方法说明 |
---|---|---|
Field |
getDeclaredField(String name) |
获取指定name名称的(包含private修饰的)字段,不包括继承的字段 |
Field[] |
getDeclaredField() |
获取Class对象所表示的类或接口的全部(包含private修饰的)字段,不包括继承的字段 |
Field |
getField(String name) |
获取指定name名称、具备public修饰的字段,包含继承字段 |
Field[] |
getField() |
获取修饰符为public的字段,包含继承字段 |
Field 相关的 API 我就不全局列举出来了,能够点击这里查看。
Method 提供了有关类或接口的单个方法的信息,以及对它的动态访问的能力。
能够经过 Class 提供的方法,获取 Field 对象,具体以下:
方法返回值 | 方法名称 | 方法说明 |
---|---|---|
Method |
getDeclaredMethod(String name, Class<?>... parameterTypes) |
返回一个指定参数的Method对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。 |
Method[] |
getDeclaredMethod() |
返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的全部方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。 |
Method |
getMethod(String name, Class<?>... parameterTypes) |
返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。 |
Method[] |
getMethods() |
返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法。 |
Method 相关的 API 能够点击这里查看。
Constructor 提供了有关类的构造方法的信息,以及对它的动态访问的能力。
能够经过 Class 提供的方法,获取 Constructor 对象,具体以下:
方法返回值 | 方法名称 | 方法说明 |
---|---|---|
Constructor<T> |
getConstructor(Class<?>... parameterTypes) |
返回指定参数类型、具备public访问权限的构造函数对象 |
Constructor<?>[] |
getConstructors() |
返回全部具备public访问权限的构造函数的Constructor对象数组 |
Constructor<T> |
getDeclaredConstructor(Class<?>... parameterTypes) |
返回指定参数类型、全部声明的(包括private)构造函数对象 |
Constructor<?>[] |
getDeclaredConstructor() |
返回全部声明的(包括private)构造函数对象 |
Constructor 相关的 API 我就不全局列举出来了,能够点击这里查看。
在 Java 中数组也是一种类,Array 提供了动态建立数组和访问数组元素的静态方法。
经过 getXXX(Object array, int index)
方法,传入数组对象和下标索引,能够获取到该位置的值。
经过 newInstance(Class<?> componentType, int length)
方法,传入数组类型和长度,建立数组。以下:
// 下面建立的两个数组等价
int x[] = new int[10];
int y[] = (int[]) Array.newInstance(int.class, 10);
// 输出 true
System.out.println(x.length == y.length);
复制代码
经过 newInstance(Class<?> componentType, int... dimensions)
方法,建立多维数组。以下:
// 下面建立的两个数组等价
int x[][] = new int[10][10];
int y[][] = (int[][]) Array.newInstance(int.class, 10, 10);
// 输出 true
System.out.println(x.length == y.length);
复制代码
几乎全部的 Java 框架都会使用到反射,例如动态配置:读取写好的配置文件的值,而后经过反射机制将这些值设置到配置类中。总的来讲应用场景有以下几点:
固然反射也有一些缺点: