问题背景

有某个颇为复杂的功能,功能拆分时把该功能拆分成了数十个步骤,每个步骤用一个方法来实现。需要依次调用数十个方法/函数,这些方法有相同的签名。
为了后期的维护和扩展,显然不可能像这样去调用:

 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

评价

优点:

  1. 如果需要追加步骤或者调整步骤顺序,只需要更改存储方法名称的数组就行了,简单方便。
  2. 如果需要在每个方法调用前/后增加处理逻辑,在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

评价

优点:

  1. 如果需要追加步骤,增加相应的方法即可。
  2. 如果需要调整步骤顺序,调整注解中sort的值,在调用之前对其排序。
  3. 如果需要在每个方法调用前/后增加处理逻辑,在for循环里面处理一次就行,工作量也不大。

缺点:

  1. 调整顺序时需要增加额外的排序步骤。
  2. 如果注解被误用到了不是该步骤的方法上,会导致运行时出现问题。如果注解被误用到了不是该步骤的方法上,会导致运行时出现问题。

实现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

评价

优点:

  1. 如果需要追加步骤或调整步骤顺序,修改stepList这个数组就行。
  2. 如果需要在每个方法调用前/后增加处理逻辑,在for循环里面处理一次就行,工作量也不大
  3. 与前面两个使用反射来获取到方法的引用,这里直接通过方法引用,编辑器和编译器可以检查出潜在的错误,从而安全性较高。

缺点:
暂未发现明确的缺点。

4. 其它实现方式

当然也有其它方式来实现在一个循环中依次调用多个方法 ,比如:枚举Enum,或者 把方法拆分到每个不同的类,每个类里的方法都是相同的名字和签名,然后创建每个类的实例放入一个数组,遍历,调用之。

由于这些实现方式的代码量较大,这里就不予举例了。

此文完。

Java中如何优雅地调用多个方法相关推荐

  1. java调用visa的dll库,查看新闻/公告--[备忘]Java中,使用JNA调用Visa32.dll,控制频谱仪~~...

    Java中,使用JNA调用Visa32.dll,控制频谱仪~~ C:\Program Files\Agilent\IO Libraries Suite\ 有visa.chm,是方法和属性的说明. 首先 ...

  2. java 中lock,java中lock获取锁的四种方法

    在java接口中会存放着许多方法,方便线程使用时的直接调用.对于lock接口大家都不陌生,我们已经初步对概念进行了理解.那么在获取锁的方法上想必还不是很清楚.下面我们就lock获取锁的四种方法分别进行 ...

  3. java面试题27 java中下面哪些是Object类的方法()

    java面试题27 java中下面哪些是Object类的方法() A notify() B notifyAll() C sleep() D wait() 蒙蔽树上蒙蔽果,蒙蔽树下你和我.遇到这种题,我 ...

  4. Java中如何编写一个完美的equals方法

    在Java中,由于语言规范要求equals方法具有以下特性: 1)自反性:对于任何非空引用x,x.equals(x)应当返回true. 2)对称性:对于任何引用x和y,当且仅当x.equals(y)返 ...

  5. Java中Date和Calender类的使用方法

    查看文章     Java中Date和Calender类的使用方法 2009-10-04 20:49 Date和Calendar是Java类库里提供对时间进行处理的类,由于日期在商业逻辑的应用中占据着 ...

  6. 关于Java中Match类的appendReplacement()方法的一个坑{ character to be escaped }

    关于Java中Match类的appendReplacement()方法的一个坑{java.lang.IllegalArgumentException: character to be escaped ...

  7. java 转换gbk编码,java中GBK转UTF-8乱码的解决方法

    java中GBK转UTF-8乱码的解决方法 如果自己采用的是GBK编码,对方采用得到是UTF-8编码,发送数据时需要将GBK编码数据转换成UTF-8编码数据,这样对方才不会乱码. 问题出现:GBK转U ...

  8. Java中动态加载字节码的方法 (持续补充)

    文章目录 Java中动态加载字节码的方法 1.利用 URLClassLoader 加载远程class文件 2.利用 ClassLoader#defineClass 直接加载字节码 2.1 类加载 - ...

  9. JAVA中线程同步的几种实现方法

    JAVA中线程同步的几种实现方法 一.synchronized同步的方法: 1.synchronized同步方法 即有synchronized关键字修饰的方法. 由于java的每个对象都有一个内置锁, ...

最新文章

  1. DataBind数据核心
  2. 【ACM】杭电OJ 2040
  3. 如何理解最小二乘法(19世纪的统计学就相当于18世纪的微积分对于数学的地位)
  4. 张新波 | 十面埋伏 - 论大数据风控技术体系的构建
  5. 基于单TCP连接的高吞吐模型设计
  6. 成本计算引擎动态规则解析技术详解
  7. 【2016.11.16】HTML学习笔记
  8. (90)FPGA仿真计数器激励
  9. STM8学习笔记---通过示波器分析串口数据
  10. Linux Crontab 详解
  11. linux的虚拟内存是4G,而每个进程都有自己独立的4G内存空间,怎么理解?
  12. 关于svn目录地址迁移
  13. matlab求任意点最短路径,【最短路】求两点间最短路径的改进的Dijkstra算法及其matlab实现...
  14. 欠薪的公司,不要做任何犹豫!
  15. linux awk使用详解
  16. c语言程序员表白的语录,程序员一句话表白情书
  17. 新IT引领新经济 新华三惠州云博会展现“云”魅力
  18. css33d图片轮播_通过html+css3实现图片轮播切换
  19. vs2017 调试子进程
  20. Json - 学习/实践

热门文章

  1. Leetcode(5)——遍历,并查集,回溯法和二分查找
  2. 在OpenCV里用drawMarker画标记符号
  3. IDEA如何打断点调试
  4. cpu天梯图2021最新版6月
  5. Java入门 第七节 类和对象
  6. SpringBoot第 15 讲:SpringSecurity
  7. 微信支付的简单使用以及前端的显示
  8. java类加载过程(双亲委派机制)
  9. hdoj 1434 幸福列车【模拟】
  10. 12306 操作失败。(-1005)