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() {
@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实现了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。 |