您的位置 首页 java

Java 反射详解

Java 反射是一个比较重要的知识点,你会在很多地方见到反射。它提供了 Java 语言在运行期间加载、探知和使用编译期间完全未知的类的能力。这种能力在框架的编写中非常常见,例如动态代理中、类扫描解析中。

反射的定义与作用

反射机制:即 Java 语言在运行时有一种自观的能力,能够了解自身的情况并为下一步的动作做准备。反应出来就是,在运行时,对于一个类,我们能够知道该类有哪些方法和属性。对于一个对象,我们能够调用其任意的一个方法。这是一种动态获取类的信息以及动态调用对象方法的能力。

实现反射的基础

Java 提供反射机制,依赖于 Class 类和 java.lang.reflect 类库。其主要的类如下:

  1. Class:表示类或者接口
  2. Field:表示类中的成员变量
  3. Method:表示类中的方法
  4. Constructor:表示类的构造方法
  5. Array:该类提供了动态创建数组和访问数组元素的静态方法

Class

Class 类是 Java 中用来表示运行时类型信息的对应类。实际上在 Java 中每个类都有一个 Class 对象,每当我们编写并且编译一个新创建的类就会将相关信息写到 .class 文件里。当我们 new 一个新对象或者引用静态成员变量时,JVM 中的类加载器子系统会将对应 Class 对象加载到 JVM 中,然后 JVM 再根据这个类型信息相关的 Class 对象创建我们需要实例对象或者提供静态变量的引用值。我们可以将 Class 类,称为类类型,一个 Class 对象,称为类类型对象(参考 《Thinking in Java》)。

Class 类有以下的特点:

  1. Class 类也是类的一种,class 则是关键字。
  2. Class 类只有一个私有的构造函数,只有 JVM 能够创建 Class 类的实例。
  3. 对于同一类的对象,在 JVM 中只有唯一一个对应的 Class 类实例来描述其类型信息。(同一个类:即包名 + 类名相同,且由同一个类加载器加载)

.class 文件存储了一个 Class 的所有信息,比如所有的方法,所有的构造函数,所有的字段(成员属性)等等。JVM 启动的时候通过 .class 文件会将相关的类加载到内存中,过程如下:

获取 Class 实例的方法

上面提到 Class 类只有一个私有的构造函数。所以无法通过 new 的方法获取 Class 实例。

 /*
 * Constructor. Only the Java Virtual Machine creates Class
 * objects.
 */private Class() {}  

Class.forName() 方法

可以通过 Class 的 forName 方法获取 Class 实例,其中类的名称要写类的完整路径。该方法只能用于获取引用类型的类类型对象。

 // 这种方式会使用当前的类的加载器加载,并且会将 Class 类实例初始化
Class<?> clazz = Class.forName("java.lang.String");
// 上面的调用方式等价于
Class<?> clazz = Class.forName("java.lang.String", true, currentLoader);  

使用该方法可能会抛出 ClassNotFoundException 异常,这个异常发生在类的加载阶段,原因如下:

  1. 类加载器在类路径中没有找到该类(检查:查看所在加载的类以及其所依赖的包是否在类路径下)
  2. 该类已经被某个类加载器加载到 JVM 内存中,另外一个类加载器又尝试从同一个包中加载

Object.getClass() 方法

如果我们有一个类的对象,那么我们可以通过 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 语法获取该类类型的对象。

 // 类
Class clazz = Integer.class;
// 数组
Class clazz2 = int [][].class;  

包装类的 TYPE 静态属性

对于基本类型和 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 中有获取其他 Class 的方法,列举如下:

  1. Class.getSuperclass():获取该类的父类
  2. Class.getClasses() :获取该类所有公共类、接口、枚举组成的Class 数组,包括继承的
  3. Class.getDeclaredClasses():获取该类显式声明的所有类、接口、枚举组成的 Class 数组
  4. (Class/Field/Method/Constructor).getDeclaringClass():获取该类/属性/方法/构造函数所在的类

Member & AccessibleObject

在讲 Field、Method、Constructor 之前,先说说 Member 和 AccessibleObject。Member 是一个接口,表示 Class 的成员,前面的三个类都是其实现类。

AccessibleObject 是 Field、Method、Constructor 三个类共同继承的父类,它提供了将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力。通过 setAccessible 方法可以忽略访问级别,从而访问对应的内容。并且 AccessibleObject 实现了 AnnotatedElement 接口,提供了与获取注解相关的能力。

Field

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

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

Constructor 提供了有关类的构造方法的信息,以及对它的动态访问的能力。

可以通过 Class 提供的方法,获取 Constructor 对象,具体如下:

方法返回值

方法名称

方法说明

Constructor<T>

getConstructor(Class<?>… parameterTypes)

返回指定参数类型、具有public访问权限的构造函数对象

Constructor<?>[]

getConstructors()

返回所有具有public访问权限的构造函数的Constructor对象数组

Constructor<T>

getDeclaredConstructor(Class<?>… parameterTypes)

返回指定参数类型、所有声明的(包括private)构造函数对象

Constructor<?>[]

getDeclaredConstructor()

返回所有声明的(包括private)构造函数对象

Constructor 相关的 API 我就不全局列举出来了,可以点击这里查看。

Array

在 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 框架都会使用到反射,例如动态配置:读取写好的配置文件的值,然后通过反射机制将这些值设置到配置类中。总的来说应用场景有如下几点:

  1. 框架开发中使用,例如动态配置
  2. 插件开发中使用,例如持续集成
  3. 应用扩展

当然反射也有一些缺点:

  1. 性能低
  2. 可读性差
  3. 只能在运行期间报错

文章来源:智云一二三科技

文章标题:Java 反射详解

文章地址:https://www.zhihuclub.com/185126.shtml

关于作者: 智云科技

热门文章

网站地图