理解和运用Java中的Lambda

回想一下,JDK8是2014年发布正式版的,到现在为(2020-02-08)止已经过去了5年多。JDK8引入的两个比较强大的新特性是Lambda表达式(下文的Lambda特指JDK提供的Lambda)和Stream,这两个强大的特性让函数式编程在Java开发中发扬光大。这篇文章会从基本概念、使用方式、实现原理和实战场景等角度介绍Lambda的全貌,其中还会涉及一些函数式编程概念、JVM一些知识等等。

基本概念

下面介绍一些基本概念,一步一步引出Lambda的概念。

函数式接口

函数式接口和接口默认方法都是JDK8引入的新特性。函数式接口的概念可以从java.lang.FunctionalInterface注解的API注释中得知:

An informative annotation type used to indicate that an interface type declaration is intended to be a functional interface as defined by the Java Language Specification.

Conceptually, a functional interface has exactly one abstract method. Since {@linkplain java.lang.reflect.Method#isDefault() default methods} have an implementation, they are not abstract.

简单来说就是:@FunctionalInterface是一个提供信息的接口(其实就是标识接口),用于表明对应的接口类型声明是一个Java语言规范定义的函数式接口。从概念上说,一个函数式接口有且仅有一个抽象方法,因为接口默认方法必须予以实现,它们不是抽象方法。

所以可以这样给函数式接口定义:如果一个接口声明的时候有且仅有一个抽象方法,那么它就是函数式接口,可以使用@FunctionalInterface注解标识。

JDK中已经定义了很多内置的函数式接口,例如:

// java.lang.Runnable @FunctionalInterface public interface Runnable { public abstract void run(); } // java.util.function.Supplier @FunctionalInterface public interface Supplier<T> { T get(); }

也可以自定义函数式接口,例如:

@FunctionalInterface public interface CustomFunctionalInterface { // 可以缩写为void process(); 接口方法定义的时候,默认使用public abstract修饰 public abstract void process(); } 接口默认方法

接口默认方法的含义可以见Java官方教程中对应的章节,在文末的参考资料可以查看具体的链接:

Default methods enable you to add new functionality to the interfaces of your libraries and ensure binary compatibility with code written for older versions of those interfaces.

简单来说就是:默认方法允许你在你的类库中向接口添加新的功能,并确保新增的默认方法与这些接口的较早版本编写的代码二进制兼容

接口默认方法(下称默认方法)通过default关键字声明,可以直接在接口中编写方法体。也就是默认方法既声明了方法,也实现了方法。这一点很重要,在默认方法特性出现之前,Java编程语言规范中,接口的本质就是方法声明的集合体,而自默认方法特性出现之后,接口的本质也改变了。默认方法的一个例子如下:

public interface DefaultMethod { default void defaultVoidMethod() { } default String sayHello(String name) { return String.format("%s say hello!", name); } static void main(String[] args) throws Exception { class Impl implements DefaultMethod { } DefaultMethod defaultMethod = new Impl(); System.out.println(defaultMethod.sayHello("throwable")); // throwable say hello! } }

如果继承一个定义了默认方法的接口,那么可以有如下的做法:

完全忽略父接口的默认方法,那么相当于直接继承父接口的默认方法的实现(方法继承)。

重新声明默认方法,这里特指去掉default关键字,用public abstract关键字重新声明对应的方法,相当于让默认方法转变为抽象方法,子类需要进行实现(方法抽象)。

重新定义默认方法,也就是直接覆盖父接口中的实现(方法覆盖)。

结合前面一节提到的函数式接口,这里可以综合得出一个结论:函数式接口,也就是有且仅有一个抽象方法的接口,可以定义0个或者N(N >= 1)个默认方法。这一点正是Stream特性引入的理论基础。举个例子:

@FunctionalInterface public interface CustomFunctionalInterface { public abstract void process(); default void defaultVoidMethod() { } default String sayHello(String name) { return String.format("%s say hello!", name); } }

这里说点题外话。

在写这篇文章的时候,笔者想起了一个前同事说过的话,大意如下:在软件工程中,如果从零做起,任何新功能的开发都是十分简单的,困难的是在兼容所有历史功能的前提下进行新功能的迭代。试想一下,Java迭代到今天已经过去十多年了,Hotspot VM源码工程已经十分庞大(手动编译过OpenJDK Hotspot VM源码的人都知道过程的痛苦),任何新增的特性都要向前兼容,否则很多用了历史版本的Java应用会无法升级新的JDK版本。既要二进制向前兼容,又要迭代出新的特性,Java需要进行舍夺,默认方法就是一个例子,必须舍去接口只能定义抽象方法这个延续了多年在Java开发者中根深蒂固的概念,夺取了基于默认方法实现构筑出来的流式编程体系。笔者有时候也在思考:如果要我去开发Stream这个新特性,我会怎么做或者我能怎么做?

嵌套类(Nested Classes)

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpjwpd.html