Lambda 表达式,即函数式编程是 JDK8 的一个新特性,也被称为闭包,Lambda表达式允许把函数作为一个方法的参数,即行为参数化,函数作为参数传递进方法中。
Lambda表达式可以取代大部分的匿名内部类,写出更优雅的 Java 代码,尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构。
总结就是:使用不可变值与函数,函数对不可变值进行处理,映射成另一个值。
一、什么是函数式接口
函数接口是只有一个抽象方法的接口,用作 Lambda 表达式的类型。使用@FunctionalInterface注解修饰的类,编译器会检测该类是否只有一个抽象方法或接口,否则,会报错。可以有多个默认方法,静态方法。
lambda 表达式是一小段代码,它接受参数并返回一个值。Lambda 表达式类似于方法,但它们不需要名称,并且可以直接在方法体中实现。
语法:最简单的 lambda 表达式包含一个参数和一个表达式:
零参数:
() -> System.out.println("零参数 lambda");
一个参数:
p -> System.out.println("一个参数:" + param);
多个参数:
(p1 [,p2,p3,....pn]) -> System.out.println("多个参数:" + p1 + ", " + p2 + ... + pn);
上面的表达式有一定的限制。它们要么返回一个值要么执行一段方法,并且它们不能包含变量、赋值或语句,例如if or for 。为了进行更复杂的操作,可以使用带有花括号的代码块。如果 lambda 表达式需要返回一个值,那么代码块应该有一个return语句。
(parameter1, parameter2) -> { code block [return] }
1、方法引用
类 :: 静态方法
Consumer<String> c = [ (s) -> System.out.println(s); <=> System.out::println; ]
对象 :: 实例方法
List<String> list = Lists.newArrayList();
Consumer<String> c = [ (e) => list.add(e); <=> list::add; ]
构造器 :: new
Supplier<List<String>> s = [ () -> new ArrayList<>(); <=> ArrayList::new; ]
2、@FunctionalInterface注解
有且只有一个抽象方法的接口被称为函数式接口,函数式接口适用于函数式编程的场景,Lambda就是Java中函数式编程的体现,可以使用Lambda表达式创建一个函数式接口的对象,一定要确保接口中有且只有一个抽象方法,这样Lambda才能顺利的进行推导。
与@Override 注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解:@FunctionalInterface 。该注解可用于一个接口的定义上,一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法(equal和hashcode方法不算),否则将会报错。但是这个注解不是必须的,只要符合函数式接口的定义,那么这个接口就是函数式接口。
3、 java8自带的常用函数式接口。
4、简单的例子
package com.biyu.study.lambda;
import com.biyu.study.lambda.bean.Employee;
import java.math.BigDecimal;
import java.util.function.*;
public class Test {
public static void main(String[] args) {
Predicate<Integer> predicate = x -> x > 20;
Employee employee = new Employee("Lily", "女", 18);
System.out.println("Lily的大于18岁吗?:" + predicate.test(employee.getAge()));
Consumer<String> consumer = System.out::println;
consumer.accept("Hello World");
Function<Employee, String> function = Employee::getName;
String name = function.apply(employee);
System.out.println(name);
Supplier<Integer> supplier = () -> Integer.valueOf(BigDecimal.TEN.toString());
System.out.println(supplier.get());
UnaryOperator<Boolean> unaryOperator = uglily -> !uglily;
Boolean apply2 = unaryOperator.apply(true);
System.out.println(apply2);
BinaryOperator<Integer> operator = (x, y) -> x * y;
Integer integer = operator.apply(2, 3);
System.out.println(integer);
test(() -> "函数式接口");
}
/**
* 自定义函数式接口使用
*
* @param worker
*/
public static void test(Worker worker) {
String work = worker.work();
System.out.println(work);
}
public interface Worker {
String work();
}
}
以上演示了lambda接口的使用及自定义一个函数式接口并使用。
二、Lambda 流的常用方法
1、ForEach
集合的遍历 forEach 方法:
public static void testForEach(){
List<String> list = new ArrayList<String>() {{
add("1");
add("2");
add("3");
}};
list.forEach(s-> System.out.println(s));
}
2、Collect
将操作后的对象转化为新的对象:
public static void testCollect(){
List<String> list = new ArrayList<String>() {{
add("1");
add("2");
add("2");
}};
//转换为新的list
List newList = list.stream().map(s -> Integer.valueOf(s)).collect(Collectors.toList());
System.out.println(newList);
}
3、Filter
Filter 为过滤的意思,只要满足 Filter 表达式的数据就可以留下来,不满足的数据被过滤掉。
public static void testFilter() {
List<String> list = new ArrayList<String>() {{
add("1");
add("2");
add("3");
}};
List<String> filtered = list.stream()
// 过滤掉我们希望留下来的值
// 表示我们希望字符串是 1 能留下来
// 其他的过滤掉
.filter(str -> "1".equals(str))
.collect(Collectors.toList());
System.out.println(filtered);
}
4、Map
map 方法可以让我们进行一些流的转化,比如原来流中的元素是 A,通过 map 操作,可以使返回的流中的元素是 B。
public static void testMap() {
List<String> list = new ArrayList<String>() {{
add("A");
add("B");
add("C");
}};
//通过 map 方法list中元素转化成 小写
List<String> strLowerList = list.stream()
.map(str -> str.toLowerCase())
.collect(Collectors.toList());
System.out.println(strLowerList);
}
5、MapToInt
mapToInt 方法的功能和 map 方法一样,只不过 mapToInt 返回的结果已经没有泛型,已经明确是 int 类型的流了。
public static void testMapToInt() {
List<String> list = new ArrayList<String>() {{
add("1");
add("2");
add("3");
}};
List<Integer> intList=list.stream()
.mapToInt(s->Integer.valueOf(s))
// 一定要有 mapToObj,因为 mapToInt 返回的是 IntStream,因为已经确定是 int 类型了,所以没有泛型的,
// 而 Collectors.toList() 强制要求有泛型的流,所以需要使用 mapToObj方法返回有泛型的流
.mapToObj(s->s)
.collect(Collectors.toList());
System.out.println(intList);
Double sum = list.stream()
.mapToDouble(s->Double.valueOf(s))
// DoubleStream/IntStream 有许多 sum(求和)、min(求最小值)、max(求最大值)、average(求平均值)等方法
.sum();
System.out.println(sum);
}
6、Distinct
distinct 方法有去重的功能:
public static void testDistinct() {
List<String> list = new ArrayList<String>() {{
add("1");
add("2");
add("2");
add("5");
add("3");
}};
List<Integer> intList = list.stream()
.map(s -> Integer.valueOf(s))
.distinct()
.collect(Collectors.toList());
System.out.println(intList);
}
7、Sorted
Sorted 方法提供了排序的功能,并且允许我们自定义排序。
public static void testSorted() {
List<String> list = new ArrayList<String>() {{
add("1");
add("2");
add("-1");
add("9");
add("11");
add("27");
add("0");
}};
List<Integer> intList = list.stream()
.map(s -> Integer.valueOf(s))
// 等同于 .sorted(Comparator.naturalOrder()) 自然排序
.sorted()
.collect(Collectors.toList());
System.out.println(intList);
// 自定义排序器
intList =list.stream()
.map(s -> Integer.valueOf(s))
// 反自然排序
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
System.out.println(intList);
}
8、groupingBy
groupingBy 是能够根据字段进行分组,toMap 是把 List 的数据格式转化成 Map 的格式。
public static void testGroupBy() {
List<String> list = new ArrayList<String>() {{
add("iphone 4s");
add("iphone 6");
add("iphone 12");
add("iphone 14");
add("xiaomi");
add("huawei");
add("oppo");
}};
Map<String, List<String>> strList = list.stream().collect(Collectors.groupingBy(s -> {
if (s.startsWith("iphone")){
return "iphone";
} else {
return "other";
}
}));
System.out.println(strList);
}
9、FindFirst
findFirst 表示匹配到第一个满足条件的值就返回:
public static void testFindFirst() {
List<String> list = new ArrayList<String>() {{
add("1");
add("2");
add("2");
}};
list.stream()
.filter(s -> "2".equals(s))
.findFirst()
.get();
// 防止空指针
list.stream()
.filter(s -> "2".equals(s))
.findFirst()
// orElse 表示如果 findFirst 返回 null 的话,就返回 orElse 里的内容
.orElse("3");
Optional<String> str = list.stream()
.filter(s -> "2".equals(s))
.findFirst();
// isPresent 为 true 的话,表示 value != null
if (str.isPresent()) {
return;
}
}
10、Reduce
reduce 方法允许我们在循环里面叠加计算值:
public static void testReduce() {
List<String> list = new ArrayList<String>() {{
add("1");
add("2");
add("3");
}};
int sum = list.stream()
.map(s -> Integer.valueOf(s))
// s1 和 s2 表示循环中的前后两个数
.reduce((s1, s2) -> s1 + s2)
.orElse(0);
System.out.println(sum);
sum =list.stream()
.map(s -> Integer.valueOf(s))
// 第一个参数表示基数,会从 100 开始加
.reduce(100, (s1, s2) -> s1 + s2);
System.out.println(sum);
}
11、Peek
peek 方法很简单,我们在 peek 方法里面做任意没有返回值的事情,比如打印日志:
public static void testPeek() {
List<String> list = new ArrayList<String>() {{
add("1");
add("2");
add("3");
}};
list.stream().map(s -> Integer.valueOf(s))
.peek(s -> System.out.println(s))
.collect(Collectors.toList());
}
12、Limit
limit 方法会限制输出值个数,入参是限制的个数大小:
public static void testLimit() {
List<String> list = new ArrayList<String>() {{
add("1");
add("2");
add("3");
add("4");
add("5");
}};
List<Integer> integers= list.stream()
.map(s -> Integer.valueOf(s))
.limit(3L)
.collect(Collectors.toList());
System.out.println(integers);
}
13、Max,Min
通过 max、min 方法,可以获取集合中最大、最小的对象。
public static void testMaxMin() {
List<String> list = new ArrayList<String>() {{
add("1");
add("3");
add("2");
add("-12");
add("99");
}};
String maxNum = list.stream().max(Comparator.comparing(s -> Integer.valueOf(s))).get();
String minNum = list.stream().min(Comparator.comparing(s -> Integer.valueOf(s))).get();
System.out.println(maxNum);
System.out.println(minNum);
}
三、写在最后
本章节主要从实际使用讲述了常用的方法及流,文中例子主要是为了讲解较为简单,大家可以去使用java8重构自己现有的代码,自行领会lambda的奥妙使用。java8可以很清晰表达你要做什么,代码也很简洁。