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个的时候,圆括号可以省略。于是,上面可以继续简化为:
{指令1; 指令2; ......}
3,函数体可选大括号:当函数体中仅包含一个指令的时候,大括号可以省略。
指令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() {@Overridepublic void run() {System.out.println(Thread.currentThread());}};runnableJdk7.run();//Lambda 形式Runnable runnableJdk8 = () -> System.out.println(Thread.currentThread());runnableJdk8.run();}}
上述代码,以定义一个线程为例,展示了Jdk8以前以及Jdk8的做法。那么,Lambda究竟做了什么呢?很明显,简化了接口抽象方法的实现,我们用Lambda实现了Runnable的 run方法,并创建了一个对象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方法,具体实现如下。
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) {Sink<P_OUT> opWrapSink(int flags, Sink<P_OUT> sink) {return new Sink.ChainedReference<P_OUT, P_OUT>(sink) {public void begin(long size) {downstream.begin(-1);}public void accept(P_OUT u) {if (predicate.test(u))downstream.accept(u);}};}};}
从实现中,我们可以看到,这个方法,传入一个java.util.function.Predicate的接口函数,并且调用了其抽象方法test(T t)。该接口的内容大致如下,其中抽象方法test,接受一个输入对象,返回ture或者false。额,我觉得这有点废话,毕竟代码是这么写的。(#^.^#)
public 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方法的入参 t。而x.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。 |
