**
最近上了项目,发现对java8的新特性理解不到位。遂整理了一下关于java8函数式接口的知识。
了解函数式接口,个人的学习的路线为:
匿名内部类 -> lambda表达式 -> 四大函数式接口
- 匿名内部类
匿名内部类可以避免继承和实现的时候多写一个单独的class, 在实例化的时候直接通过匿名内部类重写接口后者父类的方法。比如:
public abstract class Preson{
void eat();
}
public class Child implements Person{
@override
void eat(){
System.out.println("吃奶");
}
}
public class Test{
public static void main(String[] args) {
Person person = new Child();
person.eat();
}
}
这里返回的就是eat。但是通过匿名内部类,可以避免再去定义一个临时的class child:
Interface Person{
void eat();
}
public class Test{
Person person = new Person{
void eat(){
System.out.println("吃奶");
}
}
person.eat();
}
就避免了重新去生成一个类来实现或者继承。
-
lambda表达式
lambda表达式更加简化了这种匿名内部类的操作,直接使用->就完成了把方法当作参数传递给参数。
lambda表达式有4个重要特性:(1). 只有一个参数时,括号可有可无:
x -> x + x;
(2). 可以不用打括号,不用写return
(x, y) -> x + y;
(3). 打了括号以后,就一定要有return
(x, y)-> { return x+y; }
(4). 参数类型是可选的。
lambda在匿名内部类的基础上更加简化了代码,并且增加了代码的可读性。 -
函数式接口
函数式接口是有且仅有一个抽象方法的接口,是可以自定义的,用@FunctionalInterface。
函数式接口的意思和目的,个人的理解为:**把函数当作参数进行传递。**这里参考了这篇博客:
https://juejin.cn/post/6844903892166148110
本篇笔记主要是为了加深自己对java8的四大函数式接口的理解而做的笔记。之前在项目上,在application层做排序和筛选的时候,当时刚上手,不太理解,代码体写的十分混乱。当时被要求用stream来做过滤和排序的时候脑子是昏的,下来以后看了函数式接口后,豁然开朗,明白了以前别人提出的很多思路以及自己确实不该犯的重构错误。下面切入正题,记录一下四大函数式接口:
(1).Consumer <T> 消费型函数式接口
消费型接口的特征是:有输入,无输出。因为是消费者,就是拿来用的。
调用该接口内部使用accept(T t)。
Consumer<String> consumer1 = str -> System.out.print("车名:" + str.split(",")[0]);
Consumer<String> consumer2 = str -> System.out.println("-->颜色:" + str.split(",")[1]);
String[] strings = {"奥迪,黑色", "五菱宏光,白色"};
for (String string: strings) {
consumer1.andThen(consumer2).accept(string);
}
内部有两方法:
accept()
andThen(Consumer<T t> after)
该方法会先调用accept(),再调用after.accept();
看到这可能会想,刚才不是才说函数式接口只能有一个方法吗?
为什么这里有两个方法。因为andThen这个方法使用default修饰的,java8允许在函数式接口内部为方法加上default修饰符,在调用的时候不用去实现default方法。被default修饰的方法可以有默认的方法体。
这类消费型接口接口很适合用来配合做过滤,或者做值的计算。stream的peek方法接受的参数就是一个Consumer Interface。举个例子,当的业务需求是计算旗下超市上个月销售量增长率超过百分之10的门店,只需要在mapper层查出上个月和上上个月的订单,假设在返回的对象内部定义一个计算月增长率Ratio的成员属性和内部方法CalculateRatio,这时候在stream中peek(对象::CalculateRatio)可以在业务层计算月的增长率,提高代码的可读性,为之后过滤做准备。
(2). Supplier 供给者函数式接口
无传参,有返回。这是和消费型接口相反的函数式接口。
Supplier<String> supplier = () -> "呵呵哈!";
System.out.println(supplier.get());
该接口内部仅有一个方法:
get()
目前在项目上还没有遇到供给型接口的应用场景。暂时知道有这么个玩意儿就可以了。
(3). Function(T t) 功能型函数式接口
有传参,有返回。“功能型接口”,听名字就知道这个玩意儿十分的重要。个人而言,这玩意儿的应用是最多的!
public static void main(String[] args) {
//有传参,有返回
Function<Integer, Integer> function1 = it -> it * it;
Function<Integer, Integer> function2 = it -> it + it;
Function<Integer, Integer> function3 = Function.identity();
System.out.println(function1.apply(3));
System.out.println(function1.compose(function2).apply(3));
System.out.println(function1.andThen(function2).apply(3));
System.out.println(function3.apply(3));
System.out.println("----------------------------------------------");
List<Integer> integerList = createList();
System.out.println(integerList.stream().map(function1).collect(Collectors.toList()));
}
private static List<Integer> createList() {
List<Integer> integerList = new ArrayList<>();
for (int i = 1; i <= 5; i++) {
integerList.add(i);
}
return integerList;
}
输出的结果为:
9
36
18
3
[1, 4, 9, 16, 25]
该函数式接口内有4个方法:
apply
andThen( Function <T t> after)
和Consumer一样,先执行apply,再执行after.apply
compose( Function <T t> before)
和andthen相反,先执行before.apply,再执行apply
identity
返回input
该类型的应用很常见。比如:在复用一个查询的时候,通过stream的map(map的传参是一个function)可以根据get方法来获取想要的数据的List。最开始觉得identity()的应用场景不多,后来发现,在collect(toMap(**, **))的时候,可以传入identity,构建一个id和对象一一对应的map。
(4). Predicate(T t) 断言型函数式接口
有传参,返回一个布尔值。
因为Predicate的方法比较多,这里先列一下方法,下面再说应用。该接口内部有5个方法:
boolean test(T t)
and(Predicate<T t>) other
作用和&&相同
or(Predicate<T t> other)
作用和||相同
nagete
作用为取反
isEqual(Object obj)
判断是否相等,和Obejects.equals(obj1, obj2)作用一样
Predicate<Integer> predicate1 = it -> it > 0;
Predicate<Integer> predicate2 = it -> it < 20;
System.out.println(predicate1.test(21));
System.out.println(predicate1.and(predicate2).test(21));
System.out.println(predicate1.or(predicate2).test(21));
System.out.println(predicate1.negate().test(21));
System.out.println(predicate2.negate().test(21));
System.out.println(Predicate.isEqual(21).test(21));
断言型接口最常用的一个业务场景就是stream的filter了,该方法接受的参数为Predicate函数式接口。结合Consumer中提到的超市的例子,通过peek计算出每个月的增长量后,可以用filter来过滤出每个月增长率Ratio超过百分之10的超市门店。(一般filter能先用就先用,这和Stream PipeLine中的中间操作有关,filter可以减少后面的方法执行的次数。)
关于函数式接口的个人总结就到这里了,希望对大家有帮助。如果之前有两天能拿出来充电,补习一下就好了,最近反思了一下项目上的同事给的意见,确实之前犯的一些java8新特性的问题是可以很快就避免的,可惜。在这个过程中也总结出来一条经验:写代码,看代码,要的是那份冷静,所谓欲速则不达,可以很好的体现在程序员初学者身上,要冷静,不能急躁。