方便的Stream

简介: 方便的Stream

背景:当使用集合数组的时候,经常会使用循环的方式逐个处理。在Java8提供了很多新的特性包括Stream,使得代码更简洁、偏声明式的编码风格,更容易体现出代码的逻辑意图,逻辑间解耦,一个stream中间处理逻辑,无需关注上游与下游的内容,只需要按约定实现自身逻辑即可


函数式接口

众所周知,Java8提供了很多新的特性,Lambda表达式,函数式接口,Optional,新的日期类api。今天简单聊一下Stream的前世今生。


Lambda表达式我们现在已经用的很多了,而函数式接口则是为了支持Lambda表达式,Java8提供了很多内置的函数式接口,如Runnable,Comparator等是从原有的API升级来的,而有些是Java8新增的,如Consumer等。

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

类上有注解@FunctionalInterface就可以认为这是一个函数式接口,可以用在Lambda表达式中。Lambda表达式极大的简化了我们的编程

// jdk1.8之前
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("yes");
    }
}).start();
// jdk1.8及以后
new Thread(() -> System.out.println("yes")).start();

为了方便我们使用Lambda表达式,Java8提供了一些内置的函数式接口

函数式接口 方法 用途
Consumer 消费型接口 void accept(T t) 输入参数为T,没有返回
Supplier 供给型接口 T get() 返回R
Function<T, R> 函数型接口 R apply(T t) 输入参数为T,返回为R
Predicate 判断型接口 boolean test(T t) 对象是否满足条件,true为满足,false为不满足

Java8为什么要新提供这些函数式接口呢?

我举个例子你就明白了。

public class NumberClass{
  Integer a, b, c;
  public NumberClass(final Integer a, final Integer b, final Integer c) {
    this.a = a;
    this.b = b;
    this.c = c;
  }
  public Integer getA() {
    return this.a;
  }
  public Integer getB() {
    return this.b;
  }
  public Integer getC() {
    return this.c;
  }
}

我写了个1个方法

public static Integer doV1(NumberClass numberClass){
  Integer result = 0;
  //step 1
  result = (numberClass.getA() + numberClass.getB() + numberClass.getC());
  //step 2
  result = (result * numberClass.getA());
  return result;
}

结果1个方法还不够用,业务逻辑需要变动,但是原来的方法不能动,怎么办,再加个方法

public static Boolean doV2(NumberClass numberClass){
  Integer result = 0;
  //step 1
  result = (numberClass.getA() * numberClass.getB() + numberClass.getC());
  //step 2
  result = (result * numberClass.getA());
  return result > 100;
}

结果有需要加一些新功能,原来业务不变动~~~ 开始操蛋起来了,总是加方法也不是办法对吧,况且是功能性差不多的方法

直接优化一下写一个模版优化一下

public static <D> D processTemplate(NumberClass numberClass, Function<NumberClass, D> function){
  D applyResult = function.apply(numberClass);
  return applyResult;
}
NumberClass numberClass = new NumberClass(1, 2, 50);
Integer integerResult = processTemplate(numberClass, (n) -> {
  Integer result = 0;
  //step 1
  result = (numberClass.getA() + numberClass.getB() + numberClass.getC());
  //step 2
  result = (result * numberClass.getA());
  return result;
});
Boolean booleanResult = processTemplate(numberClass, (n) -> {
  Integer result = 0;
  //step 1
  result = (numberClass.getA() * numberClass.getB() + numberClass.getC());
  //step 2
  result = (result * numberClass.getA());
  return result > 100;
});

上面的需求就可以用如下几行代码实现。

函数式接口的使用

函数式接口 方法 用途
Consumer 消费型接口 void accept(T t) 输入参数为T,没有返回
Supplier 供给型接口 T get() 返回R
Function<T, R> 函数型接口 R apply(T t) 输入参数为T,返回为R
Predicate 判断型接口 boolean test(T t) 对象是否满足条件,true为满足,false为不满足
@Test
public void testCase1() {
    // 10
    consumeTask(10, (m) -> System.out.println(m));
}
public void consumeTask(int num, Consumer<Integer> consumer) {
    consumer.accept(num);
}
@Test
public void testCase2() {
    // AAA
    System.out.println(strHandler("aaa", (str) -> str.toUpperCase()));
}
public String strHandler(String str, Function<String, String> function) {
    return function.apply(str);
}

当然,为了方便我们的使用,还有很多其他的内置接口,看入参和返回值就能知道接口的作用

函数式接口 方法
BiFunction<T, U, R> R apply(T t, U u)
BiConsumer<T, U> void accept(T t, U u)
ToIntFunction int applyAsInt(T value)
IntFunction R apply(int value)


Stream介绍

在Java8之前,如果我们想对集合进行操作还是比较麻烦的。Java8设计了Stream API来简化对集合的操作,Stream API的设计基于函数式编程和lambda表达式,行云流水似的编程方式给人带来新的体验。


Stream操作分为如下三个步骤


创建Stream:从数据源,例如集合,数组中获取一个流

中间操作:对数据进行处理

终止操作:执行中间操作,并产生结果。一般返回void或一个非流的结果

注意当不执行终止操作的时候,中间操作不会执行

List<Integer> dataList = Arrays.asList(1, 2, 3, 4);
// 没有输出
dataList.stream().map(x -> {
    System.out.println(x);
    return x;});
// 输出 1 2 3 4
// 正常是换行,我这用空格代替了,下同
dataList = dataList.stream().map(x -> {
    System.out.println(x);
    return x;
}).collect(Collectors.toList());

创建Stream

// 1. Collection集合的stream()或者parallelStream()
List<String> list = Lists.newArrayList();
Stream<String> stream1 = list.stream();
// 2. 调用Arrays.stream(T[] array)静态方法
Integer[] array = {1, 2, 3};
Stream<Integer> stream2 = Arrays.stream(array);
// 3. 调用Stream.of(T... values)静态方法
Stream<String> stream3 = Stream.of("aa", "bb", "cc");
// 4. 调用Stream.iterate(final T seed, final UnaryOperator<T> f),创建无限流
// (x) -> x + 2 为函数式接口,传入x返回x+2,0为最开始的值
Stream<Integer> stream4 = Stream.iterate(0, (x) -> x + 2);
// 一直输出 0 2 4 6 8 10 12 ...
stream4.forEach(System.out::println);
// 5. 调用调用Stream.generate(),创建无限流
Stream<Integer> stream5 = Stream.generate(() -> 10);
// 一直输出10,你可以用Random等类随机生成哈
stream5.forEach(System.out::println);

中间操作

筛选与切片

函数名 解释
filter 从流中排除某些元素
limit 使元素不超过指定数量

skip

跳过前n个元素,如果流中元素不超过n个,则返回一个空流
distinct 通过hashCode()和equals()去除重复元素
List<Integer> list = Arrays.asList(1, 2, 3, 4);
// 1 3
list.stream().filter(x -> x % 2 == 1).forEach(System.out::println);
// 3 4
list.stream().skip(2).forEach(System.out::println);

看一下filter方法和forEach方法的定义

Stream.java

Stream<T> filter(Predicate<? super T> predicate);
void forEach(Consumer<? super T> action);

这不就是我门上面介绍的函数式接口吗?

很多方法的入参其实就是一个函数式接口

映射

函数名 解释
map 接收一个函数作为参数,该函数被应用到每个元素上,并将其映射成一个新的元素
flatMap 接受一个函数作为参数,将流中的每一个值都转换成另一个流,然后将所有流连接成一个流

先看这2个方法的定义

<R> Stream<R> map(Function<? super T, ? extends R> mapper);
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

map方法的入参和返回值可以为任意值

flatMap方法的入参为任意值,返回值必须为Stream

List<String> list = Arrays.asList("abcd", "efgh");
// [Ljava.lang.String;@7b3300e5 [Ljava.lang.String;@2e5c649
list.stream().map(x -> x.split("")).forEach(System.out::println);
// a b c d e f g h
list.stream().flatMap(x -> Arrays.stream(x.split(""))).forEach(System.out::println);

解释一下这个输出,x.split(“”)后为数组,所以第一个输出的为数组的地址

第二个x.split(“”)后为数组,然后将数组转为多个流,将多个流合并后输出

排序

函数名 解释
sorted() 自然排序,通过Comparable接口定义的规则来排序
sorted(Comparator) 定制排序,通过Comparator接口定义的规则来排序
List<String> list = Arrays.asList("b", "a", "c");
// a b c
list.stream().sorted().forEach(System.out::println);
// c b a
list.stream().sorted((x, y) -> y.compareTo(x)).forEach(System.out::println);

终止操作

查找与匹配

函数名 解释
allMatch 是否匹配所有元素
anyMatch 是否至少匹配一个元素
noneMatch 是否没有匹配所有元素
findFirst 返回第一个元素
findAny 返回当前流中的任意元素
count 返回当前流中元素总个数
max 返回流中最大值
min 返回流中最小值
List<Integer> list = Arrays.asList(1, 2, 3, 4);
// false
// 当list都为1时才会返回true
System.out.println(list.stream().allMatch(num -> num.equals(1)));
// true
System.out.println(list.stream().anyMatch(num -> num.equals(1)));
// 4
System.out.println(list.stream().max((x, y) -> x.compareTo(y)).get());

归约

函数名 解释
reduce 归约,将流中元素反复结合起来得到一个值
List<Integer> list = Arrays.asList(1, 2, 3, 4);
int sum = list.stream().reduce(0, (x, y) -> x + y);
// 10
// 初始值为0,执行过程为
// x = 0 y = 1
// x = 1 y = 2
// x = 3 y = 4 ...
// 10
// 10
 System.out.println(sum);

收集

用collect方法来进行收集,方法定义如下

Stream.java

<R> R collect(Supplier<R> supplier,
              BiConsumer<R, ? super T> accumulator,
              BiConsumer<R, R> combiner);
<R, A> R collect(Collector<? super T, A, R> collector);

当然我一般不自己实现这个接口,可以直接用Collectors工具类

@Data
@AllArgsConstructor
public class Student {
    private String name;
    private int age;
}
List<Student> studentList = Arrays.asList(new Student("张三", 30),
        new Student("李四", 20),
        new Student("王五", 20));
List<String> nameList = studentList.stream().map(Student::getName).collect(Collectors.toList());
// [张三, 李四, 王五]
System.out.println(nameList);
Set<Integer> ageSet = studentList.stream().map(Student::getAge).collect(Collectors.toSet());
// [20, 30]
System.out.println(ageSet);
LinkedHashSet<Integer> linkedHashSet =
        studentList.stream().map(Student::getAge).collect(Collectors.toCollection(LinkedHashSet::new));
// [30, 20]
System.out.println(linkedHashSet);
// 总数
long count = studentList.stream().collect(Collectors.counting());
// 3
System.out.println(count);
// 平均值
double ageAvg = studentList.stream().collect(Collectors.averagingDouble(Student::getAge));
// 23.3
System.out.println(ageAvg);
// 总和
int totalAge = studentList.stream().collect(Collectors.summingInt(Student::getAge));
// 70
System.out.println(totalAge);
// 最大值
Optional<Student> student = studentList.stream().collect(Collectors.maxBy((x, y) -> x.getAge() - y.getAge()));
// Student(name=张三, age=30)
System.out.println(student.get());
// 按照年龄分组
// 还可以多级分组,按照年龄分组后,再按照其他条件分组,不再演示
Map<Integer, List<Student>> listMap = studentList.stream().collect(Collectors.groupingBy(Student::getAge));
// {20=[StreamDemo.Student(name=李四, age=20), StreamDemo.Student(name=王五, age=20)], 30=[StreamDemo.Student(name=张三, age=30)]}
System.out.println(listMap);


一些业务场景


枚举值参数校验

项目中有很多单选项需要定义相关的枚举值,前端传入后需要校验这些值是否在枚举范围内

public enum MSG_TYPE {
    IMAGE((byte) 0, "图片"),
    TEXT((byte) 1, "文本");
    public final byte value;
    public final String name;
    MSG_TYPE(byte value, String name) {
        this.value = value;
        this.name = name;
    }
}
// 模拟前端传入的参数为1
boolean isExist = Arrays.stream(MSG_TYPE.values()).anyMatch(v -> v.value == 1);
// true
System.out.println(isExist);
isExist = Arrays.stream(MSG_TYPE.values()).anyMatch(v -> v.value == 5);
// false
System.out.println(isExist);

从不同服务拿到数据进行组装

比如从A服务拿到了用户数据,从B服务拿到了用户订单数据,从C服务拿到了会员相关信息,如果想要多个服务拿到的数组信息糅合到一个数组里面,可以采用循环的方式判断填充,多层循环处理这样子是比较耗费资源


可以使用如下方法


取得3个服务的集合信息


找出共有唯一字段将其中两个集合信息进行map映射:如(用户账号 -> 会员信息),(用户账号 -> 订单信息 )


进行参数组装

@Data
public static class MemberInfo{//会员信息
  private String userId;
  private String memberId;
  private Integer memberLevel;
  private String memberDesc;
  //...
  public MemberInfo(final String userId, final String memberId, final Integer memberLevel, final String memberDesc) {
    this.userId = userId;
    this.memberId = memberId;
    this.memberLevel = memberLevel;
    this.memberDesc = memberDesc;
  }
}
@Data
public static class OrderInfo{//订单信息
  private String userId;
  private String orderId;
  private BigDecimal orderAmount;
  private Date payTime;
  //...
  public OrderInfo(final String userId, final String orderId, final BigDecimal orderAmount, final Date payTime) {
    this.userId = userId;
    this.orderId = orderId;
    this.orderAmount = orderAmount;
    this.payTime = payTime;
  }
}
@Data
public static class UserInfo{//用户信息
  private String userId;
  private String userName;
  private String phone;
  private String address;
  //...
  public UserInfo(final String userId, final String userName, final String phone, final String address) {
    this.userId = userId;
    this.userName = userName;
    this.phone = phone;
    this.address = address;
  }
}
@Data
public static class QueryListInfo{//组合信息
  private String userId;
  private String userName;
  private String phone;
  private String address;
  private String memberId;
  private Integer memberLevel;
  private String memberDesc;
  private List<OrderInfo> orderInfos;
  //...
  public QueryListInfo() {
  }
}

组装逻辑

List<MemberInfo> memberInfos = Arrays.asList(
                new MemberInfo("QXZ112233", "1", 1, "大怨种低级会员"),
                new MemberInfo("QXZ112234", "2", 2, "大怨种中级会员"),
                new MemberInfo("QXZ112235", "3", 3, "大怨种高级会员")
        );
        List<OrderInfo> orderInfos = Arrays.asList(
                new OrderInfo("QXZ112233", "1", new BigDecimal(100), new Date()),
                new OrderInfo("QXZ112233", "2", new BigDecimal(100), new Date()),
                new OrderInfo("QXZ112234", "3", new BigDecimal(100), new Date()),
                new OrderInfo("QXZ112234", "4", new BigDecimal(100), new Date()),
                new OrderInfo("QXZ112235", "5", new BigDecimal(100), new Date())
        );
        List<UserInfo> userInfos = Arrays.asList(
                new UserInfo("QXZ112233", "大怨种1号", "13111112222", "地球"),
                new UserInfo("QXZ112234", "大怨种2号", "13111113333", "地球"),
                new UserInfo("QXZ112235", "大怨种3号", "13111114444", "地球")
        );
        //将数据转换成map
        Map<String, List<OrderInfo>> orderInfoMap = orderInfos.stream().collect(Collectors.groupingBy(OrderInfo::getUserId));
        Map<String, MemberInfo> memberInfoMap = memberInfos.stream().collect(Collectors.toMap(MemberInfo::getUserId, Function.identity()));
        List<QueryListInfo> queryListInfos = new ArrayList<>();
        for (UserInfo userInfo : userInfos) {
            String userId = userInfo.getUserId();
            MemberInfo memberInfo = memberInfoMap.get(userId);
            List<OrderInfo> orderInfos1 = orderInfoMap.get(userId);
            //数据填充
            QueryListInfo queryListInfo = new QueryListInfo();
            queryListInfo.setAddress(userInfo.getAddress());
            queryListInfo.setUserId(userInfo.getUserId());
            queryListInfo.setUserName(userInfo.getUserName());
            queryListInfo.setMemberId(memberInfo.getMemberId());
            queryListInfo.setMemberLevel(memberInfo.getMemberLevel());
            queryListInfo.setMemberDesc(memberInfo.getMemberDesc());
            queryListInfo.setOrderInfos(orderInfos1);
        }
目录
打赏
0
0
0
0
165
分享
相关文章
基于双向RRT算法的三维空间最优路线规划matlab仿真
本程序基于双向RRT算法实现三维空间最优路径规划,适用于机器人在复杂环境中的路径寻找问题。通过MATLAB 2022A测试运行,结果展示完整且无水印。算法从起点和终点同时构建两棵随机树,利用随机采样、最近节点查找、扩展等步骤,使两棵树相遇以形成路径,显著提高搜索效率。相比单向RRT,双向RRT在高维或障碍物密集场景中表现更优,为机器人技术提供了有效解决方案。
阿里云服务器租用价格参考:最新包年包月收费标准与活动价格整理
购买阿里云服务器一年多少钱?2025年阿里云服务器价格又调整了,轻量云服务器2核2G200M峰值带宽38元一年、2核4G4M带宽60GB ESSD云盘298元一年,e实例云服务器2核2G3M带宽99元1年,u1实例2核4G5M带宽199元一年、4核8G云服务器955元一年,4核16G10M云服务器70元1个月、210元3个月,8核32G10M带宽160元1个月、480元3个月。对于想要上云的用户来说,了解阿里云服务器的价格体系是选择合适产品的第一步。那么,2025年购买阿里云服务器到底需要多少钱呢?本文为大家整理汇总了截止目前阿里云服务器的最新包年包月收费标准和活动价格情况,以供参考。
《Flutter社交应用暗黑奥秘:模式适配与色彩的艺术》
暗黑模式已成为提升用户体验的重要功能,尤其在社交应用中,其适配与优化至关重要。对于Flutter开发者而言,实现暗黑模式不仅仅是颜色的简单反转,还需综合考虑用户体验、美学设计和技术实现。通过监听系统主题变化、配置`theme`与`darkTheme`属性,以及调整色彩对比度和文本样式,可以打造舒适且高效的界面。此外,状态管理、第三方组件适配和严格测试确保了功能的完善性。未来,智能切换和个性化调整将进一步提升用户体验,使应用在竞争中脱颖而出。这不仅是技术挑战,更是对用户需求的深度理解和艺术化呈现。
60 20
如何提升审核通过率?|阿里云短信服务
提升短信审核率的 tips 都在这里了哦!
315 13
【算法解题思想】动态规划+深度优先搜索(C/C++)
【算法解题思想】动态规划+深度优先搜索(C/C++)
Spring Boot2 系列教程(十四)CORS 解决跨域问题
Spring Boot2 系列教程(十四)CORS 解决跨域问题
Spring AI Fluent API:与AI模型通信的流畅体验
【11月更文挑战第24天】随着人工智能(AI)技术的飞速发展,越来越多的应用场景开始融入AI技术以提升用户体验和系统效率。在Java开发中,与AI模型通信成为了一个重要而常见的需求。为了满足这一需求,Spring AI引入了ChatClient,一个提供流畅API(Fluent API)的客户端,用于与各种AI模型进行通信。本文将深入探讨ChatClient的底层原理、业务场景、概念、功能点,并通过Java代码示例展示如何使用Fluent API与AI模型进行通信。
185 0
深入测评:ONLYOFFICE 8.1 桌面编辑器究竟有多强大?
深入测评:ONLYOFFICE 8.1 桌面编辑器究竟有多强大?
191 1
Android RecycleView 深度解析与面试题梳理
本文详细介绍了Android开发中高效且功能强大的`RecyclerView`,包括其架构概览、工作流程及滑动优化机制,并解析了常见的面试题。通过理解`RecyclerView`的核心组件及其优化技巧,帮助开发者提升应用性能并应对技术面试。
244 8