前提

Java反射的API在JavaSE1.7的时候已经基本完善,但是本文编写的时候使用的是Oracle JDK11,因为JDK11对于sun包下的源码也上传了,可以直接通过IDE查看对应的源码和进行Debug。

本文主要介绍一个使用反射一定会遇到的问题-反射调用异常处理。

反射调用异常处理

反射调用出现异常的方法主要考虑下面的情况:

  • 属性操作:java.lang.reflect.Field#set(Object obj, Object value)java.lang.reflect.Field#get(Object obj)
  • 构造器调用:java.lang.reflect.Constructor#newInstance(Object ... initargs)
  • 方法调用:java.lang.reflect.Method#invoke(Object obj, Object... args)

处理属性操作异常

先看设置属性的方法:

public void set(Object obj, Object value) throws IllegalArgumentException, IllegalAccessException

实际上,通过方法注释可以得知会抛出四种异常:

  • IllegalAccessException:非法访问异常,注意它是检查(checked)异常,也就是需要显示捕获,此异常会在修饰符禁用访问的时候抛出,可以通过setAccessible(true)抑制修饰符检查来避免抛出此异常。
  • IllegalArgumentException:非法参数异常,它是运行时异常,当入参实例obj不是当前Field所在类(包括父类、子类和接口)的时候会抛出此异常。
  • NullPointerException:空指针异常,当入参实例obj为null的时候会抛出此异常。
  • ExceptionInInitializerError:初始化器调用异常导致的错误,如果由于set(Object obj, Object value)方法引发的初始化失败会包装成ExceptionInInitializerError,此异常的父类为Error,常见的发生情况就是静态成员或者静态代码块依赖到反射属性设置。

前面三种异常都很好理解,最后一个ExceptionInInitializerError可能有点陌生,它的抛出条件是:在静态代码块初始化解析过程总抛出异常或者静态变量初始化的时候抛出异常。笔者尝试了很多例子都没办法造出案例,从Stackoverflow找到一个例子:

public class Example {
public static void main(String[] args) throws Exception {
Field field = Fail.class.getDeclaredField("number");
field.set(null, 42); // Fail class isn't initialized at this point
}
} class Fail {
static int number;
static {
boolean val = true;
if (val)
throw new RuntimeException(); // causes initialization to end with an exception
}
}

简单来说就是:静态代码块和静态变量的初始化顺序和它们在类文件编写的顺序是一致的,如果一个类未初始化直接使用它的静态代码块和静态变量通过Field#set(Object obj, Object value)调用就会出现ExceptionInInitializerError异常。

属性的获取方法抛出的异常和设置值方法是一致的,这里不做详细展开:

public Object get(Object obj) throws IllegalArgumentException, IllegalAccessException

处理构造器调用异常

构造器调用主要是用于对象的实例化,先看newInstance方法的签名:

public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException
  • InstantiationException:实例化异常,抛出此异常的一般情况是:当前构造所在类型为一个抽象类型。
  • IllegalAccessException:非法访问异常。
  • IllegalArgumentException:非法参数异常,下面的情况会抛出此异常:参数数量或者类型不匹配,参数列表为原始类型但是实际使用了包装类型、参数列表为原始类型但是实际使用了包装类型、构造所在的类是枚举类型等。
  • InvocationTargetException:目标调用异常,这个是需要处理的重点异常,在下一节"处理方法调用异常"详细探讨。

这里只举个例子说明一下InstantiationException出现的场景:

public abstract class AbstractSample {

	public AbstractSample() {
} public static void main(String[] args) throws Exception{
Constructor<AbstractSample> declaredConstructor = AbstractSample.class.getDeclaredConstructor();
declaredConstructor.newInstance();
}
}

像上面的抽象类AbstractSample包含一个默认的公有构造,使用Constructor#newInstance()会抛出InstantiationException异常:

Exception in thread "main" java.lang.InstantiationException
at java.base/jdk.internal.reflect.InstantiationExceptionConstructorAccessorImpl.newInstance(InstantiationExceptionConstructorAccessorImpl.java:48)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
at club.throwable.jdk.sample.reflection.reflect.AbstractSample.main(AbstractSample.java:18)

处理方法调用异常

方法调用是反射中使用频率最高的反射操作,主要是Method#invoke(Object obj, Object... args)方法:

public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException

主要包括以下几种异常:

  • IllegalAccessException:非法访问异常。
  • IllegalArgumentException:非法参数异常,下面的情况会抛出此异常:入参obj并不是当前实例方法对应的实例对象、参数数量或者类型不匹配,参数列表为原始类型但是实际使用了包装类型、参数列表为原始类型但是实际使用了包装类型等等。
  • NullPointerException:空指针异常,入参obj为null时候会抛出此异常。
  • ExceptionInInitializerError:初始化器调用异常导致的错误。
  • InvocationTargetException:目标调用异常。

重点看InvocationTargetException(继承自ReflectiveOperationException,而ReflectiveOperationException继承自Exception,也就是它是checked异常,必须显式捕获):

public class InvocationTargetException extends ReflectiveOperationException {

    private static final long serialVersionUID = 4085088731926701167L;

    // 持有的目标异常实例
private Throwable target; public InvocationTargetException(Throwable target) {
super((Throwable)null); // Disallow initCause
this.target = target;
} public InvocationTargetException(Throwable target) {
super((Throwable)null); // Disallow initCause
this.target = target;
} public Throwable getTargetException() {
return target;
} public Throwable getCause() {
return target;
}
}

从注释中得知:方法(Method)或者构造(Constructor)调用异常会抛出此InvocationTargetException异常,用于包装源异常,源异常实例作为目标被InvocationTargetException通过成员target持有,可以通过InvocationTargetException#getTargetException()或者InvocationTargetException#getCause()获取原始的目标异常。这里注意到,InvocationTargetException在覆盖父类构造的时候使用了null,所以调用其getMessage()方法会得到null。

举个例子:

public class InvocationTargetExceptionMain {

	public void method() {
throw new NullPointerException("Null");
} public static void main(String[] args) throws NoSuchMethodException, SecurityException {
InvocationTargetExceptionMain main = new InvocationTargetExceptionMain();
Method method = InvocationTargetExceptionMain.class.getDeclaredMethod("method");
try {
method.invoke(main);
} catch (IllegalAccessException e) {
//no-op
} catch (InvocationTargetException e) {
System.out.println("InvocationTargetException#message:" + e.getMessage());
if (e.getTargetException() instanceof NullPointerException) {
NullPointerException nullPointerException = (NullPointerException) e.getTargetException();
System.out.println("NullPointerException#message:" + nullPointerException.getMessage());
}
}
}
}

运行后输出:

InvocationTargetException#message:null
NullPointerException#message:Null

构造器Constructor#newInstance()中抛出InvocationTargetException的场景是类似的。

小结

在反射操作中,方法调用的频次是最高的,其次是通过构造器实例化对象。需要重点关注这两个地方的异常处理,特别是异常类型InvocationTargetException,紧记需要获取原始目标异常类型再进行判断,否则很容易导致逻辑错误(最近笔者在做一个功能的时候刚好踩了这个坑)。

个人博客

(本文完 e-a-20181215 c-2-d)

深入分析Java反射(六)-反射调用异常处理的更多相关文章

  1. 深入分析Java反射(五)-类实例化和类加载

    前提 其实在前面写过的<深入分析Java反射(一)-核心类库和方法>已经介绍过通过类名或者java.lang.Class实例去实例化一个对象,在<浅析Java中的资源加载>中也 ...

  2. java反射 之 反射基础

    一.反射 反射:JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为 ...

  3. 黑马程序员——【Java高新技术】——反射机制

    ---------- android培训.java培训.期待与您交流! ---------- 一.概述 1.Java反射机制:是指“在运行状态中”,对于任意一个类,都能够知道这个类中的所有属性和方法: ...

  4. 【Java】【反射】

    一,java的核心机制 java有两种核心机制:java虚拟机(JavaVirtual Machine)与垃圾收集机制(Garbage collection): Java虚拟机:是运行所有Java程序 ...

  5. java基础篇---反射机制

    一.JAVA是动态语言吗? 一般而言,说到动态言,都是指在程序运行时允许改变程序结构或者变量类型,从这个观点看,JAVA和C++一样,都不是动态语言. 但JAVA它却有着一个非常突出的动态相关机制:反 ...

  6. Java中的反射机制和动态代理

    一.反射概述 反射机制指的是Java在运行时候有一种自观的能力,能够了解自身的情况为下一步做准备,其想表达的意思就是:在运行状态中,对于任意一个类,都能够获取到这个类的所有属性和方法:对于任意一个对象 ...

  7. java反射(四)--反射与简单java类

    一.传统简单java类 简单的java类主要是由属性所组成,并且提供有相应的setter以及getter的处理方法,同时简单java类最大的特征就是通过对象保存相应的类的属性内容,但是如果使用传统的简 ...

  8. java进阶(41)--反射机制

    文档目录: 一.反射机制的作用 二.反射机制相关类 三.获取class的三种方式 四.通过反射实例化对象 五.通过读属性文件实例化对象 六.通过反射机制访问对象属性 七.通过反射机制调用方法 ---- ...

  9. java基础学习:java中的反射

    一.什么是java反射 什么是 java 的反射? 说到反射,写这篇文章时,我突然想到了人的"反省",反省是什么?吾一日三省吾身,一般就是反思自身,今天做了哪些对或错的事情. ja ...

随机推荐

  1. 14.swoole学习笔记--异步读取文件

    <?php //异步读取文件 swoole_async_readfile(__DIR__."/1.txt",function($filename,$content){ ech ...

  2. while循环和do-while循环语句

    while 语句 条件表达式的结果是一个 boolean 值,如果为true,则执行循环体:如果为 false,循环就会结束. while 循环体是一个代码块,所以 while 循环是可以嵌套其他的语 ...

  3. 048-PHP定义常量

    <?php define('NUM',123); //定义常量NUM echo NUM; //输出NUM的值 define('STR','ABC',TRUE); //定义常量STR并设置大小写不 ...

  4. benchmark与gem5-gpu交互

    gem5-gpu作为一个异构多核系统的模拟器,当我们使用异构融合多核处理器架构(特别是支持HSA的处理器架构)运行GPU与CPU的benchmark时,研究自己设计的算法或添加的硬件对GPU与CPU存 ...

  5. 【CF1154G】Minimum Possible LCM

    题意 给你 \(n\) 个数 \(a_i\) ,求出 \(\text{lcm}\) 最小的一对数. \(n\le 10^6, a_i\le 10^7\) 题解 直接枚举 ,找到当前数最小的两个倍数,统 ...

  6. 干货分享|留学Essay怎么写?

    留学生活其实就是分割成一个个deadline,留学就是赶完一个又一个deadline.朋友同学的革命情感源自赶一个个deadline时候的不离不弃,相知相守,无数个夜里大家群里打卡,你今天Essay写 ...

  7. Android Studio模拟器无法访问网络

    Android Studio3.5 模拟器无法访问网络的原因?

  8. Docker NGINX 例子

    版权所有,未经许可,禁止转载 章节 Docker 介绍 Docker 和虚拟机的区别 Docker 安装 Docker Hub Docker 镜像(image) Docker 容器(container ...

  9. kafka源码系列之mysql数据增量同步到kafka

    一,架构介绍 生产中由于历史原因web后端,mysql集群,kafka集群(或者其它消息队列)会存在一下三种结构. 1,数据先入mysql集群,再入kafka 数据入mysql集群是不可更改的,如何再 ...

  10. 哈希(hash)理解

    转载自https://www.cnblogs.com/mingaixin/p/4318837.html 一.什么是哈希?(一种更复杂的映射) Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就 ...