写单元测试要给一个私有方法打桩,不能改源码又不能直接调,同事让我试试反射。取到方法对象后,关键一步就是调用它,用对了能把私有逻辑拉出来单测,用错了就是一堆异常看不明白。
它到底做了什么事
可以理解成用名字和参数类型在运行时找到某个方法,再让某个对象执行它。第一个参数是执行的目标对象,静态方法传空即可,后面按顺序补实参。它是运行时动态调用,绕过了编译期的直接绑定,所以灵活但也容易出错,参数类型对不上直接抛异常。
调用前必须做准备
先用类对象按方法名和参数类型拿到方法对象,参数类型数组要写准确,比如整数和长整型不能混。私有方法和受保护方法默认不可访问,要先设置可访问为真,高版本还能用更细的方式打开单次访问。方法名不存在会抛无此方法异常,参数类型不匹配会抛非法参数异常,看清异常名字基本能定位。
返回值和异常都有讲究
调用结果统一是对象类型,基本类型会被装箱,取出来要按声明类型强转,别写成包装类又当基本类型用。方法本身抛出的异常会被包一层,取真实原因要用原因方法,不然只看外层信息会误判。返回值是空类型时结果是空,别强转成数字,会报空指针。
什么时候该用、什么时候避开
框架、注解处理、序列化工具离不了它,插件化按配置调不同实现也常用。但业务代码里滥用会让调用关系藏起来,翻代码找半天,编译期检查也失效。能用接口多态就别反射,只有处理私有逻辑做测试、或按名字分发这种场景才值得。缓存方法对象能省一些查找开销,海量调用时差别明显。
- 三件套:拿方法、开可访问、按类型传参
- 异常处理:捕获时用原因方法取真实异常,别只打印外层
- 取舍原则:业务代码优先多态,反射留给框架与测试
类对象.getDeclaredMethod(名字, 参数类型...)方法对象.setAccessible(true)方法对象.invoke(目标对象, 实参...)异常.getCause()取真实原因