vlambda博客
学习文章列表

Lambda与函数式接口


JDK8 于2014年发布至今,已有6个年头。其中几个重要的特性,lambda表达式,函数式接口,方法引用,默认方法等,至今未完全普及。在回到家乡邯郸这个三线小城市后,竟然有同事问我,有没有简单的统计List数据的方法,颇让我惊讶。虽然我认为一个对公司有贡献的程序员,首先应该熟悉业务。但是我也担心,技术上落后太多,也会拖累项目的性能。因此,本着服务家乡的想法,梳理一下Java程序员基本的知识点。与互联网项目不同,我更加重视基础知识。对于微服务框架,我认为绝大多数应用,是用不到的。如果为了使用而使用,只会增加团队不必要的负担。闲话不多说,我们开始介绍Lambda,同时,会介绍函数式接口。


引言

Lambda表达式,也可以称作闭包。它是推动Jdk8发布的最重要的特性。在此之前,jdk是不允许将函数作为参数进行传递的,即不支持函数式编程。这使其在很多地方,代码过于啰嗦,如同老太太的叨叨。因此,在Jdk8引入Lambda后,代码变得更加简洁,更加清晰了。


Lambda语法

完整的Lambda表达式包含三部分组成:参数列表,箭头,声明语句。

(Type1 param1, Type2 param2, ..., TypeN paramN)-> {声明1; 声明2; ...... return mentmentM;}


以下是Lambda表达式的重要说明:

1,参数可选类型声明:不需要指明param1,param2的具体类型,编译器可以自动识别。于是,上面可以简化为:

param1, param2, ..., paramN)-> {指令1; 指令2; ......}

2,参数可选圆括号:当参数仅有1个的时候,圆括号可以省略。于是,上面可以继续简化为:
param-> {指令1; 指令2; ......}

3,函数体可选大括号:当函数体中仅包含一个指令的时候,大括号可以省略。

param-> 指令1;

具体表达式示例

// 1.不需要的参数,返回变量5() -> 5
// 2.接收一个参数(数字类型),返回其2倍的值x -> 2 * x
// 3.接受2个参数(数字),并返回他们的差值(x,y) -> x-y
// 4.接收2个int型整数,返回他们的和(int x, int y) -> x + y
// 5.接受一个字符串对象,并在控制台打印,不返回任何值(看起来像是返回void)(String s)-> System.out.print(s)

函数式接口

要想使用Lambda,必然需要一个函数式接口。那么,什么是函数式接口呢?函数式接口,Functional Interface,必须有且仅有一个抽象方法的接口,对默认方法的个数没有限制(Jdk8 允许在接口中有默认实现),同时函数式接口用@FunctionalInterface来标注。函数式接口可以友好的支持Lambda。

Jdk8 之前符合函数式接口条件的接口有:

  • java.lang.Runnable

  • java.util.concurrent.Callable

  • java.security.PrivilegedAction

  • java.util.Comparator

  • java.io.FileFilter

  • java.nio.file.PathMatcher

  • java.lang.reflect.InvocationHandler

  • java.beans.PropertyChangeListener

  • java.awt.event.ActionListener

  • javax.swing.event.ChangeListener

Jdk8 新增的函数接口一般都存储在java.util.function中,其中有很多类,这里不一一赘述。感兴趣的同学,可以在这个package中自学一下。接下来,我们举两个例子,来说明函数式接口与Lambda的用法。

public class LambdaDemo {
public static void main(String[] args) { runDemo(); }
public static void runDemo() { //传统定义 Runnable runnableJdk7 = new Runnable() { @Override public void run() { System.out.println(Thread.currentThread()); } }; runnableJdk7.run();
//Lambda 形式 Runnable runnableJdk8 = () -> System.out.println(Thread.currentThread()); runnableJdk8.run(); }
}

上述代码,以定义一个线程为例,展示了Jdk8以前以及Jdk8的做法。那么,Lambda究竟做了什么呢?很明显,简化了接口抽象方法的实现,我们用Lambda实现了Runnablerun方法,并创建了一个对象runnableJdk8。这就是函数式接口必须有且只能有一个抽象方法的原因。因为创建的时候,等号右面必须有,且只能有一个Lambda表达式,这个Lambda表达式,就是用来实现这个函数式接口唯一的抽象方法。

我们再举一个例子,假设有一张学生成绩表,筛选成绩大于80的。该怎么写呢?

public static void score() {
List<Student> listJdk7 = new ArrayList<>( Arrays.asList( new Student(1, "张三", 41), new Student(2, "王二", 92), new Student(3, "李四", 52), new Student(4, "王五", 81), new Student(5, "赵六", 67) ) );
Iterator<Student> listIt = listJdk7.iterator(); while (listIt.hasNext()) { Student next = listIt.next(); if (next.getScore().intValue() < 80) { listIt.remove(); } } System.out.println(JSON.toJSONString(listJdk7, SerializerFeature.PrettyFormat)); //以下是Jdk8 Lambda 结合 函数式接口 List<Student> listJdk8 = new ArrayList<>( Arrays.asList( new Student(1, "张三", 41), new Student(2, "王二", 92), new Student(3, "李四", 52), new Student(4, "王五", 81), new Student(5, "赵六", 67) ) ); List<Student> collect = listJdk8.stream().filter(x -> x.getScore() > 80).collect(Collectors.toList()); System.out.println(JSON.toJSONString(collect, SerializerFeature.PrettyFormat)); }


Jdk7这里不在解释,这里解释一下Jdk8的实现。Jdk8调用了Stream的实现类java.util.stream.ReferencePipeline<P_IN, P_OUT>filter方法,具体实现如下。

 @Override public final Stream<P_OUT> filter(Predicate<? super P_OUT> predicate) { Objects.requireNonNull(predicate); return new StatelessOp<P_OUT, P_OUT>(this, StreamShape.REFERENCE, StreamOpFlag.NOT_SIZED) { @Override Sink<P_OUT> opWrapSink(int flags, Sink<P_OUT> sink) { return new Sink.ChainedReference<P_OUT, P_OUT>(sink) { @Override public void begin(long size) { downstream.begin(-1); }
@Override public void accept(P_OUT u) { if (predicate.test(u)) downstream.accept(u); } }; } }; }


从实现中,我们可以看到,这个方法,传入一个java.util.function.Predicate的接口函数,并且调用了其抽象方法test(T t)该接口的内容大致如下,其中抽象方法test,接受一个输入对象,返回ture或者false额,我觉得这有点废话,毕竟代码是这么写的。(#^.^#)

@FunctionalInterfacepublic interface Predicate<T> {
/** * Evaluates this predicate on the given argument. * * @param t the input argument * @return {@code true} if the input argument matches the predicate, * otherwise {@code false} */ boolean test(T t); ...}

我们刚才的写法是
x -> x.getScore() > 80

x 代表我们要操作的对象,也就是test方法的入参 tx.getScore()>80则是test方法的具体实现。这个实现会返回boolean值。这样是不是好理解了。


总结

借助函数式接口与接口默认方法,可以使代码变得更简洁,更快速。

那么,要来一杯咖啡吗?

附录:Jdk8 自带的函数式接口

序号 接口 & 描述
1 BiConsumer<T,U>代表了一个接受两个输入参数的操作,并且不返回任何结果
2 BiFunction<T,U,R>代表了一个接受两个输入参数的方法,并且返回一个结果
3 BinaryOperator代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果
4 BiPredicate<T,U>代表了一个两个参数的boolean值方法
5 BooleanSupplier代表了boolean值结果的提供方
6 Consumer代表了接受一个输入参数并且无返回的操作
7 DoubleBinaryOperator代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。
8 DoubleConsumer代表一个接受double值参数的操作,并且不返回结果。
9 DoubleFunction代表接受一个double值参数的方法,并且返回结果
10 DoublePredicate代表一个拥有double值参数的boolean值方法
11 DoubleSupplier代表一个double值结构的提供方
12 DoubleToIntFunction接受一个double类型输入,返回一个int类型结果。
13 DoubleToLongFunction接受一个double类型输入,返回一个long类型结果
14 DoubleUnaryOperator接受一个参数同为类型double,返回值类型也为double 。
15 Function<T,R>接受一个输入参数,返回一个结果。
16 IntBinaryOperator接受两个参数同为类型int,返回值类型也为int 。
17 IntConsumer接受一个int类型的输入参数,无返回值 。
18 IntFunction接受一个int类型输入参数,返回一个结果 。
19 IntPredicate:接受一个int输入参数,返回一个布尔值的结果。
20 IntSupplier无参数,返回一个int类型结果。
21 IntToDoubleFunction接受一个int类型输入,返回一个double类型结果 。
22 IntToLongFunction接受一个int类型输入,返回一个long类型结果。
23 IntUnaryOperator接受一个参数同为类型int,返回值类型也为int 。
24 LongBinaryOperator接受两个参数同为类型long,返回值类型也为long。
25 LongConsumer接受一个long类型的输入参数,无返回值。
26 LongFunction接受一个long类型输入参数,返回一个结果。
27 LongPredicateR接受一个long输入参数,返回一个布尔值类型结果。
28 LongSupplier无参数,返回一个结果long类型的值。
29 LongToDoubleFunction接受一个long类型输入,返回一个double类型结果。
30 LongToIntFunction接受一个long类型输入,返回一个int类型结果。
31 LongUnaryOperator接受一个参数同为类型long,返回值类型也为long。
32 ObjDoubleConsumer接受一个object类型和一个double类型的输入参数,无返回值。
33 ObjIntConsumer接受一个object类型和一个int类型的输入参数,无返回值。
34 ObjLongConsumer接受一个object类型和一个long类型的输入参数,无返回值。
35 Predicate接受一个输入参数,返回一个布尔值结果。
36 Supplier无参数,返回一个结果。
37 ToDoubleBiFunction<T,U>接受两个输入参数,返回一个double类型结果
38 ToDoubleFunction接受一个输入参数,返回一个double类型结果
39 ToIntBiFunction<T,U>接受两个输入参数,返回一个int类型结果。
40 ToIntFunction接受一个输入参数,返回一个int类型结果。
41 ToLongBiFunction<T,U>接受两个输入参数,返回一个long类型结果。
42 ToLongFunction接受一个输入参数,返回一个long类型结果。
43 UnaryOperator接受一个参数为类型T,返回值类型也为T。