java8新体验--lambda表达式和stream流

1、序言

  Java8的重大变化相信没有小伙伴不知道了,即便没有使用过,也多少有些听闻。简单的记录一下一些简单的用法,主要包括lambda表达式和stream流的简单使用。
  ** 本书内容部分来自书籍《Java8实战》,感兴趣的小伙伴可以了解一下。

  定义一个简单的用户实体类,作为后面的例子:

public class User implements Serializable {
    private static final long serialVersionUID = -6661244494928294785L;
    /** 姓名 */
    private String userName;
    /** 性别 */
    private String sex;
    /** 分数 */
    private Integer score;
	/** 班级  */
    private Integer grade;
    /** 省略构造方法、getset、toString等方法 */
}
List<User> userList = Arrays.asList(new User("张三", "man", 68, 2),
                new User("李四", "woman", 98, 3),
                new User("王五", "man", 68, 3),
                new User("赵六", "woman", 57, 3));

2、lambda表达式

  个人对lambda使用的最多的地方就是代替了匿名内部类(还有之后和stream流的使用)。
  说道匿名内部类,我想使用的比较多的就是集合排序和比较了,之前内部类的方式就不多说了,直接来lambda表达式的造型:

// 分数升序
userList.sort(Comparator.comparing(User::getScore));
// 分数降序
userList.sort(Comparator.comparing(User::getScore).reversed());
// 分数降序,分数相同时,按照班级升序
userList.sort(Comparator.comparing(User::getScore).reversed()
			.thenComparing(User::getGrade));

3、stream流

流的使用大致包括三件事:

  1. 一个数据源(如集合)来执行一个查询;
  2. 一个中间操作链,形成一条流的流水线
  3. 一个终端操作,执行流水线,并能生成结果。

3.1 创建stream流

在 Java 8 中, 集合接口有两个方法来生成流:

  • stream() − 为集合创建串行流
  • parallelStream() − 为集合创建并行流

3.2 中间操作

3.2.1 filter筛选集合
// 筛选分数大于等于60分的用户
List<User> collect = userList.stream().filter(u -> u.getScore() >= 60)
										  .collect(Collectors.toList());
3.2.2 map、peek映射操作
// map获取userList里的用户名称集合
List<String> collect = userList.stream().map(User::getUserName)
										.collect(Collectors.toList());
// peek为元素设置值
List<User> collect = userList.stream().filter(u -> u.getGrade() == 2)
						.peek(u -> u.setGrade(100)).collect(Collectors.toList());
3.2.3 limit、skip截取流
// limit截取集合前两个
List<User> collect = userList.stream().limit(2).collect(Collectors.toList());
// skip跳过前两个
List<User> collect = userList.stream().skip(2).collect(Collectors.toList());
3.2.4 flatMap扁平流

  你可以简单的将它理解为两重for循环(事实并不是,只是一种理解)

// 创建一个新的,没有班级的User集合
List<User> userList2 = Arrays.asList(new User("张三", "man", 68),
               						 new User("李四", "woman", 78),
                					 new User("王五", "man", 88),
                					 new User("陈七", "woman", 67));

需要实现目标,根据userList里的用户名称为userList2里的user添加班级:

List<User> collect = userList.stream().flatMap(u1 -> userList2.stream()
           .filter(u2 -> Objects.equals(u1.getUserName(), u2.getUserName()))
           .peek(u2 -> u2.setGrade(u1.getGrade()))).collect(Collectors.toList());

flatMap推荐博客flatMap推荐博客

3.2.5 sorted排序
// 分数升序
userList.sort(Comparator.comparing(User::getScore));
// 多条件排序
userList.sort(Comparator.comparing(User::getGrade).reversed()
							.thenComparing(User::getScore));
3.2.6 distinct去重
// 分数去重
List<Integer> collect = userList.stream().map(User::getScore)
								.distinct().collect(Collectors.toList());

注意:如果去重判断的是对象,需要重写equals()和hashCode()方法来判断对象是否相同


2021.1.13 更新
按照实体类某个字段去重

@Test
public void test3() {
    List<User> users = Arrays.asList(
        new User(1, "张三", 18, "aa"),
        new User(2, "张三1", 18, "aa"),
        new User(3, "张三2", 19, "ab"),
        new User(4, "张三3", 19, "ab"),
        new User(5, "张三4", 17, "ac"),
        new User(6, "张三5", 17, "ad"),
        new User(7, "张三6", 18, "ae")
    );
// 按照年龄去重
ArrayList<User> collect = users.stream().collect(
       Collectors.collectingAndThen(Collectors.toCollection(
            () -> new TreeSet<>(Comparator.comparing(User::getAge))), ArrayList::new));
   System.out.println("年龄去重结果:" + collect);
}

按照实体类某几个字段去重

// 按照年龄和备注去重
ArrayList<User> collect1 = users.stream().collect(
	Collectors.collectingAndThen(Collectors.toCollection(
		() -> new TreeSet<>(Comparator.comparing(User::getAge)
							.thenComparing(User::getRemark))), ArrayList::new));
System.out.println("年龄和备注去重结果:" + collect1);

打印结果:

年龄去重结果:[User(id=5, name=张三4, age=17, remark=ac), User(id=1, name=张三, age=18, remark=aa), User(id=3, name=张三2, age=19, remark=ab)]
年龄和备注去重结果:[User(id=5, name=张三4, age=17, remark=ac), User(id=6, name=张三5, age=17, remark=ad), User(id=1, name=张三, age=18, remark=aa), User(id=7, name=张三6, age=18, remark=ae), User(id=3, name=张三2, age=19, remark=ab)]


3.3 终端操作

3.3.1 forEach遍历集合
// 流遍历
userList.forEach(System.out::println);
// 并行流遍历,并行操作
userList.parallelStream().forEach(System.out::println);

注意:尽可能少的在forEach里对元素进行增、删、改,特别是使用并行流parallelStream

3.3.2 count返回流中的个数
// 分数到达70的个数
long count = userList.stream().filter(u -> u.getScore() > 70).count();
3.3.3 collect收集器

个人觉得这是stream流最重要的一环,流的终端操作

3.3.3.1 常见集合收集器
// 将流收集到List集合内
userList.stream().map(User::getUserName).collect(Collectors.toList());

在《Java8实战》一书或者很多博客内的收集器写法比较简单,如:collect(toList())
原因在于导包的时候导入了Collectors类的所以静态工厂方法

// collect(Collectors.toList())的导包
import java.util.stream.Collectors;
// collect(toList())的导包
import static java.util.stream.Collectors.*;

收集器除了能将数据收集为List,还能收集为常见的其他集合类型,如:

  • Collectors.toMap()
  • Collectors.toSet()
  • Collectors.toCollection()
  1. Collectors.toCollection(ArrayList::new)
  2. Collectors.toCollection(LinkedList::new)
  3. Collectors.toCollection(CopyOnWriteArrayList::new)
  4. Collectors.toCollection(TreeSet::new)
3.3.3.2 数值流
1、 对象流转数值流
// 分数求和
int sum = userList.stream().mapToInt(User::getScore).sum();
// 分数最大值,返回OptionalInt
int max = userList.stream().mapToInt(User::getScore).max().orElse(-1);
// 分数最小值,返回OptionalInt
int min = userList.stream().mapToInt(User::getScore).min().orElse(-1);
2、 数值流转对象流(boxed()方法)
// 数值流转对象流,不转的话很多方法使用不了
Stream<Integer> boxed = userList.stream().mapToInt(User::getScore).boxed();
3.3.3.3 汇总

IntSummaryStatistics类,包含了总个数、求和、最小值、最大值、平均值

IntSummaryStatistics collect = userList.stream()
							.collect(Collectors.summarizingInt(User::getScore));
// 元素个数
long count = collect.getCount();
// 总和
long sum = collect.getSum();
// 最小值
int min = collect.getMin();
// 平均值
double average = collect.getAverage();
// 最大值
int max = collect.getMax();
3.3.3.4 连接字符串joining()

将集合中的元素拼成字符串

// 返回 张三李四王五赵六
String collect = userList.stream().map(User::getUserName)
						.collect(Collectors.joining());
// 返回 张三,李四,王五,赵六
String collect1 = userList.stream().map(User::getUserName)
						.collect(Collectors.joining(","));
// 返回 [张三,李四,王五,赵六]
String collect2= userList.stream().map(User::getUserName)
						.collect(Collectors.joining(",", "[", "]"));
3.3.3.5 分组groupingBy
// 将集合按条件分组,比如按性别
Map<String, List<User>> collect = userList.stream()
			.collect(Collectors.groupingBy(User::getSex));
// 按成绩是否及格分组
Map<String, List<User>> collect1 = userList.stream().collect(
		Collectors.groupingBy(u -> u.getScore() >= 60 ? "pass" : "fail"));
// 分组统计及格和不及格人数
Map<String, Long> collect2 = userList.stream().collect(Collectors.groupingBy(
		u -> u.getScore() >= 60 ? "pass" : "fail", Collectors.counting()));
		// 多条件分组,性别 + 是否及格
Map<String, Map<String, List<User>>> collect = userList.stream()
			.collect(Collectors.groupingBy(User::getSex, Collectors
			.groupingBy(u -> u.getScore() >= 60 ? "pass" : "fail")));
3.3.4 reduce规约

很多操作收集器collect里面都有,而且collect里的方法更加简单

// 分数求和
Integer sum = userList.stream().map(User::getScore).reduce(0, Integer::sum);
// 分数最大值,给了个初始值0
Integer max = userList.stream().map(User::getScore).reduce(0, Integer::max);
// 分数最小值,如果结果为空,返回-1
Integer min = userList.stream().map(User::getScore).reduce(Integer::min).orElse(-1);
3.3.5 查找和匹配
3.3.5.1 匹配anyMatch、allMatch、noneMatch
// 任意一个元素和要求匹配
boolean b = userList.stream().anyMatch(u -> u.getScore() > 90);
// 所有元素和要求匹配
boolean b1 = userList.stream().allMatch(u -> u.getScore() > 90);
// 没有元素和要求匹配
boolean b2 = userList.stream().noneMatch(u -> u.getScore() > 90);
3.3.5.2 查找findAny、findFirst
// 返回第一个元素
User user = userList.stream().findFirst().get();
// 返回任意一个元素
User user1 = userList.stream().findAny().get();

这两个方法返回的是Optional类,这里之间简单的使用get()方法去获取值,可能会出现空指针异常

4、优化方法

  Java8新特性给我们带来了极大的便利,同时也要注意使用方式。从上面的一些例子中可以看出写法是很重复的,即便是导包时将Collectors类中静态方法导入,也不是那么方便,为了解决这样的困境,我们可以考虑别的方式来解决。比如使用IDEA的模板:

4.1 创建模板

File -> Settings -> Editor ->Live Templates -> 点击右边"+" -> Template Group,完成模板组的创建,然后开始创建模板

AbbreviationDescriptionTemplate text
filter流集合过滤stream().filter(u -> E N D END END)
filterlist集合过滤liststream().filter(u -> E N D END END).collect(Collectors.toList());
foreach集合流遍历forEach(System.out::println);
groupBy集合流分组collect(Collectors.groupingBy( E N D END END));
groupByCount分组计数collect(Collectors.groupingBy(u -> E N D END END, Collectors.counting()));
joining流拼装字符串collect(Collectors.joining(","));
limit流截取limitstream().limit( E N D END END)
limitlist流截取liststream().limit( E N D END END).collect(Collectors.toList());
map流映射mapstream().map( E N D END END)
maplist流映射liststream().map( E N D END END).collect(Collectors.toList());
sort集合升序排序sort(Comparator.comparing( E N D END END));
sortR集合降序排列sort(Comparator.comparing( E N D END END).reversed());
toListlist收集器collect(Collectors.toList());

4.2 使用模板

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8NAOHOhs-1619804015088)(http://www.lichuan.cool/images/alistudy/模板.png “模板图片”)]
如果有条件,可以使用最新版的IDEA2020.2版,对lambda表达式提供了更好的支持。


2020-09-08 补充

5、使用补充

5.1 Map遍历

方法一:

map.forEach((k, v) -> System.out.println(k + "==" + v));

注意点:Map不能直接创建stream流,更不能使用并行流parallelStream(),需要先执行entrySet()【或者keySet()方法】,如果需要使用到流操作,推荐使用entrySet()
方法二:

map.keySet().stream().forEach(u -> System.out.println(u + "--" + map.get(u)));
map.entrySet().parallelStream().forEach(u -> System.out.println(u.getKey() +"==" + u.getValue()));

5.2 parallelStream()并行流执行太慢

并行流执行的速度与本机电脑的核心数有关,默认线程数与电脑核心数相同,但是有时候会需要更多的线程来执行操作,这里需要用到ForkJoinPool线程池,ForkJoinPool推荐博客

// 创建线程池
ForkJoinPool pool = new ForkJoinPool(70);
// 执行
pool.submit(() -> 相关业务操作).get();
// 回收
pool.shutdown();

请注意线程安全问题


2020-9-24补充
optional好文推荐:理解、学习与使用 JAVA 中的 OPTIONAL

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值