JDK8新特性:Lambda表达式
概述
- lambda表达式可以说是实现SAM的语法糖,使得Java编程更加高级,省力
- lambda表达式可以大大减少代码的冗余
使用规则
-
接口需要有且仅有一个需要实现的方法
-
使用方法,以创建新的线程为例
// 例1 // jdk8 之前 Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("创建线程。。。"); } }); thread.start(); // jdk8 之后,使用lambda表达式 Thread thread = new Thread(() -> System.out.println("创建线程。。。")); thread.start(); // 例2 // jdk8 之前 List<String> list = Arrays.asList("b", "s", "f", "w", "a", "c", "y"); Collections.sort(list, new Comparator<String>() { @Override public int compare(String o1, String o2) { return o1.compareTo(o2); } }); // jdk8 之后,使用lambda表达式 List<String> list = Arrays.asList("b", "s", "f", "w", "a", "c", "y"); Collections.sort(list, (o1, o2) -> o1.compareTo(o2)); /*或者*/ Collections.sort(list, Comparable::compareTo); -
lambda表达式配合stream API可以更加高效的实现业务代码
/*功能:将list中的所有元素小写*/ List<String> list = Arrays.asList("Hello", "ArE", "yOU", "DOing"); // jdk 8之前 List<String> lowercase = new ArrayList<>(); for (int i = 0; i < list.size(); i++) { lowercase.add(list.get(i).toLowerCase()); } // jdk8 之后,使用lambda表达式 List<String> lowerList = list.stream().map(param -> param.toLowerCase()).collect(Collectors.toList()); // 或者 List<String> lowerList = list.stream().map(String::toLowerCase).collect(Collectors.toList());
lambda表达语法
-
一般语法
- 参数使用小括号
- 使用箭头指向实现的代码体
- 有返回值时最后直接return即🉑️。
(Type1 param1, Type2 param2, ..., TypeN paramN) -> { statment1; statment2; //............. return statmentM; } -
单参数语法
- 单个参数的情况下,可以省略参数的小括号
param1 -> { statment1; statment2; //............. return statmentM; } -
单语句语法
- 如果方法体只有一条语句,可以省略大括号
- 如果有返回值,还可以省略return关键字
param1 -> statment // 等同于 param1 -> return statment; // 等同于 param1 -> {return statment;} -
方法引用写法
-
使用类名/对象实例 + "::"符号 + 方法名 的方式充当接口某方法的实现
Class or instance :: method
-
Lambda表达式可用变量范围
-
lambda表达式外部的变量,内部的变量和传入的参数变量
// outSider为外部变量,uuid是内部变量,param 是传递的变量 String outSider = "outSider"; List<String> list = Arrays.asList("How", "ArE", "yOU", "DOing"); List<String> lowerList = list.stream().map(param -> { String uuid = UUID.randomUUID().toString(); return outSider + " --- " + param.toLowerCase() + " --- " + uuid; }).collect(Collectors.toList()); -
⚠️注意:使用外部变量时,编译器会为外部变量自动加了final修饰,不能修改其值
-
因为加了final,如果希望修改,则需要使用对象引用的方式
AtomicReference<String> outSider = new AtomicReference<>("outSider");
lambda表达式中this的概念
-
lambda表达式中的this并不指向其产生的SAM对象,而是指向外部的对象
public class TestClazz { @Test public void test2() { String outSider = "outSider"; List<String> list = Arrays.asList("How", "ArE", "yOU", "DOing"); List<String> lowerList = list.stream().map(param -> { System.out.println("==>" + this.getClass().getName()); return param.toLowerCase(); }).collect(Collectors.toList()); } }输出: ==>com.test.TestClazz ==>com.test.TestClazz ==>com.test.TestClazz ==>com.test.TestClazz
方法引用和构造器引用
-
方法引用
-
objectName::instanceMethod 对象实例::方法,将lambda表达式的参数作为方法的参数传入其中
-
ClassName::staticMethod 类名::静态方法,同样是将lambda表达式的参数作为方法的参数传入其中
-
ClassName::instanceMethod 类名::方法,将lambda表达式的第一个参数作为调用方,调用后面的instanceMethod,其余参数作为instanceMethod方法的参数
第一种:System.out::println 等同于 x->System.out.println(x)
第二种:Math::max 等同于 (x, y)->Math.max(x,y)
第三种:String::toLowerCase 等同于 x->x.toLowerCase()
-
-
构造器引用
-
用法:ClassName::new
-
将lambda表达式的参数作为构造器的参数,构造出新的对象
eg:BigDecimal::new 等同于 x->new BigDecimal(x)
-
Stream API
Stream的理解/特点
(1) Stream是元素的集合,类似于Iterator,使用这个集合可以进行多种Iterator无法进行的操作
(2) 支持顺序和并行的对原Stream进行汇聚的操作
public class TestClazz {
@Test
public void test3() {
List<Integer> nums = Lists.newArrayList(1,null,3,4,null,6);
long count = nums.stream().filter(num -> num != null).count();
// Integer count = nums.stream().filter(Objects::nonNull).max(Comparable::compareTo).get();
System.out.println(count);
}
}
解释:图片就是对于Stream例子的一个解析,可以很清楚的看见:原本一条语句被三种颜色的框分割成了三个部分。红色框中的语句是一个Stream的生命开始的地方,负责创建一个Stream实例;绿色框中的语句是赋予Stream灵魂的地方,把一个Stream转换成另外一个Stream,红框的语句生成的是一个包含所有nums变量的Stream,进过绿框的filter方法以后,重新生成了一个过滤掉原nums列表所有null以后的Stream;蓝色框中的语句是丰收的地方,把Stream的里面包含的内容按照某种算法来汇聚成一个值,例子中是获取Stream中包含的元素个数。
使用stream的步骤
- 创建Stream;
- 转换Stream,每次转换原有Stream对象不改变,返回一个新的Stream对象(可以有多次转换);
- 对Stream进行聚合(Reduce)操作,获取想要的结果;
创建Stream的方式(两种)
-
通过使用Stream接口的静态方法:of(…)等 [注意:Java8里接口可以带静态方法]
- of()方法 [两个重载的方法,一个接受一个参数,一个接受可变参数]
// 声明 public static<T> Stream<T> of(T t); public static<T> Stream<T> of(T... values) // 使用 Stream<Integer> integerStream = Stream.of(1, 2, 3, 5); Stream<String> stringStream = Stream.of("taobao");- generate()方法,创建一个无限长度的stream,所以一般配合limit使用
// 声明 public static<T> Stream<T> generate(Supplier<T> s) { Objects.requireNonNull(s); return StreamSupport.stream( new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false); } // 使用 Stream.generate(new Supplier<Double>() { @Override public Double get() { return Math.random(); } }).limit(20L).forEach(System.out::println); // 或者 Stream.generate(() -> Math.random()).limit(20L).forEach(System.out::println); // 或者 Stream.generate(Math::random).limit(20L).forEach(System.out::println); // 以上三种表达方法一样的- iterate() 方法,也是生成无限长度的stream,但与generate方法不一样的是,其元素的生成是重复的利用给定的种子值(seed)调用指定的函数生成的,就是将seed代入到函数并得到结果,然后再将结果代入到函数中得出结果,如此往复。
// 声明 public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f); // 使用 Stream.iterate(2, param -> 2 * param).limit(10).forEach(param -> System.out.print(param + " ")); // 结果:2 4 8 16 32 64 128 256 512 1024-
其他生成Stream的方法:
(1)生成空的Stream public static Stream empty()
(2)将两个Stream连接后返回 Stream.concat(Stream.empty(), Stream.generate(Math::random).limit(20L));
-
通过Collection接口的默认方法创建:stream()
- Collection接口实现类的实例对象调用父接口的默认实现stream()方法即可返回对应的Stream对象
Stream<Integer> stream = Lists.newArrayList(1,2,3,4,6).stream();
Stream的集中常用方法
-
distinct: 对于Stream中包含的元素进行去重操作(去重逻辑依赖元素的equals方法),新生成的Stream中没有重复的元素;(自定义类型是根据对象的hashCode和equals来去除重复元素的。)

-
filter: 对于Stream中包含的元素使用给定的过滤函数进行过滤操作,新生成的Stream只包含符合条件的元素

-
map: 对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素。这个方法有三个对于原始类型的变种方法,分别是:mapToInt,mapToLong和mapToDouble。这三个方法也比较好理解,比如mapToInt就是把原始Stream转换成一个新的Stream,这个新生成的Stream中的元素都是int类型。之所以会有这样三个变种方法,可以免除自动装箱/拆箱的额外消耗;

-
flatMap:和map类似,不同的是其每个元素转换得到的是Stream对象,会把子Stream中的元素压缩到父集合中;

// flatMap 把 input Stream 中的层级结构扁平化,就是将最底层元素抽出来放到一起,最终 output 的新 Stream 里面已经没有 List 了,都是直接的数字
// flatMap给一段代码理解:
Stream<List<Integer>> inputStream = Stream.of(
Arrays.asList(1),
Arrays.asList(2, 3),
Arrays.asList(4, 5, 6)
);
Stream<Integer> outputStream = inputStream.
flatMap((childList) -> childList.stream());
-
peek: 生成一个包含原Stream的所有元素的新Stream,同时会提供一个消费函数(Consumer实例),新Stream每个元素被消费的时候都会执行给定的消费函数;

-
limit: 对一个Stream进行截断操作,获取其前N个元素,如果原Stream中包含的元素个数小于N,那就获取其所有的元素;

-
skip: 返回一个丢弃原Stream的前N个元素后剩下元素组成的新Stream,如果原Stream中包含的元素个数小于N,那么返回空Stream;

-
整体调用例子
List<Integer> nums = Lists.newArrayList(1,1,null,2,3,4,null,5,6,7,8,9,10);
int sum = nums.stream().filter(Objects::nonNull).distinct().mapToInt(num -> num * 2).peek(System.out::println).skip(2).limit(4).sum();
System.out.println(“sum is:” + sum);
说明:
这段代码演示了上面介绍的所有转换方法(除了flatMap),简单解释一下这段代码的含义:给定一个Integer类型的List,获取其对应的Stream对象,然后进行过滤掉null,再去重,再每个元素乘以2,再每个元素被消费的时候打印自身,在跳过前两个元素,最后去前四个元素进行加和运算(解释一大堆,很像废话,因为基本看了方法名就知道要做什么了。这个就是声明式编程的一大好处!)。大家可以参考上面对于每个方法的解释,看看最终的输出是什么。
2
4
6
8
10
12
sum is:36
针对多次转换的说明:
可能会有这样的疑问:在对于一个Stream进行多次转换操作,每次都对Stream的每个元素进行转换,而且是执行多次,这样时间复杂度就是一个for循环里把所有操作都做掉的N(转换的次数)倍啊。其实不是这样的,转换操作都是lazy的,多个转换操作只会在汇聚操作(见下节)的时候融合起来,一次循环完成。我们可以这样简单的理解,Stream里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在汇聚操作的时候循环Stream对应的集合,然后对每个元素执行所有的函数。
Stream方法总结
| 方法名 | 方法作用 | 返回值类型 | 方法种类 |
|---|---|---|---|
| count | 统计个数 | long | 终结 |
| forEach | 逐一处理 | void | 终结 |
| collect | 转化stream成为集合 | List/Map/… | 终结 |
| limit | 取用前几个 | Stream | 函数拼接 |
| skip | 跳过前几个 | Stream | 函数拼接 |
| map | 映射 | Stream | 函数拼接 |
| concat | 组合 | Stream | 函数拼接 |
| distinct | 去重 | Stream | 函数拼接 |
| peek | 添加额外处理函数 | Stream | 函数拼接 |
| sorted | 排序 | Stream | 函数拼接 |
| filter | 过滤 | Stream | 函数拼接 |
sorted 排序 Stream 函数拼接
Stream的3个注意事项:
-
Stream只能操作一次,因为操作一次就会返回一个新的Stream
-
Stream方法返回的是新的流
-
Stream不调用终结方法,中间的操作不会执行
汇聚(Reduce)Stream
概述
-
简介:汇聚操作(也称为折叠)是接收一个数据序列为输入,反复使用合并操作,把序列中的元素合并成一个汇总的结果。Stream接口有一些通用的汇聚操作,比如reduce()和collect();也有一些特定用途的汇聚操作,比如sum(),max()和count()。注意:sum方法不是所有的Stream对象都有的,只有IntStream、LongStream和DoubleStream是实例才有。
例如
查找一个数字列表的总和或者最大值,或者把这些数字累积成一个List对象。
-
分类
**可变汇聚:**把输入的元素们累积到一个可变的容器中,比如Collection或者StringBuilder;
**其他汇聚:**除去可变汇聚剩下的,一般都不是通过反复修改某个可变对象,而是通过把前一次的汇聚结果当成下一次的入参,反复如此。比如reduce,count,allMatch;
可变汇聚
- 可变汇聚只有一个方法:collect,它可以把Stream中需要的元素收集到一个容器中。比如,Collection
// 重载方法1
<R, A> R collect(Collector<? super T, A, R> collector);
// 使用范例
List<Integer> numsWithoutNull = nums.stream().filter(num -> num != null).collect(Collectors.toList());
参考链接:Collectors 参考地址
// 重载方法2
/* @param supplier 一个工厂函数,用来生成一个新的容器.
* @param accumulator 一种添加新元素的、不干涉的、无状态的函数,用于将额外的元素合并到结果中
* @param combiner 用来把中间状态的多个结果容器合并成为一个(并发的时候会用到)
*/
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
// 使用范例
List<Integer> nums = Lists.newArrayList(1, 1, null, 2, 3, 4, null, 5, 6, 7, 8, 9, 10);
List<Integer> numsWithoutNull = nums.stream().filter(num -> num != null).
collect(() -> new ArrayList<Integer>(),
(list, item) -> list.add(item),
(list1, list2) -> list1.addAll(list2));
// 另一种写法
List<Integer> numsWithoutNull = nums.stream().filter(Objects::nonNull).
collect(ArrayList::new,
ArrayList::add,
ArrayList::addAll);
参考链接 Collector 参考地址
其他汇聚
-
reduce方法:最通用的方法,count和sum都可以通过它实现
-
三种重载实现
- 参数accumulator的accept()方法的实现需要符合结合律,即 (a op b) op c = a op (b op c)
- 返回值是Optional类型的值,防止了NullPointerException,因为Optional在初始化赋值的时候进行了检验,如果是null,就抛出NPE。
Optional<T> reduce(BinaryOperator<T> accumulator); // 范例 List<Integer> ints = Lists.newArrayList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); System.out.println("ints sum is:" + ints.stream().reduce((sum, item) -> sum + item).get());
- 与上面基本一致,不同的是它允许用户提供一个循环计算的初始值,如果Stream为空,就直接返回该值。而且这个方法不会返回Optional,因为其不会出现null值 ```java T reduce(T identity, BinaryOperator<T> accumulator); // 范例 List<Integer> ints = Lists.newArrayList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); System.out.println("ints sum is:" + ints.stream().reduce(0, (sum, item) -> sum + item));<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner); -
-
count方法:获取Stream中元素的个数
-
allMatch:是不是Stream中的所有元素都满足给定的匹配条件
-
anyMatch:Stream中是否存在任何一个元素满足匹配条件
-
findFirst: 返回Stream中的第一个元素,如果Stream为空,返回空Optional
-
noneMatch:是不是Stream中的所有元素都不满足给定的匹配条件
-
max和min:使用给定的比较器(comparator),返回Stream中的最大|最小值
本文详细介绍了JDK8中的Lambda表达式及其使用规则,包括语法特点、变量范围及this概念。此外,还深入探讨了Stream API的应用,如创建方式、常用方法及汇聚操作,并举例说明。


被折叠的 条评论
为什么被折叠?



