首先要说明的是在Java中并不存在指针概念,也没有什么所谓的黑科技。而得益于 Lambda 表达式在 Java8 中的引入,使得我们可以模仿函数指针来实现一套相似的逻辑。因此本文在介绍 Java 版本的函数指针时要从 Lambda 表达式开始。
一、Lambda 是什么
Lambda 表达式是一个匿名函数,可以将代码像数据一样进行传递。换句话说 Lambda 表达式是函数式编程的一种体现,它允许将函数当作参数传递给方法,或者将函数作为返回值,这种支持使得 Java 在函数式编程方面更为灵活,能够更好地处理集合操作、并行计算等任务。
1.1 代码简化过程
Java 作为一个面向对象的语言,通常会创建一系列的类来封装和处理具体的业务逻辑。在面对需要创建临时对象或者实现接口的情况下,“匿名内部类”的概念就会使得代码十分简洁。比方说我们使用如下内部类来比较两个数据的大小
TreeSet<Integer> treeSet = new TreeSet<>(new Comparator<Integer>() { |
这其中真正有效的语句是 Integer.compare(o1, o2) , 但匿名内部类的写法还是让我们写了很多冗余的代码,当比较的规则比较多的时候,可能就显比较杂乱。因此在此基础上,可以进一步采用设计模式,比如 策略模式 来改进,不过这同样带了一个问题:每种比较规则都会创建一个新的类,这是代码清晰要付出的代价。
那么当我们使用 Lambda 表达式会怎样呢?
TreeSet<Integer> treeSet = new TreeSet<>((o1, o2) -> Integer.compare(o1, o2)); |
看到了吗!原来相对复杂的代码精简到了最核心的一行,几乎去掉了所有冗余的代码!
1.2 Lambda 的原理分析
Lambda 表达式可以极大的简化我们的代码,但不是在任何地方都可以直接使用 Lambda 表达式,这需要两个依据:
- 必须有相应的函数接口
- 类型推断机制
怎么理解呢?函数接口是指内部只有一个抽象方法的接口,实际上 Lambda 的类型就是对应函数接口的类型就(类似 “张三” 在程序中对应的 String 类型),在上下文信息足够的情况下,编译器可以推断出参数表的类型,而不需要显式指明。
Lambda 表达式在 JVM 层级是通过 invokedynamic 指令实现,使用 Lambda 表达式不会产生新的类,这和匿名内部类不同。匿名内部类虽然在使用的时候不需要显示指定类名,但是编译器会自动为该类取名。以如下文件为例:
public class LambdaTest { |
使用 javac LambdaTest.java 得到 LambdaTest.class 文件,再使用 javap 反编译看那一下内容:
//javap -c -p LambdaTest.class |
反编译之后我们发现 Lambda 表达式被封装成了主类的一个 私有方法,并通过 invokedynamic 指令进行调用。
二、函数指针实现方式
经过以上对 Lambda 表达式的说明,接下来就根据表达式的特性来实现 Java 版本的 “函数指针”。
- 定义函数指针
public interface FunConsumer<U> { |
- 函数实现
private void test1(String name){ |
- 模拟调用
//指针数组 |
- 结果
test1 : 测试1 |
关于上面的 this 说明,this 在 Java 中是代表对象本身,在这里是用来表示要引用的是对象的实例方法,在 Lambda 表达式中一共有四种引用方式:
方式 | 示例 |
---|---|
引用静态方法 | DemoClass::staticMethodName |
引用特定对象的实例方法 | demoClass::instanceMethodName |
引用特定类型的任意对象的实例方法 | String::compareToIgnoreCase |
引用构造函数 | HashSet::new |