Java反射入门、原理与使用方法详解

反射之中包含了一个「反」字,所以想要解释反射就必须先从「正」开始解释。

一般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。

Apple apple = new Apple(); //直接初始化,「正射」
apple.setPrice(4);

上面这样子进行类对象的初始化,我们可以理解为「正」。

而反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。

这时候,我们使用 JDK 提供的反射 API 进行反射调用:

Class clz = Class.forName("com.chenshuyi.reflect.Apple");
Method method = clz.getMethod("setPrice", int.class);
Constructor constructor = clz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, 4);

上面两段代码的执行结果,其实是完全一样的。但是其思路完全不一样,第一段代码在未运行时就已经确定了要运行的类(Apple),而第二段代码则是在运行时通过字符串值才得知要运行的类(com.chenshuyi.reflect.Apple)。

所以说什么是反射?

反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。

一个简单的例子

上面提到的示例程序,其完整的程序代码如下:

public class Apple {

  private int price;

  public int getPrice() {
    return price;
  }

  public void setPrice(int price) {
    this.price = price;
  }

  public static void main(String[] args) throws Exception{
    //正常的调用
    Apple apple = new Apple();
    apple.setPrice(5);
    System.out.println("Apple Price:" + apple.getPrice());
    //使用反射调用
    Class clz = Class.forName("com.chenshuyi.api.Apple");
    Method setPriceMethod = clz.getMethod("setPrice", int.class);
    Constructor appleConstructor = clz.getConstructor();
    Object appleObj = appleConstructor.newInstance();
    setPriceMethod.invoke(appleObj, 14);
    Method getPriceMethod = clz.getMethod("getPrice");
    System.out.println("Apple Price:" + getPriceMethod.invoke(appleObj));
  }
}

从代码中可以看到我们使用反射调用了 setPrice 方法,并传递了 14 的值。之后使用反射调用了 getPrice 方法,输出其价格。上面的代码整个的输出结果是:

Apple Price:5
Apple Price:14

从这个简单的例子可以看出,一般情况下我们使用反射获取一个对象的步骤:

  • 获取类的 Class 对象实例
Class clz = Class.forName("com.zhenai.api.Apple");
  • 根据 Class 对象实例获取 Constructor 对象
Constructor appleConstructor = clz.getConstructor();
  • 使用 Constructor 对象的 newInstance 方法获取反射类对象
Object appleObj = appleConstructor.newInstance();

而如果要调用某一个方法,则需要经过下面的步骤:

  • 获取方法的 Method 对象
Method setPriceMethod = clz.getMethod("setPrice", int.class);
  • 利用 invoke 方法调用方法
setPriceMethod.invoke(appleObj, 14);

到这里,我们已经能够掌握反射的基本使用。但如果要进一步掌握反射,还需要对反射的常用 API 有更深入的理解。

在 JDK 中,反射相关的 API 可以分为下面几个方面:获取反射的 Class 对象、通过反射创建类对象、通过反射获取类属性方法及构造器。

反射常用API

获取反射中的Class对象

在反射中,要获取一个类或调用一个类的方法,我们首先需要获取到该类的 Class 对象。

在 Java API 中,获取 Class 类对象有三种方法:

第一种,使用 Class.forName 静态方法。当你知道该类的全路径名时,你可以使用该方法获取 Class 类对象。

Class clz = Class.forName("java.lang.String");

第二种,使用 .class 方法。

这种方法只适合在编译前就知道操作的 Class。

Class clz = String.class;

第三种,使用类对象的 getClass() 方法。

String str = new String("Hello");
Class clz = str.getClass();

通过反射创建类对象

通过反射创建类对象主要有两种方式:通过 Class 对象的 newInstance() 方法、通过 Constructor 对象的 newInstance() 方法。

第一种:通过 Class 对象的 newInstance() 方法。

Class clz = Apple.class;
Apple apple = (Apple)clz.newInstance();

第二种:通过 Constructor 对象的 newInstance() 方法

Class clz = Apple.class;
Constructor constructor = clz.getConstructor();
Apple apple = (Apple)constructor.newInstance();

通过 Constructor 对象创建类对象可以选择特定构造方法,而通过 Class 对象则只能使用默认的无参数构造方法。下面的代码就调用了一个有参数的构造方法进行了类对象的初始化。

Class clz = Apple.class;
Constructor constructor = clz.getConstructor(String.class, int.class);
Apple apple = (Apple)constructor.newInstance("红富士", 15);

通过反射获取类属性、方法、构造器

我们通过 Class 对象的 getFields() 方法可以获取 Class 类的属性,但无法获取私有属性。

Class clz = Apple.class;
Field[] fields = clz.getFields();
for (Field field : fields) {
  System.out.println(field.getName());
}

输出结果是:

price

而如果使用 Class 对象的 getDeclaredFields() 方法则可以获取包括私有属性在内的所有属性:

Class clz = Apple.class;
Field[] fields = clz.getDeclaredFields();
for (Field field : fields) {
  System.out.println(field.getName());
}

输出结果是:

name
price

与获取类属性一样,当我们去获取类方法、类构造器时,如果要获取私有方法或私有构造器,则必须使用有 declared 关键字的方法。

反射源码解析

当我们懂得了如何使用反射后,今天我们就来看看 JDK 源码中是如何实现反射的。或许大家平时没有使用过反射,但是在开发 Web 项目的时候会遇到过下面的异常:

java.lang.NullPointerException
...
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:497)

可以看到异常堆栈指出了异常在 Method 的第 497 的 invoke 方法中,其实这里指的 invoke 方法就是我们反射调用方法中的 invoke。

Method method = clz.getMethod("setPrice", int.class);
method.invoke(object, 4);  //就是这里的invoke方法

例如我们经常使用的 Spring 配置中,经常会有相关 Bean 的配置:

<bean class="com.chenshuyi.Apple">
</bean>

当我们在 XML 文件中配置了上面这段配置之后,Spring 便会在启动的时候利用反射去加载对应的 Apple 类。而当 Apple 类不存在或发生启发异常时,异常堆栈便会将异常指向调用的 invoke 方法。

从这里可以看出,我们平常很多框架都使用了反射,而反射中最最终的就是 Method 类的 invoke 方法了。

下面我们来看看 JDK 的 invoke 方法到底做了些什么。

进入 Method 的 invoke 方法我们可以看到,一开始是进行了一些权限的检查,最后是调用了 MethodAccessor 类的 invoke 方法进行进一步处理,如下图红色方框所示。

那么 MethodAccessor 又是什么呢?

其实 MethodAccessor 是一个接口,定义了方法调用的具体操作,而它有三个具体的实现类:

  • sun.reflect.DelegatingMethodAccessorImpl
  • sun.reflect.MethodAccessorImpl
  • sun.reflect.NativeMethodAccessorImpl

而要看 ma.invoke() 到底调用的是哪个类的 invoke 方法,则需要看看 MethodAccessor 对象返回的到底是哪个类对象,所以我们需要进入 acquireMethodAccessor() 方法中看看。

从 acquireMethodAccessor() 方法我们可以看到,代码先判断是否存在对应的 MethodAccessor 对象,如果存在那么就复用之前的 MethodAccessor 对象,否则调用 ReflectionFactory 对象的 newMethodAccessor 方法生成一个 MethodAccessor 对象。

在 ReflectionFactory 类的 newMethodAccessor 方法里,我们可以看到首先是生成了一个 NativeMethodAccessorImpl 对象,再这个对象作为参数调用 DelegatingMethodAccessorImpl 类的构造方法。

这里的实现是使用了代理模式,将 NativeMethodAccessorImpl 对象交给 DelegatingMethodAccessorImpl 对象代理。我们查看 DelegatingMethodAccessorImpl 类的构造方法可以知道,其实是将 NativeMethodAccessorImpl 对象赋值给 DelegatingMethodAccessorImpl 类的 delegate 属性。

所以说ReflectionFactory 类的 newMethodAccessor 方法最终返回 DelegatingMethodAccessorImpl 类对象。所以我们在前面的 ma.invoke() 里,其将会进入 DelegatingMethodAccessorImpl 类的 invoke 方法中。

进入 DelegatingMethodAccessorImpl 类的 invoke 方法后,这里调用了 delegate 属性的 invoke 方法,它又有两个实现类,分别是:DelegatingMethodAccessorImpl 和 NativeMethodAccessorImpl。按照我们前面说到的,这里的 delegate 其实是一个 NativeMethodAccessorImpl 对象,所以这里会进入 NativeMethodAccessorImpl 的 invoke 方法。

而在 NativeMethodAccessorImpl 的 invoke 方法里,其会判断调用次数是否超过阀值(numInvocations)。如果超过该阀值,那么就会生成另一个MethodAccessor 对象,并将原来 DelegatingMethodAccessorImpl 对象中的 delegate 属性指向最新的 MethodAccessor 对象。

到这里,其实我们可以知道 MethodAccessor 对象其实就是具体去生成反射类的入口。通过查看源码上的注释,我们可以了解到 MethodAccessor 对象的一些设计信息。

"Inflation" mechanism. Loading bytecodes to implement Method.invoke() and Constructor.newInstance() currently costs 3-4x more than an invocation via native code for the first invocation (though subsequent invocations have been benchmarked to be over 20x faster).Unfortunately this cost increases startup time for certain applications that use reflection intensively (but only once per class) to bootstrap themselves.

Inflation 机制。初次加载字节码实现反射,使用 Method.invoke() 和 Constructor.newInstance() 加载花费的时间是使用原生代码加载花费时间的 3 - 4 倍。这使得那些频繁使用反射的应用需要花费更长的启动时间。

To avoid this penalty we reuse the existing JVM entry points for the first few invocations of Methods and Constructors and then switch to the bytecode-based implementations. Package-private to be accessible to NativeMethodAccessorImpl and NativeConstructorAccessorImpl.

为了避免这种痛苦的加载时间,我们在第一次加载的时候重用了 JVM 的入口,之后切换到字节码实现的实现。

就像注释里说的,实际的 MethodAccessor 实现有两个版本,一个是 Native 版本,一个是 Java 版本。

Native 版本一开始启动快,但是随着运行时间边长,速度变慢。Java 版本一开始加载慢,但是随着运行时间边长,速度变快。正是因为两种存在这些问题,所以第一次加载的时候我们会发现使用的是 NativeMethodAccessorImpl 的实现,而当反射调用次数超过 15 次之后,则使用 MethodAccessorGenerator 生成的 MethodAccessorImpl 对象去实现反射。

Method 类的 invoke 方法整个流程可以表示成如下的时序图:

讲到这里,我们了解了 Method 类的 invoke 方法的具体实现方式。知道了原来 invoke 方法内部有两种实现方式,一种是 native 原生的实现方式,一种是 Java 实现方式,这两种各有千秋。而为了最大化性能优势,JDK 源码使用了代理的设计模式去实现最大化性能。

(0)

相关推荐

  • java反射应用详细介绍

    本篇文章依旧采用小例子来说明,因为我始终觉的,案例驱动是最好的,要不然只看理论的话,看了也不懂,不过建议大家在看完文章之后,在回过头去看看理论,会有更好的理解. 下面开始正文. [案例1]通过一个对象获得完整的包名和类名 复制代码 代码如下: package Reflect; /** * 通过一个对象获得完整的包名和类名 * */ class Demo{ //other codes... } class hello{ public static void main(String[] args)

  • java反射机制示例详解

    1.什么是反射?一个类有多个组成部分,例如:成员变量,方法,构造方法等.反射就是加载类,并解剖出类的各个组成部分. 2.加载类java中有一个Class类用于代表某一个类的字节码.Class类既然代表某个类的字节码,那就要提供加载某个类字节码的方法:forName().   此方法用于加载某个类的字节码到内存中,并使用class对象进行封装.另外2种得到class对象的方式:类名.class对象.getClass() 先创建一个简单的Person类 复制代码 代码如下: public class

  • Java反射机制的实现详解

    很多主流框架都使用了反射技术.像ssh框架都采用两种技术 xml做配置文件+反射技术. 与反射有关的类包. java.lang.reflect.*;和java.lang.Class; Java中所有类型(包括基本类型)都对应一个Class对象,这个Class就是java.lang.Class.即每一个类型,在Class中都有一个Class对象跟它对应.Class 没有公共构造方法.注意不是没有,是没有公共的. 如何获得Class对象 复制代码 代码如下: .针对每一个对象.getCalss(),

  • Java反射机制的学习总结

    一.什么是反射机制 简单的来说,反射机制指的是程序在运行时能够获取自身的信息.在java中,只要给定类的名字,那么就可以通过反射机制来获得类的所有信息. 二.哪里用到反射机制 有些时候,我们用过一些知识,但是并不知道它的专业术语是什么,在刚刚学jdbc时用过一行代码, Class.forName("com.mysql.jdbc.Driver.class").newInstance();但是那时候只知道那行代码是生成驱动对象实例,并不知道它的具体含义.听了反射机制这节课后,才知道,原来这

  • java反射获取和调用方法

    Class类中获取方法: public Method[] getMethods();//获取包括自身和继承(实现)过来的所有的public方法--Method不支持泛型<>,即后面不接<> public Method[] getDeclaredMethods();//获取自身所有的方法(private.public.protected,和访问权限无关),不包括继承的 在jdk1.8后可以直接获取私有属性的方法不需要设置权限 但是仅限于getDeclaredMethod方法 对于Me

  • Java反射机制详解

    本文较为详细的分析了Java反射机制.分享给大家供大家参考,具体如下: 一.预先需要掌握的知识(java虚拟机) java虚拟机的方法区: java虚拟机有一个运行时数据区,这个数据区又被分为方法区,堆区和栈区,我们这里需要了解的主要是方法区.方法区的主要作用是存储被装载的类 的类型信息,当java虚拟机装载某个类型的时候,需要类装载器定位相应的class文件,然后将其读入到java虚拟机中,紧接着虚拟机提取class 中的类型信息,将这些信息存储到方法区中.这些信息主要包括: 1.这个类型的全

  • java中如何反射获取一个类

    反射说白了就是可以获得一个类的所有信息,主要包括方法和属性两部分. 1.获得方法包括获得方法的名称,方法的返回类型,方法的访问修饰符,以及通过反射执行这个方法. 2.获得属性包括属性的名称,类型,访问修饰符,以及这个属性的值. 这些获得都有相应的API提供操作. 代码如下: package poi; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Meth

  • JAVA反射机制实例教程

    本文以实例形式详细讲述了Java的反射机制,是Java程序设计中重要的技巧.分享给大家供大家参考.具体分析如下: 首先,Reflection是Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说"自审",并能直接操作程序的内部属性.例如,使用它能获得 Java 类中各成员的名称并显示出来. Java 的这一能力在实际应用中也许用得不是很多,但是在其它的程序设计语言中根本就不存在这一特性.例如,Pascal.C 或者 C++ 中就没有办法在程序中获得函数

  • Java反射概念与使用实例代码

    目录 前言 反射 基础数据准备 基于反射创建对象 获取反射中的对象 获取类中属性 获取类中的构造方法 获取类中方法 结语 前言 大家好,瑞雪后的第一天,每个周一的大家都期待这周五的来临.相信很多小伙伴上周末就两件事,赏雪和看EDG.哈哈 开始正题吧,今天聊一聊反射吧,在java中经常使用. 反射 相信刚接触Java的,第一句肯定会问什么是反射呢?反射有什么作用呢?为什么使用反射呢?首先反射是Java的特征之一,项目中Java程序在运行的过程中,自动去识别并创建对应的类,能够动态的调用类的属性.构

  • java的继承原理与实现方法详解

    本文实例讲述了java的继承原理与实现方法.分享给大家供大家参考,具体如下: 继承 1.java中是单继承的.每个子类只有一个父类. 语法:子类 extends 父类 2.在java中,即使没有声明父类,也有一个隐含的父类,就是Object类 3.在子类中可以使用super来调用父类的方法 4.继承中的构造方法问题 在new一个子类实例的过程中,会优先自动调用父类默认的无参数构造方法,然后再调用子类的构造方法.如果父类没有默认的构造方法,只有带参数的构造方法,此时就会出错. 除了由jvm自动调用

  • Java笛卡尔积算法原理与实现方法详解

    本文实例讲述了Java笛卡尔积算法原理与实现方法.分享给大家供大家参考,具体如下: 笛卡尔积算法的Java实现: (1)循环内,每次只有一列向下移一个单元格,就是CounterIndex指向的那列. (2)如果该列到尾部了,则这列index重置为0,而CounterIndex则指向前一列,相当于进位,把前列的index加一. (3)最后,由生成的行数来控制退出循环. public class Test { private static String[] aa = { "aa1", &q

  • ThreadPoolExecutor线程池原理及其execute方法(详解)

    jdk1.7.0_79 对于线程池大部分人可能会用,也知道为什么用.无非就是任务需要异步执行,再者就是线程需要统一管理起来.对于从线程池中获取线程,大部分人可能只知道,我现在需要一个线程来执行一个任务,那我就把任务丢到线程池里,线程池里有空闲的线程就执行,没有空闲的线程就等待.实际上对于线程池的执行原理远远不止这么简单. 在Java并发包中提供了线程池类--ThreadPoolExecutor,实际上更多的我们可能用到的是Executors工厂类为我们提供的线程池:newFixedThreadP

  • Java的JSON处理器fastjson使用方法详解

    fastjson 是一个性能很好的 Java 语言实现的 JSON 解析器和生成器,来自阿里巴巴的工程师开发. 主要特点: • 快速FAST (比其它任何基于Java的解析器和生成器更快,包括jackson) • 强大(支持普通JDK类包括任意Java Bean Class.Collection.Map.Date或enum) • 零依赖(没有依赖其它任何类库除了JDK) 示例代码: import com.alibaba.fastjson.JSON; Group group = new Group

  • Java和C#输入输出流的方法(详解)

    1,Java中操作方法: import java.io.*; public class FileInputStreamTest { public static void main(String[] args) throws IOException { //创建字节输入流 FileInputStream fis = new FileInputStream("FileInputStreamTest.java"); //创建一个长度为1024的竹筒 byte[] bbuf = new byt

  • Java实现优雅的参数校验方法详解

    目录 一.引子 二.如何优雅地校验参数 2.1 官方指导意见 2.2 注解用法说明 一.引子 要对方法的参数进行校验,最简单暴力的写法是这个样子: public static void utilA(String a,BigDecimal b){ if (StringUtils.isEmpty(a)){ System.out.println("a不可为空"); return; } if (b == null){ System.out.println("b不可为空");

  • Java实现简易拼图游戏的方法详解

    目录 效果展示 游戏结构 实现代码 效果展示 介绍:游戏共有五张图片可以选择,分成了4 X 4 十六个方格,点击开始就可以开始游戏.游戏运行的截图如下: 游戏结构 实现代码 代码如下:MedleyGame.java类 package game.medleyPicture; import java.awt.BorderLayout; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.even

  • Java实现读取Jar文件属性的方法详解

    目录 一.题目描述-读取Jar文件属性 1.题目 2.解题思路 3.代码详解 一.题目描述-读取Jar文件属性 1.题目 做一个读取jar文件的内容和功能的工具. 2.解题思路 创建一个类:ReaderJarFrame 使用ReaderJarFrame继承JFrame构建窗体 在选择jar后,窗体的表格会显示其内容. 创建一个类:ReadJar ReadJar类实现读取功能 读取的逻辑:使用JarFile类与Enumeration接口. jarFile是Java提供的一个类,可以读取jar包中的

  • Java中Optional类及orElse方法详解

    目录 引言 Java 中的 Optional 类 ofNullable() 方法 orElse() 方法 案例 orElseGet() 方法 案例 orElse() 与 orElseGet() 之间的区别 引言 为了让我更快的熟悉代码,前段时间组长交代了一个小任务,大致就是让我整理一下某个模块中涉及的 sql,也是方便我有目的的看代码,也是以后方便他们查问题(因为这个模块,涉及的判断很多,所以之前如果 sql 出错了,查问题比较繁琐). 昨天算是基本完成了,然后今天组长就让给我看一个该模块的缺陷

  • Java实现统计在线人数功能的方法详解

    目录 1. 监听器的简介 2. Java监听器的类型 (1)ServletContextListener (2)HttpSessionListener (3)ServletRequestListener (4)ServletContextAttributeListener (5)HttpSessionAttributeListener (6)ServletRequestAttributeListener (7)HttpSessionActivationListener 3.监听器Listener

随机推荐