Java 中的“函数指针”

[一点废话] 很久都没有管理过自己的博客了,原先只是想记录点自己的笔记和遇到的问题以及如何解决的,以供自己回头查看方便,同时也可以帮助其他人。最近登陆上来发现竟然有这么多人看我写过的博文,虽然写的也不怎么好,所以打算后面好好的写一些东西,养成习惯吧。最近又重新看了比较新的《Java核心卷 第十版》,发现了值得记录的一些内容,这里就结合我自己的体会总结下,鉴于本人水平有限,如果有什么理解不对的地方,欢迎指出共同学习 ^_^


目录


引言

众所周知,在C或者C++语言里面是有函数指针存在的,在Python中可以将函数对象作为参数传入其他函数中,同时在类似于Scala的函数式编程语言中,对于函数的操作更为广泛,这样可以比较便捷的将函数指针作为参数传递到另一个函数中,在其他函数中对传入的参数进行调用,这样代码的通用性更强封装性良好,例如:我们在设计算法时,通常需要测试该算法在不同的函数上的效果如何,这是就需要我们将函数作为参数传入算法,这样封装起来,当测试不同的测试函数时,只需要传入不同的函数就行,倘若没有这种机制,我们可能就需要进入算法的具体实现,更改其中具体的部分才能达到同样的效果。


C 语言中的函数指针

首先看下C语言中是如何使用函数指针的,这里我们假设背景是我们有两种处理数据的方式:method1method2,因此这两种方法需要数据文件名从而读取数据并进行处理,然后我们封装一个通用性较高的数据处理函数 dataProcess 其需要函数指针和数据文件名(这里的数据文件名就是要传递到给定的函数指针对应的函数中使用的,如果不理解的话可以看下代码,可能更容易理解)作为参数,下面是具体代码实现:

//首先这里声明函数原型,这里就假设这两个数据处理函数返回处理好后重新保存的文件名(类型为char*)好了
char* method1(char*);
char* method2(char*);
/*声明我们的数据处理通用性函数
 *这里解释下第一个参数是什么鬼,第一参数定义了这里是一个函数指针
 *且这个函数指针指向的函数必须是一个形式参数为char*类型,并且返回char*类型结果的函数
 *也就是这里必须与我们想要使用的method1 和 method2函数原型保持一致
 */
char* dataProcess(char* (*)(char*), char*);

//下面给出我们的实现
char* method1(char* dataFileName){
    //一些数据处理后,我们把数据输出的otherDataFileName文件中
    return otherDataFileName;
}

char* method2(char* dataFileName){
    //一些数据处理后,我们把数据输出的otherDataFileName文件中
    return otherDataFileName;
}

//通用调用接口函数,第一个参数是函数指针 function pointer,第二个参数是要传递到函数*FUNC_P中的参数
char* dataProcess(char* (*FUNC_P)(char*), char* dataFileName){
    //调用传入的参数并且返回结果
    return (FUNC_P)(dataFileName);
}

通过上述步骤我们就将需要使用的函数接口暴露给用户,通过外部的一句代码就可以更改算法其中的步骤,而不是每一次修改函数就要进入dataProcess 的内部去修改具体的实现代码。感觉上面的过程还是比较简单的,代码注释写的也是比较清楚的,C语言的实现和具体细节不是重点,只是为了介绍使用函数指针的灵活性和好处,下面我们介绍下Java中实现“函数指针”的操作。


Java中的“函数指针”

从表面上看Java没有提供方法指针,事实上Java的设计者曾说过方法指针是很危险的,并且常常会带来隐患。 他们认为Java提供的接口(interface)是一种更好的解决方案(这部分的内容我将会后续更新),当然除此之外,使用灵活的反射机制也可以实现相同的效果。
首先,我先贴上代码,随后进行解释,这里实现和上面C语言部分相同的功能,

下面先定义一个工具类DataProcess 其中包含了两个static方法用于处理数据

package com.houzhipeng;

/**
 * 定义处理数据方式的类
 * @author HouZhipeng
 */
public class DataProcess {

    /**
     * 定义处理数据的方式1函数,这里将其设置为类方法,方便调用
     * @param dataFileName 保存数据的文件名称
     * @return 保存处理后数据的文件名
     */
    public static String method1(String dataFileName){
        // 一些数据处理后,我们把数据输出的名为:“xxx” 的文件中
        return "xxx";
    }

    /**
     * 定义处理数据的方式2函数,这里将其设置为类方法,方便调用
     * @param dataFileName 保存数据的文件名称
     * @return 保存处理后数据的文件名
     */
    public static String method2(String dataFileName){
        // 一些数据处理后,我们把数据输出的名为:“xxx” 的文件中
        return "xxx";
    }

}

接着定义测试类TestMain类,其中包含了程序入口和通用调用方法。这里主要使用的就是反射的方式获取到位于Data Process类中的方法对象从而进一步进行调用。代码中的注释解释的比较清楚,不太清楚反射使用的可以去查看下java.lang.reflect包java.lang.Classjava.lang.reflect.Method中的相关内容。

package com.houzhipeng;

import java.lang.reflect.Method;

public class MainTest {
    public static void main(String[] args) {
        String dataFileName = "dataFileName";
        try {
            // 通过反射获取到DataProcess类中的方法对象
            Method method1 = DataProcess.class.getMethod("method1", String.class);
            Method method2 = DataProcess.class.getMethod("method2", String.class);

            // 分别使用不同的方式处理数据
            callFunc(method1, dataFileName);
            callFunc(method2, dataFileName);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 通用的调用函数
     * @param method 使用的数据处理方式,方法对象
     * @param dataFileName 指定的函数所需要的参数,保存了数据文件名称字符串
     * @return 数据处理后保存的文件名
     * @throws Exception 在调用Method对象下的invoke方法而引发的异常,需要调用者进行处理
     */
    public static String callFunc(Method method, String dataFileName) throws Exception{
        try {
            return (String) method.invoke(null, dataFileName);
        } catch (Exception exp) {
            // 将捕获的错误交给调用者处理
            throw exp;
        }
    }
}

结语

这里有一点需要提一下,invoke的原型为

public Object invoke(Object implictParameter, Object[] explictParameters)

参数和返回值必须是Object类型的,当为static方法时第一个参数可以使用null,这就意味着必须进行多次类型转换。这样做将会使编译器错过检查代码的机会。因此等到测试阶段才会发现这些错误,找到并改正它们将会更加困难。不仅如此,使用反射获得方法“指针”的代码要比仅仅直接调用方法明显要慢一些。
综上所述,建议仅在必要的时候才使用Method对象,而最好使用接口以及Java SE 8中的lambda表达式取代这种方式,后续我将补充这部分的内容,并将他们与该文档合为一个,先暂时写到这里。

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值