前提

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. HTML的文档结构与语法(一)

    一.走进Web开发 Web运行的原理: 二.HTML 1.1什么是html HTML是用来描述网页的一种语言 HTML指的是超文本标记语言(Hyper Text Markup Language) 超文 ...

  2. 033-PHP对一个数组先奇后偶,然后再进行从大到小排序

    <?php function Compare($str1, $str2) { if (($str1 % 2 == 0) && ($str2 %2 == 0)) { if ($st ...

  3. 【转】ASP.NET Core MVC/WebAPi 模型绑定探索

    前言 相信一直关注我的园友都知道,我写的博文都没有特别枯燥理论性的东西,主要是当每开启一门新的技术之旅时,刚开始就直接去看底层实现原理,第一会感觉索然无味,第二也不明白到底为何要这样做,所以只有当你用 ...

  4. javaBean命名属性时的小注意点

    javabean属性命名的时,第一个和第二个字母最好不要是大写字母,不然使用eclipse自动生成getter和setter方法时,会出现奇怪的问题,导致struts2封装属性的封装不上. priva ...

  5. 算法实战(六)Z 字形变换

    一.前言 之前因为第五题最长回文字符串需要使用到dp解法,所以我花了很长的时间来研究dp(因为每天又要上班,加上这段时间事情比较多,所以花了三个星期才搞定),好不容易算入了个门,有兴趣的同学可以看看我 ...

  6. P1036 跟奥巴马一起编程

    转跳点:

  7. Java 第一次课堂测试总结。

    Java 第一次课堂测试总结. 昨天参加了JAVA的开学测试,课上没有完成计算基点的功能,以下是修改完成后的代码.  首先是ScoreInformation类来存储学生信息. //信1805-1 王正 ...

  8. C语言预处理理论-宏定义2

    宏定义21.带参宏和带参函数的区别(1)宏定义是在预处理期间处理的,而函数是在编译期间处理的.这个区别带来的实质差异是:宏定义最终是在调用宏的地方把宏体原地展开,而函数是在调用函数处跳转到函数中去执行 ...

  9. VM15上安装macOS操作系统

    (该篇博客已经成功安装上Xcode,放心下载) 因为要开学了,需要学习mac操作系统,自己没有苹果电脑只能虚拟机上下载喽 我在电脑上安装的VM15虚拟机,不会安装的可以来这里下载软件VM15虚拟机   ...

  10. Codeforces 437D 贪心+并查集

    这个题目让我想起了上次在湘潭赛的那道跪死了的题.也是最值问题,这个也是,有n个动物园 每个都有权值 然后被m条路径相连接,保证图是连通的,然后求所有的p[i][j]之和.i,j为任意两个zoo,pij ...