Lambda 表达式与函数式接口
Java8 中引入了很多的新特性,包括接口的默认方法、函数式接口、Lambda 表达式等。今天就来聊聊用的比较多的特性: Lambda 表达式。Lambda 表达式并不是多么新的技术,它的本质是匿名内部类,在了解 Lambda 表达式之前先来看看匿名内部类。
匿名内部类
什么是匿名内部类
程序中大部分情况下用到的类都是有名字的,比如下面代码中的 Student 类,可以反复使用。
publicclassNormalNamedClass{publicstaticvoidmain(String[] args){
Student student = new Student();
student.eat();
}
}
interfacePerson{
voideat();
}
classStudentimplementsPerson{
@Override
publicvoideat(){
System.out.println("eat something.");
}
}
如果上述 Student 类只会被使用一次,而为其单独定义一个类显得比较麻烦,使用匿名内部类简化如下:
classAnonymousClass{publicstaticvoidmain(String[] args){
Person person = new Person(){
@Override
publicvoideat(){
System.out.println("eat something.");
}
};
person.eat();
}
}
interfacePerson{
voideat();
}
上述代码 Person person = new Person(){...}
包含两个步骤:
- 定义了一个匿名内部类,该类实现 Person 接口。
- new 一个匿名内部类的对象,并赋值给 person。
匿名内部类的本质是一个 带具体实现的 继承某个父类或者父接口的 匿名的 子类
匿名内部类的作用
主要是用来简化代码
Lambda 表达式
Lambda 表达式的本质是一个匿名内部类。将下面代码拷贝到 Intellij IDEA 中,idea 会提示可以进一步对改代码进行优化。
Anonymous new Person() can be replaced with lambda
classAnonymousClass{publicstaticvoidmain(String[] args){
Person person = new Person(){
@Override
publicvoideat(){
System.out.println("eat something.");
}
};
person.eat();
}
}
interfacePerson{
voideat();
}
优化后的代码如下:
classAnonymousClass{publicstaticvoidmain(String[] args){
Person person = () -> System.out.println("eat something.");
person.eat();
}
}
interfacePerson{
voideat();
}
匿名内部类被简化成一行代码:
() -> System.out.println("eat something.");
这甚至连一个方法都不像,别说像个类了。其实这就是 Lambda 表达式的重要特征:
可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
对应的例子代码如下:
// 1. 不需要参数,返回值为 5 () -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
可以看出 Lambda 表达式为了简化代码,无所不用其极。完全打破了 Java 方法在我们脑海中的固有格式:
- 一个名称
- 返回类型
- 参数列表
- 主体
Lambda 表达式只保留了最后两项,最后两项有时候还省去了圆括号、大括号和 return。
函数式接口
什么是函数式接口
先来看一份示例代码:
publicclassTestLambda{publicstaticvoidmain(String[] args){
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
list = list.stream().filter(item-> item % 2 == 0).collect(Collectors.toList());
System.out.println(list);
}
}
这份代码是筛选出 list 中为偶数的数字,非常简洁简单。stream() 将 list 转成流,filter 将满足条件的数据筛选出来继续流入下一个节点。 item-> item % 2 == 0
是个简化后的 lambda 表达式,为什么可以写在 filter() 参数里面呢?点击去可以看到 filter 的定义:
Stream<T> filter(Predicate<? super T> predicate);
再点 Predicate 进去,可以看到 Predicate 是一个接口:
@FunctionalInterfacepublicinterfacePredicate<T> {
booleantest(T t);
// 其他 default 方法省略
}
Predicate 中有很多 default 方法和一个抽象方法,并加了 @FunctionalInterface 注解,是一个函数式接口。函数式接口定义如下:
函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
函数式接口加不加 @FunctionalInterface 注解都不影响将某个接口作为函数式接口使用,但加上该注解,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。如下:
@FunctionalInterfacepublicinterfacePerson{
voideat();
voidsing();
}
上述代码会报如下错误:
为什么引入函数式接口的概念
作为纯面向对象语言 Java 方法的参数只有两种类型:基本类型和对象引用类型。没有办法把函数作为参数传递到方法中去,必须被包装在接口中,这样就不会破坏 Java 世界中一切皆对象的原则。item-> item % 2 == 0
看似传了一个方法到 filter 方法中,实际是通过各种推导传的是一个匿名内部类(接口)到其中。可以说函数式接口为 Lambda 表达式而生。
常用函数式接口
Java.util.function 中包含了很多函数式接口,用来支持 Java 的函数式编程。但其中最基本的只有 Consumer/Supplier/Function<T,R>/Predicate 四种,包中的其他的函数式接口都是该四种类型的扩展。
Consumer<T>: 接受一个输入并且无返回结果
publicclassTestConsumer{publicstaticvoidmain(String[] args){
Consumer<String> consumer = s -> System.out.println("accept:" + s);
consumer.accept("egg");
}
}
// 输出 accept egg.
Function<T,R>: 接受一个输入并且返回一个结果
publicclassTestFunction{publicstaticvoidmain(String[] args){
Function<String, String> function = (s) -> "function input with:" + s;
String result = function.apply("egg");
System.out.println(result);
}
}
Supplier<T>: 没有参数返回但返回一个结果
publicclassTestSupplier{publicstaticvoidmain(String[] args){
Supplier<String> supplier = () -> "egg";
String result = supplier.get();
System.out.println(result);
}
}
Predicate<T>: 接受一个输入并且返回一个布尔值结果
publicclassTestPredicate{publicstaticvoidmain(String[] args){
Predicate<String> predicate = (s) -> s.equalsIgnoreCase("egg");
System.out.println(predicate.test("egg"));
System.out.println(predicate.test("agg"));
}
}
Stream 中的函数式接口
筛选偶数的例子使用到了 Stream 的 filter 方法,其实他还定义了很多方法来进行各种流操作,来看看 Stream 中一些常用方法的定义:
publicinterfaceStream<T> {voidforEach(Consumer<? super T> action);
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
Stream<T> filter(Predicate<? super T> predicate);
Optional<T> min(Comparator<? super T> comparator);
...
}
方法比较多,不一一列举。可以看到 lambda 表达式大量使用函数式接口作为参数。而这些接口很多都使用了常用的函数式接口。
总结
匿名内部类是为了简化代码,Lambda 表达式是为了进一步简化代码,函数式接口是为了 Lambada 表达式而生。
以上是 Lambda 表达式与函数式接口 的全部内容, 来源链接: utcz.com/a/36256.html