方便的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);
        }
相关文章
|
6月前
|
Java API 数据处理
探索 Java 8 中的 Stream 流:构建流的多种方式
探索 Java 8 中的 Stream 流:构建流的多种方式
|
6月前
|
Java
【Java】Stream流是什么,如何使用Stream流?
【Java】Stream流是什么,如何使用Stream流?
81 0
|
2月前
|
安全 Java API
Stream流式编程,让代码变优雅
Stream流式编程,让代码变优雅
|
2月前
|
Java 数据处理
Stream流的简单使用
这篇文章介绍了Java中Stream流的基本概念和使用方法。文章解释了Stream流的三类方法:获取流、中间方法和终结方法。详细讨论了如何生成Stream流,包括从Collection体系集合、Map体系集合、数组和同种数据类型的多个数据中生成流。接着,介绍了Stream流的中间操作方法,如`filter`、`limit`、`skip`、`concat`和`distinct`。文章还讨论了Stream流的终结方法,如`forEach`和`count`,以及收集方法,如`collect`。最后,通过几个例子演示了如何使用Stream流进行数据处理和收集操作。
|
4月前
|
存储 Java BI
Java基础之stream流最新版,stream流的基本操作
Java基础之stream流最新版,stream流的基本操作
50 0
|
6月前
|
Java 容器
Stream 流常见基本操作
Stream 流常见基本操作
|
5月前
|
存储 Java API
Java——Stream流(1/2):Stream流入门、Stream流的创建(认识Stream、体验Stream流、Stream流的使用步骤、获取Stream流的方法)
Java——Stream流(1/2):Stream流入门、Stream流的创建(认识Stream、体验Stream流、Stream流的使用步骤、获取Stream流的方法)
79 0
|
6月前
|
Java
Stream流教程
Stream流教程
68 0
|
6月前
|
SQL JavaScript Java
Stream 的使用,我觉得使用它是非常方便的~
Stream 的使用,我觉得使用它是非常方便的~
47 0
|
6月前
|
消息中间件 NoSQL Redis
⑨【Stream】Redis流是什么?怎么用?: Stream [使用手册]
⑨【Stream】Redis流是什么?怎么用?: Stream [使用手册]
101 0