Lambda表达式与函数式接口基础学习
1. 概念一:Lambda表达式的操作符和基本结构
操作符: ->
基本结构:参数列表 ->
Lambda体(需要执行的功能)
2. 概念二:函数式接口
函数式接口是指只有一个抽象方法的接口;
Lambda表达式需要函数式接口的支持;
自定义的函数式接口可以加上注解 @FunctionalInterface
,用于编译器检查是否是函数式接口。
Lambda表达式的参数列表对应一个函数式接口的抽象方法的参数列表,Lambda体对应了该抽象方法的实现。
3. Lambda表达式的语法格式
第一种:无参数,无返回值
例如:() -> System.out.println("hello");
无参数,无返回值的Lambda表达式在使用中也可以结合一个有无参数,无返回值的抽象方法的函数式接口使用,如Runnable
接口的run()
(Runnable
是一个函数式接口),示例代码如下:
@Test
public void test01(){
// 原来使用匿名内部类方式
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
};
// Lambda表达式方式
Runnable r2 = () -> System.out.println("hello");
}
可以看到,在Java8以前,要实现一个Runnable
接口,在仅仅只有一条需要执行的语句的情况下也需要通过匿名内部类的方式多写好几行与执行功能无关的代码。而Java8之后的Lambda表达式则只关注具体的执行功能,简化了开发。由于run()
是没有参数也没有返回值的抽象方法,所以Lambda表达式的参数列表为空,但是()
不能省略,而Lambda体中由于只有一条语句,所以可以直接写,但是如果超过了一条语句,则需要加上{}
。
第二种:有一个参数,无返回值
例如:
(x) -> System.out.println(x);
x -> System.out.println(x); // 只有一个参数时可以不写括号,但是建议写上
Lambda表达式的参数可以自己定义,不需要加类型修饰符,因为编译器会通过代码的上下文推断出该参数的类型。示例代码如下:
@Test
public void test02(){
Consumer<String> c = (x) -> System.out.println(x + ",out");
c.accept("hhhh");
}
Consumer
接口也是一个函数式接口,它只有accept()
这一个抽象方法,该方法有一个参数并且没有返回值。
第三种:有多个参数,有返回值
Comparator<T>
接口是一个比较器,也是一个函数式接口,它的抽象方法int compare(T o1, T o2);
有两个参数并且提供返回值,示例代码如下:
@Test
public void test03(){
// 匿名内部类方式
Comparator<Integer> com1 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
System.out.println("函数式接口");
return Integer.compare(o1, o2);
}
};
// Lambda表达式方式
Comparator<Integer> com2 = (x, y) -> {
System.out.println("函数式接口");
return Integer.compare(x, y);
};
}
第四种:当Lambda体中只有一条语句时,return和{}都可以省略
还是用上一个例子:
@Test
public void test04(){
Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
}
由于该接口的抽象方法是有返回值的,而Lambda体又只有一条语句:return Integer.compare(x, y);
所以可以省略return
。
4. Java内置的四大核心函数式接口
因为Lambda表达式需要函数式接口的支持,所以当我们使用Lambda表达式时也需要对应的函数式接口,但是每次都自己定义接口会比较麻烦,所以Java为我们提供了一些内置的函数式接口,这些函数式接口基本能够满足我们大部分的需求,其中最主要的有四个:
第一个:Consumer<T>:
消费型接口
抽象方法:void accept(T t);
使用示例:
@Test
public void test01(){
enjoy(125.5, (x) -> System.out.println("用餐消费:" + x));
enjoy(255.0, (x) -> System.out.println("住宿消费:" + x));
enjoy(505.5, (x) -> System.out.println("车票消费:" + x));
}
private void enjoy(double money, Consumer<Double> consumer){
consumer.accept(money);
}
消费型接口的含义是类似于消费者一样,只接收一个参数,内部进行处理,没有返回值。这里的enjoy
方法接收两个参数,一个是金额,一个是函数式接口Consumer
,这样做的目的是为了防止代码冗余,试想如果每个不同的项目的消费情况都需要一个独立的方法来完成那么在这个例子中就得为用餐消费、住宿消费、车票消费分别写三个差不多的方法。而有了Lambda表达式和函数式接口就可以简化代码了,当我们需要输出不同的项目消费情况时只需在调用enjoy
方法时传递不同的Lambda表达式即可。
第二个:Supplier<T>
: 供给型接口
抽象方法:T get();
使用示例:
@Test
public void test02(){
System.out.println(generateString(() -> UUID.randomUUID().toString()));
}
private String generateString(Supplier<String> supplier){
return supplier.get();
}
供给型接口的含义是有返回值,没有参数。示例中的generateString
方法用于得到一个随机的字符串,它接收一个函数式接口Supplier
,Lambda表达式中的UUID.randomUUID().toString()
用于生成随机的字符串,由于只有一条语句,所以return
可以省略。
第三个:Function<T, R>
: 函数型接口
抽象方法:R apply(T t);
使用示例:
@Test
public void test03(){
String str = "sdfasdgbas";
System.out.println(stringHandler(str, (s) -> str.toUpperCase()));
System.out.println(stringHandler(str, (s) -> str.substring(0, str.length() - 1)));
}
private String stringHandler(String str, Function<String, String> function){
return function.apply(str);
}
函数型接口的含义是提供一个参数,经过处理后返回一个值。stringHandler
方法是一个字符串处理方法,它并没有在方法体中给出具体处理的代码,而是通过接收一个函数式接口Function
,来调用其apply
方法进行处理并返回值。这种思想和第一个示例一样,如果每种不同的处理方式(如:将字符串变成大写或小写,截取字符串等)都需要一个不同的方法来实现就会很麻烦。通过传递Lambda表达式的方式就会更简单。
第四个:predicate<T>
: 断言型接口
抽象方法:boolean test(T t);
使用示例:
/**
* predicate<T>: 断言型接口测试
* 将满足条件的字符串放入集合中
*/
@Test
public void test04(){
List<String> strings = new ArrayList<>(Arrays.asList(
"hhhh",
"ooo",
"egasgaasg",
"asodiang",
"waogaweogaweog"
));
System.out.println(filterStrings(strings, (s) -> s.length() > 5));
}
private List<String> filterStrings(List<String> strings, Predicate<String> predicate){
List<String> result = new ArrayList<>(strings.size());
for (String str: strings) {
if(predicate.test(str)){
result.add(str);
}
}
return result;
}
断言型接口的含义是接收一个参数,判断其是否符合某个条件,返回一个boolean
值。在本示例中,filterStrings
是一个过滤List
集合中的字符串的方法,要求筛选出符合条件的字符串并返回。同样的,筛选条件并没有直接的写在方法体中,而是通过接收一个断言型接口Predicate
调用其test方法来进行判断,在调用filterStrings
方法时就可以通过Lambda表达式写出需要的筛选条件。
其他函数式接口
当我们实际使用的时候会发现,有时候我需要传递两个参数或者三个参数该怎么办?这四大函数式接口不能满足这个需求,需要自己定义吗?不需要,因为这四个是最核心的函数式接口,其实Java还提供了从这四个扩展来的更多的函数式接口以满足需求,具体可以去查阅文档。
函数式接口和Lambda表达式的基本用法就到这里了,后面还有一篇关于Java8的方法引用的文章,也是Lambda表达式相关的内容。