函数式编程语法
函数式接口
定义:有且只有一个抽象方法的接口
可以使用@FunctionalInterface 标识函数式接口
-
当接口中存在多个抽象方法时,提示:Multiple non-overriding abstract methods found in interface
-
当接口中不存在抽象方法时,提示:No target method found
-
函数式接口可以不带泛型,可以有多个用default修饰的默认实现方法
1 |
|
传统的方式实现函数式接口,使用匿名内部类的形式。使用Lambda表达式配合函数式接口可以简化代码:不关注单个抽象方法的权限,返回值,签名,只关注于该方法的入参和方法体即可。但是仅限于一个抽象方法的接口的匿名内部类的简化情况。
情况 | 简化方式 |
---|---|
Lambda的入参,以前都要指名类型 | 其实参数类型也是可以动态推断的,可以不写参数类型 |
Lambda的入参,只有一个入参的时候,还是要加上 () | 可以省略 () 不写,当且仅当一个参数的时候 |
Lambda的方法体,只有一个语句的时候(空返回值) | 可以直接省略方法体的{} |
Lambda的方法体,只有一个语句的时候(有返回值) | 也可以直接省略方法体的{},甚至省略return关键字 |
参考文章:JDK8新特性第一篇:Lambda表达式 - 知乎 (zhihu.com)
四大函数式接口
函数式接口 | 参数类型 | 对应程序逻辑的抽象 | 具体场景 |
---|---|---|---|
Function | <出参类型, 入参类型> | 程序中映射逻辑的抽象 | 比如我们写得很多的函数:接收入参,返回出参,方法代码块就是一个映射的具体逻辑。 |
Predicate | <入参类型>,test方法的出参为boolean类型 | 程序中判断逻辑的抽象 | 比如各种if判断,对于一个参数进行各种具体逻辑的判定,最后返回一个if else能使用的布尔值 |
Consumer | <入参类型>,apply方法的出参为void | 程序中的消费型逻辑的抽象 | 就比如Collection体系forEach方法,将每一个元素取出,交给Consumer指定的消费逻辑进行消费 |
Suppiler | <出参类型>,get方法的入参为void | 程序中的生产逻辑的抽象 | 就比如最常用的,new对象,这就是一个很经典的生产者逻辑,至于new什么,怎么new,这就是Suppiler中具体逻辑的写法了 |
代码演示:
1 | public class MappingExample { |
同时,在 java.util.function 包在上面的基本接口外进行了拓展
扩展模式 | 说明 | 案例 |
---|---|---|
泛型参数具体化 | 相当于具体化了四大函数式接口的泛型,让他们更具体的对于某一种情况进行操作。比如泛型被规定为int long double等等 | IntToDoubleFunction |
泛型类型多元化 | 相当于提供了二元操作,比如二元Consumer,就是消费两个参数,二元Function,就是两个入参对应一个出参这种 | BiConsumer 二元消费者、BiFunction 二元函数 |
泛型参数统一化 | 统一,XXXXOperator接口,【主要是Function扩展体系】,出参和入参的类型,强行让他们的类型固定为同一类型 | UnaryOperator 一元运算符、DoubleBinaryOperator 小数型二元运算符 |
还有很多泛型参数具体化后的各种Consumer和Supplier,以及像 IntToDoubleFunction
之类的拓展;还有泛型参数多元化如BiConsumer
二元消费者、BiFunction
二元函数;以及泛型参数统一化的XXXXOperator接口,如等
参考资料:
JDK8新特性第二篇:四大函数式接口【Function/Consumer/Supplier/Perdicate】、接口的扩展方法【default/static】 - 知乎 (zhihu.com)
方法引用
Lambda表达式将函数简化为入参、方法体、返回值,当这些条件和外部方法匹配时,可以简化为 XXX::XX
的格式
Lambda | 等效的方法引用 |
---|---|
(Apple a) -> a.getWeight() | Apple::getWeight |
() -> Thread.currentThread().dumpStack() | Thread.currentThread()::dumpStack |
(str, i) -> str.substring(i) | String::substring |
(String s) -> System.out.println(s) | System.out::println |
静态方法引用
1 | //传统方式:匿名内部类 |
构造器引用
1 | //功能要求:新建实例对象 |
实例对象引用
案例1:字符串分隔并输出
1 | String s = "abc,def,ghi,jkl"; |
案例2:验证数字是否大于某个值
1 |
|
非静态方法引用
1 | import lombok.Getter; |
总结
类型 | 格式 | 说明 | 案例 |
---|---|---|---|
静态方法引用 | 类名::方法名 | 方法体直接引用另外一个类的静态方法 | (args) -> ClassName.staticMethod(agrs)的Lambda,转化为ClassName::staticMethod |
构造器引用 | 类名::new | 通过构造器创建实例对象 | (args) -> new ClassName(agrs) 可以被简化为 ClassName::new |
实例对象方法引用 | 实例对象名::方法名 | 调用内存中实例对象所在类的方法 | (args) ->object.XXX(args) 可以被简写为:object::XXX【备注:object是一个程序中定义的对象即可,XXX就是该Object的方法】 |
非静态方法的引用 | 类名::方法名 | 调用内存中实例对象所在类的方法 | 有参的(实例名, args) -> 实例名.XXXXX(agrs) ,无参的 () ->实例名.可以被简化为 实例所在类名::XXXXX |
参考资料:JDK8新特性第三篇:方法引用 + StreamAPI - 知乎 (zhihu.com)
复合 Lambda 表达式
比较器复合
Comparator具有一个叫作comparing的静态辅助方法,它可以接受一个Function来提取Comparable键值,并生成一个Comparator对象。
1 | inventory.sort(comparing(Apple::getWeight)//按重量比较 |
谓词复合
谓词接口包括三个方法: negate、 and和or,让你可以重用已有的Predicate来创建更复杂的谓词。比如,可以使用negate方法来返回一个Predicate的非,比如苹果不是红的
1 | Predicate<Apple> notRedApple = redApple.negate(); // 产生现有Predicate对象redApple的非 |
and和or方法是按照在表达式链中的位置,从左向右确定优先级的。因此, a.or(b).and©可以看作(a || b) && c
1 | // 链接Predicate的方法来构造更复杂Predicate对象 |
函数复合
可以把Function接口所代表的Lambda表达式复合起来。 Function接口为此配了andThen和compose两个默认方法,它们都会返回Function的一个实例。
andThen方法会返回一个函数,它先对输入应用一个给定函数,再对输出应用另一个函数。比如,假设有一个函数f给数字加1 (x -> x + 1),另一个函数g给数字乘2,你可以将它们组合成一个函数h,先给数字加1,再给结果乘2:
1 | Function<Integer, Integer> f = x -> x + 1; |
compose方法,先把给定的函数用作compose的参数里面给的那个函数,然后再把函数本身用于结果。比如在上一个例子里用compose的话,它将意味着f(g(x)),而andThen则意味着g(f(x)):
1 | Function<Integer, Integer> f = x -> x + 1; |