lambda表达式和stream流作为jdk8的新特性,极大程度的提高了java对函数式编程的便捷性,同时也提供了许多集合的流式操作,使我们在编程的过程中产生极大的便利,可以称之为颠覆性的更新。
一 Lambda表达式
Lambda表达式允许java把函数作为一段代码的入参,简化了之间 java匿名内部类作为入参的写法。简单来说就是现在在调用方法的时候,你可以把一个函数当做基本类型那样作为入参对象,传入方法中。看下面示例代码。
使用匿名内部类创建线程并启动
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("用匿名内部类创建线程");
}
}).start();
使用Lambda表达式简化代码
new Thread(()->{
System.out.println("用Lambda表达式简化代码");
}).start();
你可以看到经过Lambda表达式简化后的代码,简洁了许多。那么具体使用Lambda表达式有什么要求呢?
1.1 核心原则
可推导,可省略
1.2 基本格式
(参数列表)->{代码}
1.3 使用要求
- 使用Lambda 必须要有接口,并且接口中有且仅有一个抽像方法(即只有方法声明,没有方法体的方法)
- 必须有上下文环境,才能推导出lambda对应接口
示例如下:
package com.cvdaxia.jdk8.lambda;
import java.util.function.Function;
import java.util.function.IntConsumer;
public class Lambda01 {
public static void main(String[] args) {
/******示例一,调用forechaArray方法,传入自定义函数,完成遍历*********/
foreachArray(x -> System.out.println(x));
/******示例二,调用convert方法,传入自定义函数,完成类型转换*********/
Integer num = convert(s ->Integer.valueOf(s));
System.out.println(num);
}
/*
* 自定义方法,入参为一个接口,IntConsumer,该接口内部只有一个抽象方法
*/
public static void foreachArray(IntConsumer consumer){
int[] arr = {1,2,3,4,5,6};
for (int i:arr){
//调用该接口的抽象方法
consumer.accept(i);
}
}
/*
* 自定义方法,入参为一个接口,Function,该接口内部只有一个抽象方法apply
*/
public static Integer convert(Function<String, Integer> function){
String s = "123";
return function.apply(s);
}
}
1.4 省略规则
- 参数类型可以省略
- 方法体只有一句 时,方法体的 中括号,return ,还有代码后的 **分号 **都可省略
- 方法只有一个参数 时,方法 入参列表的括号 可以省略
注意:下面的代码案例都依据这个省略规则进行了省略
1.5 四大内置核心函数式接口
基于函数式编程,系统内置提供了四大函数式接口
1.5.1 Consumer: 消费型接口,有参数无返回
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
......
}
1.5.2 Supplier: 供给型接口,无参有返回值
@FunctionalInterface
public interface Supplier<T> {
T get();
}
1.5.3 Function:函数型接口,有参数有返回值
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
......
}
1.5.4 Predicate:断言型接口,有参数返回布尔值
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
......
}
以上四大接口为顶级接口,可以看到接口中 都有且仅有一个方法 是 没有实现 的,具体的实现是需要我们自
己来完成的,当然基于这四个顶级接口也有一些子接口,感兴趣的可以用 idea 进到接口 ctrl+H 查看一下对应的层级关系
二 stream 表达式
2.1 stream的介绍
stream 表达式是 jdk8 基于 Lambda 表达式对集合操作的封装运用,它提供了很多内置操作,能够简化程序员的代码量以及可读性,下面具体来介绍一下吧。
stream 流的操作大致可以分为两大类: 中间操作、终结操作
常用中间操作的方法有:
操作 | 作用 |
---|---|
filter | 可以对流中的元素进行过滤,符合条件的才能继续接下来的流操作 |
map | 可以把流中的数据进行计算或者转换,只能把一个对象转换成另一个对象,例如String转 Int |
distinct | 可以去除流中重复的元素 |
sorted | 可以对流中的元素编写相应规则进行排序 |
limit | 可以设置流的最大长度,超出的部分将被抛弃 |
skip | 可以跳过流中 前面 指定个数的元素 |
flatMap | 用法和Map类似,但是可以把一个对象转换成多个对象作为接下来流操作中的元素 |
常用终结操作的方法有:
操作 | 作用 | 返回值 |
---|---|---|
forEach | 对流中的元素进行遍历,并指定对每一个元素进行什么操作 | void |
count | 可以获取当前流中元素中的个数 | long |
min&max | 可以指定获取流中的最值 | Optional |
collect | 把当前流转化为一个新的集合 | Optional |
anyMatch | 可以用来判断流中是否有任意符合匹配条件的元素,返回类型为boolean | boolean |
allMatch | 可以用来判断流中是否所有元素都符合匹配条件,都符合返回true,反之false | boolean |
noneMatch | 可以用来判断流中是否所有的元素都不符合匹配条件,都不符合返回ture,反之false | boolean |
findAny | 获取流中的任意一个元素,该方法没有办法保证获取到的一定是流中第一个元素 | Optional |
findFirst | 获取流中的第一个元素 | Optional |
reduce | 对流中的元素按照你制定的规则计算出 一个结果 | Optional |
2.2 注意事项
stream流具有以下三个特点:
- 惰性求值 (如果 没有终结操作,中间操作是不会得到执行的 )
- 流是一次行的 (一旦一个流对象 经过一个终结操作后,这个流就不能再被使用 )
- 不会影响源数据 (我们在流中进行的很多数据操作,正常情况下是不会影响原来集合中的数据的,但是如果你调用了集合中对象本来的set方法,是会影响的 )
2.3 中间操作示例演示
下面就为大家一一演示一下各个方法的使用,为了更好地演示stream流的方法,也为了鼓励大家自己动手实操一下,以下所有示例就不放出结果截图,仅放出运行代码啦。
先新建以下几个基本的类,并在maven的 pom文件中引入 lombok 依赖
maven 引入 lombok 依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
新建作者 Author.java
package com.chatapp;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode //用于后期去重使用
public class Author {
private Long id;
private String name;
private Integer age;
private String info;
private List<Book> books;
}
新建图书 Book.java
package com.chatapp;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class Book {
private Long id;
private String name;
private String category;
private Integer score;
private String info;
}
新建测试类 StramDemo.java (后面所有方法的测试方法都写在这个类中)
package com.chatapp;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
/**
* @Description
* @Author CV大虾
*/
class StramDemo {
@Test
void test() {
}
private static List<Author> getAuthors(){
Author author = new Author(1L,"蒙多",33,"一个祖安人",null);
Author author2 = new Author(2L,"亚拉索",15,"狂风不止",null);
Author author3 = new Author(3L,"易",14,"限制他的思维",null);
Author author4 = new Author(3L,"易",14,"限制他的思维",null);
List<Book> book1 = new ArrayList<>();
List<Book> book2 = new ArrayList<>();
List<Book> book3 = new ArrayList<>();
book1.add(new Book(1L,"道道入魂","哲学,爱情",88,"刀锋划破"));
book1.add(new Book(2L,"一个人","个人成长,爱情",99,"悟出真理"));
book2.add(new Book(3L,"一个人","哲学",85,"带你的思维去到远方"));
book2.add(new Book(3L,"一个人","哲学",85,"带你的思维去到远方"));
book2.add(new Book(4L,"风起长安","爱情,个人传记",56,"一个哲学家的脸"));
book2.add(new Book(5L,"月亮下的少年","文学",85,"月亮下的少年郎"));
book3.add(new Book(5L,"你的剑气","爱情",56,"你无法想象"));
book3.add(new Book(6L,"风与剑","个人传记",100,"两个哲学家"));
book3.add(new Book(6L,"风与剑","个人传记",100,"两个哲学家"));
author.setBooks(book1);
author2.setBooks(book2);
author3.setBooks(book3);
author4.setBooks(book3);
return new ArrayList<>(Arrays.asList(author,author2,author3,author4));
}
}
2.3.1 filter : 条件过滤
示例
@Test
public void filterTest(){
//题目:筛选出作家年级大于18岁的,并打印出其姓名
//创建流
getAuthors().stream()
//进行条件筛选
.filter(au->au.getAge()>18)
.forEach(e->System.out.println(e.getName()) );
}
2.3.2 map:类型转换
示例
@Test
public void mapTest(){
//题目:打印所有作家的姓名
getAuthors().stream()
//可以通过断点看到经过map中间操作,流已经由Stream<Author> 转化为 Stream<String>
.map(au->au.getName())
//System.out::println 是 e->System.out.println(x) 方法引用的写法
.forEach(System.out::println);
}
2.3.4 distinct:元素去重
注意:distinct元素给对象去重的时候是基于对象重写了 equals 和 hashCode 方法
拿Author对象举例,我们之前加上的 @EqualsAndHashCode 注解,其实是帮我们完成了以下方法
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Author author = (Author) o;
return Objects.equals(id, author.id)
&& Objects.equals(name, author.name)
&& Objects.equals(age, author.age)
&& Objects.equals(info, author.info)
&& Objects.equals(books, author.books);
}
@Override
public int hashCode() {
return Objects.hash(id, name, age, info, books);
}
示例:
@Test
public void distinctTest(){
//题目 打印所有作家姓名,并且去除重复元素
getAuthors().stream()
.distinct()
.map(Author::getName)
.forEach(System.out::println);
}
2.3.5 sorted:指定规则元素排序
示例
@Test
public void sortTest(){
//题目 将作者按从年龄从小到大 排序,并且去重输出
getAuthors().stream()
.distinct()
//sorted的入参函数传入两个值,一个是当前元素,一个是下一个元素
//他们两个相减即可比出大小,如果要从大到小 拿 o2-o1即可
.sorted((o1,o2)->o1.getAge()-o2.getAge())
.forEach(e->System.out.println("姓名: "+e.getName()+" 年龄:"+e.getAge()));
}
2.3.6 limit:限制流中元素长度
limit可以限制流中的元素,如流中元素长度为10,可以用limit限制为 5,但是如果流中长度本来为2,limit限制其为5,那么当然它还是为2。也就是说 超出的长度的丢弃,没超出不影响
示例
@Test
public void limitTest(){
//题目 将作者中年龄最大的 输出
getAuthors().stream()
.distinct()
//上面介绍的sorted的用法,相减调换一下顺序,即从大到小
.sorted((o1,o2)->o2.getAge()-o1.getAge())
//限制只输出一个
.limit(1)
.forEach(e->System.out.println(e.getName()+" "+e.getAge()));
}
2.3.7 skip:跳过流中前几位的元素
limit可以跳过流中指定的前几位的元素,具体用法可以看看案例
示例
@Test
public void skipTest(){
//题目 将作者中年龄第二大的 输出
getAuthors().stream()
.distinct()
//1.先从大到小 排序
.sorted((o1,o2)->o2.getAge()-o1.getAge())
//2.跳过第一个,也就是年龄最大的元素
.skip(1)
//3.限制只输出一个,这个时候也就是第二大的了
.limit(1)
.forEach(e->System.out.println(e.getName()+" "+e.getAge()));
}
2.3.8 flatMap:将流中单个对象的属性转换成多对象流
flatMap的用法是stream中间操作中比较复杂的,它可以实现将 流中对象的某个属性转化为多个对象,并在流中进行操作 。
如上面案例Author对象中有个book 属性,它是个List 类型的集合,如果我们要对Author对象中的每一个Book对象操作,就可以用到 flatMap 这个属性
示例
@Test
public void flatMapTest(){
//题目 将所属图书中包含 个人传记 的作者 去重后输出
getAuthors().stream()
//1.先去重
.distinct()
//2.进行条件过滤 符合条件的作者才输出
// 这里又在过滤条件中对每一个 作者对象 用 Stream.of(auf) 进行了流操作
.filter(auf-> Stream.of(auf)
//3 将每一个作者中的book都转化为 一个个book的流对象
.flatMap(au->au.getBooks().stream())
// 4 这里用到了后文会介绍的终结操作 anyMatch 意思是只要有一个匹配就返回true
.anyMatch(book -> book.getCategory().contains("个人传记")))
.forEach(e->System.out.println(e.getName()));
}
上文的 filter(auf -> …) 后面写的比较复杂你可能没看太明白,把它展开来看看你可能就理解了
@Test
public void flatMapTest(){
//题目 将所属图书中包含 个人传记 的作者 去重后输出
getAuthors().stream()
//1.先去重
.distinct()
//2.进行条件过滤 符合条件的作者才输出
// 这里又在过滤条件中对每一个 作者对象 用 Stream.of(auf) 进行了流操作
.filter(auf-> {
for (Author au : Arrays.asList(auf)) {
for (Book book : au.getBooks()) {
if (book.getCategory().contains("个人传记")) {
return true;
}
}
}
return false;
})
.forEach(e->System.out.println(e.getName()));
}
从上文的对比也可以看出 stream 流 提供的方法为我们提供了多大的便利,也让我们的代码优美度更高。
2.4 终止操作示例演示
当中间操作接一个终止操作,就意味着这个流的结束,这个时候可能有些人就要问了,为什么有时候,我明明使用了一个终结操作,为什么还可以用中间操作呢?例如以下案例
getAuthors().stream().findAny().filter(e->e.getName().equals("王大仙"));
findAny()是一个终结操作,会返回流中任意一个元素,但是后面又接了一个中间操作filter,这是为什么呢?这是因为findAny() 终结了前面的流,返回了一个Optional 的对象,而 这个filter其实是这个Optional 对象封装的方法。
下面就为大家用案例来演示一下终止操作具体有那些吧
2.4.1 forEach:遍历元素
示例
@Test
public void foreachTest(){
//遍历输出所有作者的姓名 年龄 和 信息,并去重
getAuthors().stream()
.distinct()
//foreach遍历元素其实就和 for循环很类似
.forEach(e-> System.out.println("姓名: "+e.getName() +" 年龄: "+e.getAge()+" 信息:"+e.getInfo()));
}
2.4.2 count:统计元素个数
示例
@Test
public void countTest(){
//统计去重后作者所有的图书数量
long count = getAuthors().stream()
//1.先进行去重
.distinct()
//2.用flatMap 将作者中图书属性的每一个book都转化成流
.flatMap(e -> e.getBooks().stream())
//统计元素数量
.count();
System.out.println("去重作者后,所有图书的数量为:"+count);
}
2.4.3 min&max : 求元素中的最值
示例
@Test
public void maxAndMinTest(){
//题目: 统计去重作者后 拥有图书量最多的作者
Optional<Author> maxau = getAuthors().stream()
//1.先进行去重
.distinct()
.max((o1,o2)-> o1.getBooks().size()-o2.getBooks().size());
System.out.println("去重作者后 拥有图书量最多的作者:"+maxau.orElse(new Author()).getName());
//题目:统计去重作者后 拥有图书量最少的作者
Optional<Author> minau = getAuthors().stream()
//1.先进行去重
.distinct()
.max((o1,o2)-> o2.getBooks().size()-o1.getBooks().size());
System.out.println("去重作者后 拥有图书量最少的作者:"+minau.orElse(new Author()).getName());
}
2.4.4 collect: 集合操作的集合
collect是steram终结操作中很重要的一种,java8 封装了Collectors工具类,它提供了 toMap 转化流成Map,toList 转化流成一个List集合,groupingBy按指定条件分组等各种操作。下面会为大家介绍它几种常用的操作。
Collectors.groupingBy 进行分组
@Test
public void CollectorsGroupTest (){
//对所有作者按照作者名进行分组(即重名的分一组)
Map<String, List<Author>> collect = getAuthors().stream()
.collect(Collectors.groupingBy(e -> e.getName()));
//这里输出一下咱们得到的Map
collect.entrySet().stream()
.forEach(e-> System.out.println("name 为 "+e.getKey()+" 有"+e.getValue().size()+" 个"));
}
Collectors.toList 转换为新的链表集合
@Test
public void collectorListTest(){
// 题目 :将作者去重后 得到作者图书中 含有 “个人传记“ 的 新集合,(图书集合可以包含其他的书籍)
List<Author> list_new = getAuthors().stream()
.distinct()
.filter(e -> Stream.of(e).
flatMap(au -> au.getBooks().stream())
.anyMatch((b -> b.getCategory().contains("个人传记"))))
.collect(Collectors.toList());
//这里输出一下咱们得到的新集合
list_new.stream()
.forEach(e-> System.out.println("name 为 "+e.getName()+" 有为【个人传记的分类】: "+e.getBooks()+" "));
}
可以看到输出的作者 亚拉索
的 book集合中仍然有 仅带有 哲学
等分类的书籍,那么 怎么去掉这些分类呢?
先做一下下面的这个示例,做完后,你应该会有一些思路。下面难度升级,做一个复杂一点点的
Collectors.toMap 转换为哈希表
@Test
public void collectorMapTest(){
//题目 将作者去重后 在其所属的图书中找出分类带有 “个人传记“ 的, 并和作者 重新组成 一个Map<作者名,List<图书>> 的结构 (图书集合最后汇总只包含带有“个人传记”)
Map<String, List<Book>> collect = getAuthors().stream()
.distinct()
.filter(e ->
//这里我是直接拿到 作者的book集合转化为了流,也可以用前文的通过
//Stream.of(e).flatMap(au->au.getBook().stream()) 的方式获取到每一个图书的流
e.getBooks().stream()
.anyMatch(b -> b.getCategory().contains("个人传记")))
.collect(Collectors.toMap(key -> key.getName(), value -> {
//这里把value重新封装一下,把不符合类型的book 筛掉
return value.getBooks().stream()
.filter(e -> e.getCategory().contains("个人传记"))
.collect(Collectors.toList());
}));
//这里输出一下咱们得到的Map
collect.entrySet().stream()
.forEach(e-> System.out.println("name 为 "+e.getKey()+" 有为【个人传记的分类】: "+e.getValue()+" "));
}
做了这个示例,再想想第二个List要怎么去掉其他不属于【个人传记】的分类,应该也有思路了吧。
Collectors.collectingAndThe
Collectors.collectingAndThe
方法是收集方法,简单来说就是可以在执行完将流转化为一个集合后,还可以执行一个Fuction函数进行指定的操作。
public static<T,A,R,RR> Collector<T,A,RR> collectingAndThen(Collector<T,A,R> downstream,
Function<R,RR> finisher)
可以看到该方法它可接受两个参数,
第一个参数用是Collector的子类,所以Collectors类中的大多数方法都能使用,比如:toList(),toSet(),toMap()等
第二个参数是一个Function函数,它可以拿到第一个参数处理的集合结果集,并进行一系列函数操作
看完👆的文字描述,可能你还是有点懵逼,那么我们来实际操作一下
Collectors.collectingAndThe 示例一
public void collectingAndTheTest01(){
//题目 :对所有作者按照作者名进行分组(即重名的分一组),并且 在流中输出 包含有重名的分组作者 是谁
Map<String, List<Author>> collect = getAuthors().stream()
//第一个参数完成按照名字分组的操作
.collect(Collectors.collectingAndThen(Collectors.groupingBy(e -> e.getName()),
//第二个参数完成分组完成后名字输出的function操作
e-> {
e.entrySet().stream().forEach(w->{
if(w.getValue().size()>1){
System.out.println("名字为 :"+w.getKey()+" 的作者有 "+w.getValue().size());
}
});
return e;
}));
Collectors.collectingAndThe 示例二
@Test
public void collectingAndTheTest02(){
// 题目: 使用collect完成 作者去重操作
ArrayList<Author> collect = getAuthors().stream()
.collect(Collectors.collectingAndThen(
//1.首先将集合转化为一个Set对象,set对象不允许重复,所以会完成去重
//这里因为 Author 对象本身没有实现 Comparable 接口,所以咱们传入一个自定义的 增强比较器
Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Author::getId))),
//2.第二个参数把set转化为List
ArrayList::new
));
}
2.4.5 anyMatch: 流中任意元素符合条件返回true
anyMatch会对流中的所有元素进行条件判断,如果有 任何一个元素符合条件,返回true,否则false
示例
@Test
public void anyMatchTest(){
// 题目 判断所有作者图书中是否 包含有 “哲学” 分类的数据
boolean flag = getAuthors().stream()
.flatMap(e -> e.getBooks().stream())
.anyMatch(e -> e.getCategory().contains("哲学"));
System.out.println(flag);
}
2.4.6 allMatch:流中所有元素符合条件返回true
示例
@Test
public void allMatchTest(){
// 题目 判断所有作者图书中是否 都包含有 “哲学” 分类
boolean flag = getAuthors().stream()
.flatMap(e -> e.getBooks().stream())
//所有图书都 包含有哲学才返回 true
.allMatch(e -> e.getCategory().contains("哲学"));
System.out.println(flag);
}
2.4.7 noneMatch: 流中所有元素都不符合条件返回true
示例
@Test
public void noneMatchTest(){
// 题目 判断所有作者图书中是否 都不包含有 “计算机” 分类
boolean flag = getAuthors().stream()
.flatMap(e -> e.getBooks().stream())
//所有图书分类都没有 计算机才返回 true
.noneMatch(e -> e.getCategory().contains("计算机"));
System.out.println(flag);
}
2.4.8 findFrist: 返回流中的第一个元素
findFrist 方法配合 sort排序 方法,可以求出符合条件的最大元素 和 最小元素
示例
@Test
public void findFristTest(){
//题目 作者去重后,输出年龄最大的作者对象
Optional<Author> first = getAuthors().stream()
.distinct()
//sorted的入参函数传入两个值,一个是当前元素,一个是下一个元素
//他们两个相减即可比出大小,如果要从大到小 拿 o2-o1即可
.sorted((o1, o2) -> o2.getAge() - o1.getAge())
//返回第一个也就是最大的
.findFirst();
//输出对象
first.ifPresent(System.out::println);
}
2.4.9 reduce: 制定的规则计算出 一个结果
Reduce 的用法是stream流中比较难理解,也比较常用的一个函数
它有两种比较常用的用法
-
两个参数: T reduce(T identity, BinaryOperator accumulator);
identity 为初始值
accumulator为一个接口函数传入两个值 (最终迭代值(result),下一次累加值(element))
-
一个参数:Optional reduce(BinaryOperator accumulator);
accumulator为一个接口函数传入两个值 (最终迭代值(result),下一次累加值(element))
该方法的初始值为流中的第一个对象
它内部的计算方式如下
T result =identity;
for(T element: this stream)
result= accumulator.apply(result,element)
return result
弄懂上面的核心,是理解reduce方法的关键。
示例 (两个参数的用法)
@Test
void reduceTestTwo() {
//题目 计算出NUM的总和 (要求用 reduce 编写计算公式)
Integer[] num = new Integer[]{1,2,3,4};
Integer reduce = Stream.of(num)
//0 表示初始值,这里如果是100 即是从100 开始累加
.reduce(0, (result, next) -> result + next);
System.out.println(reduce);
}
理解了上面的题目,👇🏻来个复杂一点点的:
示例 (一个参数的用法)
@Test
void reduceTestOne() {
//题目 将所有的作者信息合并成一个 作者对象返回(如 姓名合并为 string 字符串 "蒙多,亚拉索" 年龄累加 所有书本集合合并成一个集合)
Optional<Author> reduce = getAuthors().stream()
//reduce 一个参数的写法 :入参为一个BinaryOperator接口
//第一个result 是迭代 追加的对象,也是进入追加的第一个对象
//第二个next 是 下一个对象
.reduce((result, next) -> {
result.setName(result.getName() + "," + next.getName());
result.setAge(result.getAge() + next.getAge());
result.setInfo(result.getInfo() + ", " + next.getInfo());
result.getBooks().addAll(next.getBooks());
return result;
});
reduce.ifPresent(result -> System.out.println(reduce));
}
可能👆🏻 的代码你还是看着有点迷糊,那么我们来换成传统写法,看看reduce到底帮我们做了哪些操作
//reduce 方法做的事情其实就是
List<Author> authors = getAuthors();
Author result =authors.get(0);
for (int i = 1; i < authors.size(); i++) {
//因为外面已经附了初始值,所以这里从第二个对象开始了累加
Author next = authors.get(i);
result.setName(result.getName() + "," + next.getName());
result.setAge(result.getAge() + next.getAge());
result.setInfo(result.getInfo() + ", " + next.getInfo());
result.getBooks().addAll(next.getBooks());
}
System.out.println(result);
三 stream流中方法依赖的接口
在stream流中,其实有一些方法是如果是作用于实体,如上文中的 Author
和 Book
, 那么我们其实是可以在实体中重写一些方法来使流的操作方法更加符合我们的预期。
3.1 流中在实体可定制化重写的方法和接口
stream方法 | 实体中可实现接口 | 实体中可重写方法 |
---|---|---|
sorted | Comparable接口 | |
distinct | equals和hashCode方法 | |
Comparator.comparing | Comparable接口 |
Author类
package com.chatapp;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Objects;
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode //用于后期去重使用
public class Author implements Comparable<Author>{
private Long id;
private String name;
private Integer age;
private String info;
private List<Book> books;
//重写该方法可不用再sorted 传入lambda表达式
@Override
public int compareTo(Author o) {
return this.getAge()-o.getAge();
}
//重写该方法,实现指定distinct 的判断
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Author author = (Author) o;
return Objects.equals(id, author.id);
}
//重写该方法,实现指定distinct 的判断
@Override
public int hashCode() {
return Objects.hash(id);
}
}
3.1.1 sorted 实体中自定义比较
sorted方法可以传入lambda表达式,例如上文的写法
sorted((o1,o2)->o1.getAge()-o2.getAge()) 实现排序,也可以在实体类中实现Comparable接口
@Test
public void sortTest(){
//题目 将作者按从年龄从小到大 排序,并且去重输出
getAuthors().stream()
.distinct()
//sorted的入参函数传入两个值,一个是当前元素,一个是下一个元素
//他们两个相减即可比出大小,如果要从大到小 拿 o2-o1即可
.sorted()
.forEach(e->System.out.println("姓名: "+e.getName()+" 年龄:"+e.getAge()));
}
3.1.2 distinct 实体中自定义去重
distinct 方法也可以通过重写equals和hashCode方法达到实体指定字段去重
例如两个对象:只要id相同,哪怕其他属性不同,也认为是同一对象
Author author = new Author(1L,“蒙多”,33,“一个祖安人”,null);
Author author2 = new Author(1L,“亚拉索”,15,“狂风不止”,null);
@Test
public void distinctTest(){
//题目 使用distinct 给 下面两个对象去重
Author author = new Author(1L,"蒙多",33,"一个祖安人",null);
Author author2 = new Author(1L,"亚拉索",15,"狂风不止",null);
Stream.of(author,author2)
.distinct()
.forEach(e->System.out.println("姓名: "+e.getName()+" 年龄:"+e.getAge()));
}
3.1.3 Comparator.comparing 增强器替换
在实体中实现 Comparable,把比较属性换成去重的标识属性,即可完成不传入增强器的new Set去重
package com.chatapp;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Objects;
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode //用于后期去重使用
public class Author implements Comparable<Author>{
private Long id;
private String name;
private Integer age;
private String info;
private List<Book> books;
//用id作为去重标识,所以这里比较id
@Override
public int compareTo(Author o) {
return this.id.compareTo(o.id);
}
}
@Test
public void collectingAndTheTest02(){
// 题目: 使用collect完成 作者去重操作
Author author = new Author(1L,"蒙多",33,"一个祖安人",null);
Author author2 = new Author(1L,"亚拉索",15,"狂风不止",null);
ArrayList<Author> collect = Stream.of(author,author2)
.collect(Collectors.collectingAndThen(
//1.首先将集合转化为一个Set对象,set对象不允许重复,所以会完成去重
//这里因为 Author 实现了 Comparable 接口,并且指定了id作为比较 所以不必像前文说的那样传入增强处理器
//Comparator.comparing(Author::getId)
Collectors.toCollection(() -> new TreeSet<>()),
//2.第二个参数把set转化为List
ArrayList::new
));
collect.stream().forEach(System.out::println);
}
总结
以上就是关于 jdk8 新特性有关于lambda表达式和stream流的全部内容,相信你如果能熟练掌握以上的一些用法,在实际编程中一定会在一定程度上提高你代码的执行效率和优美度,当然啦,学习这些也是为了能看懂别人写的stream代码和lambda代码到底在做些什么事,也能偶尔装一手不是 😄