Java中如何优雅地调用多个方法
问题背景
有某个颇为复杂的功能,功能拆分时把该功能拆分成了数十个步骤,每个步骤用一个方法来实现。需要依次调用数十个方法/函数,这些方法有相同的签名。
为了后期的维护和扩展,显然不可能像这样去调用:
step1(); // 第一步step2(); // 第二步...stepn(); // 第n步
这样去调用的话,如果后期要在每个方法/函数后面都增加一个额外的功能(比如测量每个步骤的运行时间),那么工作量就翻了N倍,超出想象!
如果把这些方法直接或间接放入一个数组中,遍历这个数组,取出这些方法调用,代码就会变得非常简洁,也容易维护。如:
stepList = [step1, step, ..., stepn]; // 把这些步骤对应的方法都放入一个数组中for(int i=0; i<n; i++) { // 遍历stepList[i](); // 调用}
围绕这个思路,我们来看看在具体编程语言中如何实现。这里以Java为例。当然,如果用 C、Javascript、PHP,会更简单。
各个步骤的示例代码如下:
为测试,在一个类中编写了3个方法,来代表需要执行的多个步骤。
public class TestAction {/*** 步骤一*/public boolean step1() {System.out.println("step 1");return false;}/*** 步骤2*/public boolean step2() {System.out.println("step 2");return true;}/*** 步骤3*/public boolean step3() {System.out.println("step 3");return true;}
}
实现1. 反射 (Reflection)
把方法名字以字符串形式存放在一个数组,然后通过反射
(Reflection
)来获取到这个方法,再用invoke
调用该方法。
/**
* 方式1: 用反射批量调用方法 (该方法写在 TestAction 类中)
* @return*/
public boolean doAllByReflect() {Class claz = getClass();String stepList[] = {"step1", "step2", "step3" // 把方法名称放在数组里};boolean result = false;try {for(String methodName : stepList) {Method method =claz.getMethod(methodName, null); // 通过反射获取到该方法result = (boolean)method.invoke(this, null); // 调用}} catch (Exception e) { // 为缩减篇幅,具体异常的捕获和处理就不写了e.printStackTrace();}return result;
}
测试输出
step 1
step 2
step 3
评价
优点:
- 如果需要追加步骤或者调整步骤顺序,只需要更改存储方法名称的数组就行了,简单方便。
- 如果需要在每个方法调用前/后增加处理逻辑,在
for
循环里面处理一次就行,工作量也不大。
缺点:
1.上面纯粹使用反射,把方法名字以字符串形式放入数组,编辑器和编译器无法发现拼写错误,只有在运行时才能发现错误。所以编译器会提示需要捕获4种异常:
- NoSuchMethodException 没有该方法。属于拼写错误导致的
- IllegalArgumentException 参数非法错误。调用时传入了错误类型/数量的参数时导致的
- InvocationTargetException 调用的方法出现异常。
- IllegalAccessException 非法访问异常。如果在同一个类内调用,不存在该问题。
所以如果代码编写不注意,容易埋下隐患。
实现2. 反射 + 注解 (Refelction + Annotation)
比之于通过方法的字符串名称来获取方法实例,可以给需要调用的方法用注解
(Annotation
)打上标签,然后用反射获取所有方法(getDeclaredMethods()
)并遍历,判断是否有该注解(isAnnotationPresent()
方法),有就调用。
实现如下:
2.1 定义Action
注解
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface Action {int sort(); // sort用来给方法排序,让方法按指定顺序调用,这里不考虑
}
2.2 把需要调用的方法加上Action注解
public class TestAction {/*** 步骤一*/@Action(sort=1)public boolean step1() {System.out.println("step 1");return false;}/*** 步骤2*/@Action(sort=2)public boolean step2() {System.out.println("step 2");return true;}/*** 步骤3*/@Action(sort=3)public boolean step3() {System.out.println("step 3");return true;}
}
2.3 调用
/*** 方式2: 反射+注解 批量调用方法(该方法写在 TestAction 类中)* @return*/
public boolean doAllAnnotation() {Class claz = getClass();boolean result = false;try {Method[] methodList = claz.getDeclaredMethods();for(Method method : methodList) {// 如果该方法有Aaction注解,调用之if (method.isAnnotationPresent(Action.class)) {method.invoke(this, null);}}} catch (Exception e) {e.printStackTrace();result = false;}return result;
}
测试输出
step 1
step 2
step 3
评价
优点:
- 如果需要追加步骤,增加相应的方法即可。
- 如果需要调整步骤顺序,调整注解中
sort
的值,在调用之前对其排序。 - 如果需要在每个方法调用前/后增加处理逻辑,在
for
循环里面处理一次就行,工作量也不大。
缺点:
- 调整顺序时需要增加额外的排序步骤。
- 如果注解被误用到了不是该步骤的方法上,会导致运行时出现问题。如果注解被误用到了不是该步骤的方法上,会导致运行时出现问题。
实现3. 利用Java8新特性: 方法引用和函数式接口
Java8提供了一些新特性,可以像函数式编程语言一样把方法当做一等公民,这两个便是 方法引用
(Method Reference
)和函数式接口
(Functional Interface
)。
首先利用函数式接口
让每个方法都具有相同的类型(从而可以放入同一个数组),然后利用方法引用
获取到该方法的引用,最后用 for循环
依次调用每个方法。
实现如下:
3.1 定义一个接口 IAction
@FunctionalInterface
public interface IAction {boolean execute(); // 方法签名要与step系列方法的一样
}
3.2 调用
/*** 方式3: 方法引用 + 函数式接口 (该方法写在 TestAction类中)* @return*/
public boolean doAll() {IAction[] stepList = {this::step1, // step1()方法的引用this::step2,this::step3};boolean result = false;for(IAction action : stepList) {action.execute(); // 执行该方法}return result;
}
在Java8中,还可以用stream
系列方法来代替上面的for
循环:
Arrays.asList(stepList).stream().forEach(step-> step.execute());
测试输出
step 1
step 2
step 3
评价
优点:
- 如果需要追加步骤或调整步骤顺序,修改
stepList
这个数组就行。 - 如果需要在每个方法调用前/后增加处理逻辑,在
for
循环里面处理一次就行,工作量也不大 - 与前面两个使用
反射
来获取到方法的引用,这里直接通过方法引用
,编辑器和编译器可以检查出潜在的错误,从而安全性较高。
缺点:
暂未发现明确的缺点。
4. 其它实现方式
当然也有其它方式来实现在一个循环中依次调用多个方法 ,比如:枚举
Enum,或者 把方法拆分到每个不同的类,每个类里的方法都是相同的名字和签名,然后创建每个类的实例放入一个数组,遍历,调用之。
由于这些实现方式的代码量较大,这里就不予举例了。
此文完。
Java中如何优雅地调用多个方法相关推荐
- java调用visa的dll库,查看新闻/公告--[备忘]Java中,使用JNA调用Visa32.dll,控制频谱仪~~...
Java中,使用JNA调用Visa32.dll,控制频谱仪~~ C:\Program Files\Agilent\IO Libraries Suite\ 有visa.chm,是方法和属性的说明. 首先 ...
- java 中lock,java中lock获取锁的四种方法
在java接口中会存放着许多方法,方便线程使用时的直接调用.对于lock接口大家都不陌生,我们已经初步对概念进行了理解.那么在获取锁的方法上想必还不是很清楚.下面我们就lock获取锁的四种方法分别进行 ...
- java面试题27 java中下面哪些是Object类的方法()
java面试题27 java中下面哪些是Object类的方法() A notify() B notifyAll() C sleep() D wait() 蒙蔽树上蒙蔽果,蒙蔽树下你和我.遇到这种题,我 ...
- Java中如何编写一个完美的equals方法
在Java中,由于语言规范要求equals方法具有以下特性: 1)自反性:对于任何非空引用x,x.equals(x)应当返回true. 2)对称性:对于任何引用x和y,当且仅当x.equals(y)返 ...
- Java中Date和Calender类的使用方法
查看文章 Java中Date和Calender类的使用方法 2009-10-04 20:49 Date和Calendar是Java类库里提供对时间进行处理的类,由于日期在商业逻辑的应用中占据着 ...
- 关于Java中Match类的appendReplacement()方法的一个坑{ character to be escaped }
关于Java中Match类的appendReplacement()方法的一个坑{java.lang.IllegalArgumentException: character to be escaped ...
- java 转换gbk编码,java中GBK转UTF-8乱码的解决方法
java中GBK转UTF-8乱码的解决方法 如果自己采用的是GBK编码,对方采用得到是UTF-8编码,发送数据时需要将GBK编码数据转换成UTF-8编码数据,这样对方才不会乱码. 问题出现:GBK转U ...
- Java中动态加载字节码的方法 (持续补充)
文章目录 Java中动态加载字节码的方法 1.利用 URLClassLoader 加载远程class文件 2.利用 ClassLoader#defineClass 直接加载字节码 2.1 类加载 - ...
- JAVA中线程同步的几种实现方法
JAVA中线程同步的几种实现方法 一.synchronized同步的方法: 1.synchronized同步方法 即有synchronized关键字修饰的方法. 由于java的每个对象都有一个内置锁, ...
最新文章
- C++ 笔记(13)— 函数(函数声明、函数定义、函数调用[传值、指针、引用]、函数参数默认值、函数重载)
- Android 系统自动重启Bug(高通平台)
- ARM公布“物联网”嵌入式mbed OS系统软件平台
- mybatis增删改查快速实现!!!
- Selenium | 网上教程
- android 弹出菜单 toast,Android学习第二天:Toast(提醒)、Menu(菜单)、Intent的显式和隐式(包括打开、适配网站,调用拨号界面等)...
- C++获取PE文件的入口点
- Linux USB 驱动开发(二)—— USB 驱动几个重要数据结构
- 300plc与组态王mpi通讯_S7-300与S7-200之间的MPI通信
- 面试官 | 什么是递归算法?它有什么用?
- 百度小程序全套源码下载、免费分享,一键生成百度小程序
- 技术人应有的职业意识
- day25-静态、组合、继承
- iOS马甲包预审分析工具
- web课程设计网页规划与设计 :DW旅游主题网页设计——凤阳智慧旅游官方-地方旅游网站模板html源码HTML+CSS+JavaScript
- 用python构建多只股票日收益率直方图_Barra纯因子收益率的Python实现
- 取消UL和OL符号以及padding和margin后恢复默认值的CSS
- 一种TV端自动化测试应用OTA升级的方法
- 嵌入式linux环境取得U盘容量等信息的方法
- 苏宁小Biu系列产品大幅提升服务标准:亲儿子与干儿子不一样?