函数式接口

概念
函数式接口在 Java 中是指: 有且仅有一个抽象方法的接口
函数式接口,即适用于函数式编程场景的接口。而 Java 中的函数式编程体现就是 Lambda ,所以函数式接口就是可
以适用于 Lambda 使用的接口。只有确保接口中有且仅有一个抽象方法, Java 中的 Lambda 才能顺利地进行推导。
备注: 语法糖 是指使用更加方便,但是原理不变的代码语法。例如在遍历集合时使用的 for-each 语法,其实
底层的实现原理仍然是迭代器,这便是 语法糖 。从应用层面来讲, Java 中的 Lambda 可以被当做是匿名内部
类的 语法糖 ,但是二者在原理上是不同的。
格式
只要确保接口中有且仅有一个抽象方法即可:
修饰符 interface 接口名称 {
public abstract 返回值类型 方法名称 ( 可选参数信息 );
// 其他非抽象方法内容
}
由于接口当中抽象方法的 public abstract 是可以省略的,所以定义一个函数式接口很简单
public interface MyFunctionalInterface {
void myMethod ();
}
@FunctionalInterface 注解
@Override 注解的作用类似, Java 8 中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注
解可用于一个接口的定义上:
@FunctionalInterface
public interface MyFunctionalInterface {
void myMethod ();
}
一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。需要
的是,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。
自定义函数式接口
对于刚刚定义好的 MyFunctionalInterface 函数式接口,典型使用场景就是作为方法的参数:
 
函数式编程
在兼顾面向对象特性的基础上, Java 语言通过 Lambda 表达式与方法引用等,为开发者打开了函数式编程的大门。
下面我们做一个初探。
 
Lambda 的延迟执行
有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费。而 Lambda 表达式是延迟执行的,这正好可以
作为解决方案,提升性能。
性能浪费的日志案例
: 日志可以帮助我们快速的定位问题,记录程序运行过程中的情况,以便项目的监控和优化。
一种典型的场景就是对参数进行有条件使用,例如对日志消息进行拼接后,在满足条件的情况下进行打印输出:
public class Demo01Logger {
private static void log ( int level , String msg ) {
if ( level == 1 ) {
System . out . println ( msg );
}
}
public static void main ( String [] args ) {
String msgA = "Hello" ;
String msgB = "World" ;
String msgC = "Java" ;
log ( 1 , msgA + msgB + msgC );
}
}
这段代码存在问题:无论级别是否满足要求,作为 log 方法的第二个参数,三个字符串一定会首先被拼接并传入方
法内,然后才会进行级别判断。如果级别不符合要求,那么字符串的拼接操作就白做了,存在性能浪费
备注: SLF4J 是应用非常广泛的日志框架,它在记录日志时为了解决这种性能浪费的问题,并不推荐首先进行
字符串的拼接,而是将字符串的若干部分作为可变参数传入方法中,仅在日志级别满足要求的情况下才会进
行字符串拼接。例如: LOGGER.debug(" 变量 {} 的取值为 {} ", "os", "macOS") ,其中的大括号 {} 为占位
符。如果满足日志级别要求,则会将 “os” “macOS” 两个字符串依次拼接到大括号的位置;否则不会进行字
符串拼接。这也是一种可行解决方案,但 Lambda 可以做到更好。
体验 Lambda 的更优写法
使用 Lambda 必然需要一个函数式接口:
@FunctionalInterface
public interface MessageBuilder {
String buildMessage ();
}
然后对 log 方法进行改造:
public class Demo02LoggerLambda {
private static void log ( int level , MessageBuilder builder ) {
if ( level == 1 ) {
System . out . println ( builder . buildMessage ());
}
}
public static void main ( String [] args ) {
String msgA = "Hello" ;
String msgB = "World" ;
String msgC = "Java" ;
log ( 1 , () ‐> msgA + msgB + msgC );
}
这样一来,只有当级别满足要求的时候,才会进行三个字符串的拼接;否则三个字符串将不会进行拼接
扩展:实际上使用内部类也可以达到同样的效果,只是将代码操作延迟到了另外一个对象当中通过调用方法
来完成。而是否调用其所在方法是在条件判断之后才执行的。
 
使用 Lambda 作为参数和返回值
如果抛开实现原理不说, Java 中的 Lambda 表达式可以被当作是匿名内部类的替代品。如果方法的参数是一个函数
式接口类型,那么就可以使用 Lambda 表达式进行替代。使用 Lambda 表达式作为方法参数,其实就是使用函数式
接口作为方法参数。
例如 java.lang.Runnable 接口就是一个函数式接口,假设有一个 startThread 方法使用该接口作为参数,那么就
可以使用 Lambda 进行传参。这种情况其实和 Thread 类的构造方法参数为 Runnable 没有本质区别。
public class Demo04Runnable {
private static void startThread ( Runnable task ) {
new Thread ( task ). start ();
}
public static void main ( String [] args ) {
startThread (() ‐> System . out . println ( " 线程任务执行! " ));
}
}
类似地,如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个 Lambda 表达式。当需要通过一
个方法来获取一个 java.util.Comparator 接口类型的对象作为排序器时 , 就可以调该方法获取。
import java . util . Arrays ;
import java . util . Comparator ;
public class Demo06Comparator {
private static Comparator < String > newComparator () {
return ( a , b ) ‐> b . length () a . length ();
}
public static void main ( String [] args ) {
String [] array = { "abc" , "ab" , "abcd" };
System . out . println ( Arrays . toString ( array ));
Arrays . sort ( array , newComparator ());
System . out . println ( Arrays . toString ( array ));
}
}
 
常用函数式接口
JDK 提供了大量常用的函数式接口以丰富 Lambda 的典型使用场景,它们主要在 java.util.function 包中被提供。
下面是最简单的几个接口及使用示例。
Supplier 接口
java.util.function.Supplier<T> 接口仅包含一个无参的方法: T get() 。用来获取一个泛型参数指定类型的对
象数据。由于这是一个函数式接口,这也就意味着对应的 Lambda 表达式需要 对外提供 一个符合泛型类型的对象
数据。
Consumer 接口
java.util.function.Consumer<T> 接口则正好与 Supplier 接口相反,它不是生产一个数据,而是 消费 一个数据,
其数据类型由泛型决定。
抽象方法: accept
默认方法: andThen
如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费数据的时候,首先做一个操作,
然后再做一个操作,实现组合。而这个方法就是 Consumer 接口中的 default 方法 andThen 。下面是 JDK 的源代码:
default Consumer < T > andThen ( Consumer <? super T > after ) {
Objects . requireNonNull ( after );
return ( T t ) ‐> { accept ( t ); after . accept ( t ); };
}
备注: java.util.Objects requireNonNull 静态方法将会在参数为 null 时主动抛出
NullPointerException 异常。这省去了重复编写 if 语句和抛出空指针异常的麻烦
要想实现组合,需要两个或多个 Lambda 表达式即可,而 andThen 的语义正是 一步接一步 ”操作。
 
Predicate 接口
有时候我们需要对某种类型的数据进行判断,从而得到一个 boolean 值结果。这时可以使用
java.util.function.Predicate<T> 接口
 
抽象方法: test
Predicate 接口中包含一个抽象方法: boolean test(T t) 。用于条件判断的场景:
默认方法: and
既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个 Predicate 条件使用 逻辑连接起来实
并且 的效果时,可以使用 default 方法 and 。其 JDK 源码为:
default Predicate < T > and ( Predicate <? super T > other ) {
Objects . requireNonNull ( other );
return ( t ) ‐> test ( t ) && other . test ( t );
}
默认方法: or
and 类似,默认方法 or 实现逻辑关系中的 JDK 源码为:
default Predicate < T > or ( Predicate <? super T > other ) {
Objects . requireNonNull ( other );
return ( t ) ‐> test ( t ) || other . test ( t );
}
默认方法: negate
已经了解了,剩下的 (取反)也会简单。默认方法 negate JDK 源代码为
default Predicate < T > negate () {
return ( t ) ‐> ! test ( t );
}
从实现中很容易看出,它是执行了 test 方法之后,对结果 boolean 值进行 “!” 取反而已。一定要在 test 方法调用之前
调用 negate 方法,正如 and or 方法一样:
 
Function 接口
java.util.function.Function<T,R> 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,
后者称为后置条件。
抽象方法: apply
Function 接口中最主要的抽象方法为: R apply(T t) ,根据类型 T 的参数获取类型 R 的结果。
使用的场景例如:将 String 类型转换为 Integer 类型
 
默认方法: andThen
Function 接口中有一个默认的 andThen 方法,用来进行组合操作。 JDK 源代码如:
default < V > Function < T , V > andThen ( Function <? super R , ? extends V > after ) {
Objects . requireNonNull ( after );
return ( T t ) ‐> after . apply ( apply ( t ));
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值