java8-Stream流的使用

111 阅读11分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 10 天,点击查看活动详情

Stream是Java 8 API添加的一个新的抽象,称为流Stream,以一种声明性方式处理数据集合(侧重对于源数据计算能力的封装,并且支持序列与并行两种操作方式)Stream流是对集合(Collection)对象功能的增强,与Lambda表达式结合,可以提高编程效率、间接性和程序可读性。

特点 1、代码简洁:函数式编程写出的代码简洁且意图明确,使用stream接口让你从此告别for循环

2、多核友好:Java函数式编程使得编写并行程序如此简单,就是调用一下方法

流创建操作

生成流的方式主要有五种

1、Stream创建

Stream<Integer> stream = Stream.of(1,2,3,4,5);

2、Collection集合创建

List<Integer> integerList = new ArrayList<>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
        integerList.add(4);
        integerList.add(5);
Stream<Integer> listStream = integerList.stream();

3、Array数组创建

int[] intArr = {1, 2, 3, 4, 5};
IntStream arrayStream = Arrays.stream(intArr);

通过Arrays.stream方法生成流,并且该方法生成的流是数值流【即IntStream】而不是 Stream。

Stream API提供了mapToInt、mapToDouble、mapToLong三种方式将对象流【即Stream 】转换成对应的数值流,同时提供了boxed方法将数值流转换为对象流

4、文件创建

try {
		Stream<String> fileStream = Files.lines(Paths.get("data.txt"), Charset.defaultCharset());
} catch (IOException e) {
		e.printStackTrace();
}

通过Files.line方法得到一个流,并且得到的每个流是给定文件中的一行

5、函数创建

iterator

Stream<Integer> iterateStream = Stream.iterate(0, n -> n + 2).limit(5);

iterate方法接受两个参数,第一个为初始化值,第二个为进行的函数操作,因为iterator生成的流为无限流,通过limit方法对流进行了截断,只生成5个偶数

generator

Stream<Double> generateStream = Stream.generate(Math::random).limit(5);

generate方法接受一个参数,方法参数类型为Supplier ,由它为流提供值。generate生成的流也是无限流,因此通过limit对流进行了截断

操作符

流的操作类型主要分为两种:中间操作符、终端操作符

(一)中间操作符

通常对于Stream的中间操作,可以视为是源的查询,并且是懒惰式的设计,对于源数据进行的计算只有在需要时才会被执行,与数据库中视图的原理相似;Stream流的强大之处便是在于提供了丰富的中间操作,相比集合或数组这类容器,极大的简化源数据的计算复杂度。一个流可以跟随零个或多个中间操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的,仅仅调用到这类方法,并没有真正开始流的遍历,真正的遍历需等到终端操作时,常见的中间操作有下面即将介绍的 filter、map 等。

流方法含义
filter用于通过设置的条件过滤出元素
map接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素(使用映射一词,是因为它和转换类似,但其中的细微差别在于它是“创建一个新版本”而不是去“修改”)
distinct返回一个元素各异(根据流所生成元素的hashCode和equals方法实现)的流
sorted返回排序后的流
limit会返回一个不超过给定长度的流
skip返回一个扔掉了前n个元素的流
flatMap使用flatMap方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所有使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化为一个流
peek对元素进行遍历处理

起步准备

创建实体类

@Data
@AllArgsConstructor
public class User {
    private int id;
    private String name;
    private int age;
    private String city;
}

添加数据

private static List<User> getUserList() {
        List<User> userList = new ArrayList<>();

        userList.add(new User(1,"张三",18,"上海"));
        userList.add(new User(2,"王五",16,"上海"));
        userList.add(new User(3,"李四",20,"上海"));
        userList.add(new User(4,"张雷",22,"北京"));
        userList.add(new User(5,"张超",15,"深圳"));
        userList.add(new User(6,"李雷",24,"北京"));
        userList.add(new User(7,"王爷",21,"上海"));
        userList.add(new User(8,"张三丰",18,"广州"));
        userList.add(new User(9,"赵六",16,"广州"));
        userList.add(new User(10,"赵无极",26,"深圳"));

        return userList;
}
public static void main(String[] args) {
	      List<User> userList = getUserList();
}

1、filter

用于通过设置的条件过滤出元素

//1、filter:输出ID大于6的user对象
List<User> filterUserList = userList.stream()
                .filter(user -> user.getId() > 6)
                .collect(Collectors.toList());
filterUserList.forEach(System.out::println);

2、map

接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素(使用映射一词,是因为它和转换类似,但其中的细微差别在于它是“创建一个新版本”而不是去“修改”)

//2、map
List<String> mapUserList = userList.stream()
       .map(user -> user.getName() + "用户")
       .collect(Collectors.toList());
mapUserList.forEach(System.out::println);

3、distinct:去重

返回一个元素各异(根据流所生成元素的hashCode和equals方法实现)的流

//3、distinct:去重
List<String> distinctUsers =  userList.stream()
         .map(User::getCity)
         .distinct()
         .collect(Collectors.toList());
distinctUsers.forEach(System.out::println);

4、sorted

返回排序后的流

//4、sorted:排序,根据名字倒序
userList.stream()
        .sorted(Comparator.comparing(User::getName).reversed())
        .collect(Collectors.toList())
  			.forEach(System.out::println);

5、limit

会返回一个不超过给定长度的流

//5、limit:取前5条数据
userList.stream()
        .limit(5)
        .collect(Collectors.toList())
	    	.forEach(System.out::println);

6、skip

返回一个扔掉了前n个元素的流

//6、skip:跳过第几条取后几条
userList.stream()
       .skip(7)
       .collect(Collectors.toList())
       .forEach(System.out::println);

7、flatMap

使用flatMap方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所有使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化为一个流

//7、flatMap:数据拆分一对多映射
userList.stream()
        .flatMap(user -> Arrays.stream(user.getCity().split(",")))
        .forEach(System.out::println);

map:对流中每一个元素进行处理 flatMap:流扁平化,让你把一个流中的“每个值”都换成另一个流,然后把所有的流连接起来成为一个流 本质区别:map是对一级元素进行操作,flatmap是对二级元素操作map返回一个值;flatmap返回一个流,多个值

应用场景:map对集合中每个元素加工,返回加工后结果;flatmap对集合中每个元素加工后,做扁平化处理后(拆分层级,放到同一层)然后返回

8、peek

对元素进行遍历处理

//8、peek:对元素进行遍历处理,每个用户ID加1输出
userList.stream()
        .peek(user -> user.setId(user.getId()+1))
        .forEach(System.out::println);

(二)终端操作符

Stream流执行完终端操作之后,无法再执行其他动作,否则会报状态异常,提示该流已经被执行操作或者被关闭,想要再次执行操作必须重新创建Stream流

一个流有且只能有一个终端操作,当这个操作执行后,流就被关闭了,无法再被操作,因此一个流只能被遍历一次,若想在遍历需要通过源数据在生成流。

终端操作的执行,才会真正开始流的遍历。如 count、collect 等

流方法含义
collect收集器,将流转换为其他形式
forEach遍历流
findFirst返回第一个元素
findAny将返回当前流中的任意元素
count返回流中元素总数
sum求和
max最大值
min最小值
anyMatch检查是否至少匹配一个元素,返回boolean
allMatch检查是否匹配所有元素,返回boolean
noneMatch检查是否没有匹配所有元素,返回boolean
reduce可以将流中元素反复结合起来,得到一个值

1、collect

收集器,将流转换为其他形式

//1、collect:收集器,将流转换为其他形式
        Set<User> set = userList.stream()
                .collect(Collectors.toSet());
        set.forEach(System.out::println);
        System.out.println("--------------------------");
        List list = userList.stream()
                .collect(Collectors.toList());
        list.forEach(System.out::println);

2、forEach

遍历流

//2、forEach:遍历流
userList.stream()
  			.forEach(user -> System.out.println(user));
userList.stream()
  			.filter(user -> "上海".equals(user.getCity()))
  			.forEach(System.out::println);

3、findFirst

//3、findFirst:返回第一个元素
User firstUser = userList.stream()
                .findFirst()
                .get();
System.out.println("firstUser:" + firstUser);
User firstUser1 = userList.stream()
                .filter(user -> "北京".equals(user.getCity()))
                .findFirst()
                .get();
System.out.println("firstUser(北京):" + firstUser);

4、findAny

//4、findAny:将返回当前流中的任意元素
        User findUser2 = userList.stream()
                .findAny()
                .get();
        System.out.println("findUser2:" + findUser2);
        User findUser3 = userList.stream()
                .filter(user -> "北京".equals(user.getCity()))
                .findAny()
                .get();
        System.out.println("findUser3:" + findUser3);

5、count

//5、count:返回流中元素总数
long count = userList.stream()
        .filter(user -> user.getAge() > 20)
        .count();
System.out.println(count);

6、sum

//6、sum:求和
int sum = userList.stream().mapToInt(User::getId).sum();
System.out.println(sum);

7、max

//7、max:最大值
int max = userList.stream()
         .max(Comparator.comparingInt(User::getId))
         .get()
         .getId();

8、最小值

//8、min:最小值
int min = userList.stream().min(Comparator.comparingInt(User::getId)).get().getId();
System.out.println(min);

9、anyMatch

检查是否至少匹配一个元素,返回boolean

boolean matchAny = userList.stream()
                .anyMatch(user -> "北京".equals(user.getCity()));
System.out.println(matchAny);

10、allMatch

//10、allMatch:检查是否匹配所有元素
boolean matchAll = userList.stream().allMatch(user -> "北京".equals(user.getCity()));

11、noneMatch

检查是否没有匹配所有元素,返回boolean

//11、noneMatch:检查是否没有匹配所有元素,返回boolean
boolean nonaMatch = userList.stream().allMatch(user -> "云南".equals(user.getCity()));

12、reduce

详细请看这篇文章:(48条消息) JAVA8 Stream流之reduce()方法详解_Carino_U的博客-CSDN博客_stream流reduce方法

可以将流中元素反复结合起来,得到一个值

//12、reduce:将流中元素反复结合起来,得到一个值
Optional reduce = userList.stream().reduce((user, user2) -> {
      return user;
});
if(reduce.isPresent()) System.out.println(reduce.get());

(三)Collect收集

Collector:结果收集策略的核心接口,具备将指定元素累加存放到结果容器中的能力;并在Collectors工具中提供了Collector接口的实现类

1、toList

将用户ID放到List集合中

List<Integer> idList = userList.stream().map(User::getId).collect(Collectors.toList()) ;

2、toMap

将用户ID和Name以Key-Value形式存放到Map集合中

Map<Integer,String> userMap = userList.stream().collect(Collectors.toMap(User::getId,User::getName));

3、toSet

将用户所在城市存放到Set集合中

Set<String> citySet = userList.stream().map(User::getCity).collect(Collectors.toSet());

4、counting

long count = userList.stream().filter(user -> user.getId()>1).collect(Collectors.counting());

5、summingInt

对结果元素即用户ID求和

Integer sumInt = userList.stream().filter(user -> user.getId()>2).collect(Collectors.summingInt(User::getId)) 

6、minBy

筛选元素中ID最小的用户

User maxId = userList.stream().collect(Collectors.minBy(Comparator.comparingInt(User::getId))).get() ;

7、joining

将用户所在城市,以指定分隔符链接成字符串;

String joinCity = userList.stream().map(User::getCity).collect(Collectors.joining("||"));

8、groupingBy

按条件分组,以城市对用户进行分组;

Map<String,List<User>> groupCity = userList.stream().collect(Collectors.groupingBy(User::getCity));

9、orElse(null)和orElseGet(null)

1、orElse(null) 表示如果一个都没找到返回null(orElse()中可以塞默认值。如果找不到就会返回orElse中设置的默认值)

2、orElseGet(null) 表示如果一个都没找到返回null(orElseGet()中可以塞默认值。如果找不到就会返回orElseGet中设置的默认值)

orElse(null)和orElseGet(null)区别:

在执行stream().filter()方法时,即使orElse没有值 也会执行 orElse 内的方法, 而 orElseGet则不会

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
 
public class TestStream {
    public static void main(String[] args) {
        List<User> list = new ArrayList<>();
 
        //定义三个用户对象
        User user1 = new User();
        user1.setUserName("admin");
        user1.setAge(16);
        user1.setSex("男");
 
        User user2 = new User();
        user2.setUserName("root");
        user2.setAge(20);
        user2.setSex("女");
 
        User user3 = new User();
        user3.setUserName("admin");
        user3.setAge(18);
        user3.setSex("男");
 
        User user4 = new User();
        user4.setUserName("admin11");
        user4.setAge(22);
        user4.setSex("女");
 
        //添加用户到集合中
        list.add(user1);
        list.add(user2);
        list.add(user3);
        list.add(user4);
 
        /*
        在集合中查询用户名包含admin的集合
        */
        List<User> userList = list.stream().filter(user -> user.getUserName().contains("admin")
                            && user.getAge() <= 20).collect(Collectors.toList());
        System.out.println(userList);
 
        /*
        在集合中查询出第一个用户名为admin的用户
        */
        Optional<User> user = list.stream().filter(userTemp -> "admin".equals(userTemp.getUserName())).findFirst();
        System.out.println(user);
 
        /*
        orElse(null)表示如果一个都没找到返回null(orElse()中可以塞默认值。如果找不到就会返回orElse中设置的默认值)
        orElseGet(null)表示如果一个都没找到返回null(orElseGet()中可以塞默认值。如果找不到就会返回orElseGet中设置的默认值)
        orElse()和orElseGet()区别:在使用方法时,即使没有值 也会执行 orElse 内的方法, 而 orElseGet则不会
        */
        //没值
        User a =  list.stream().filter(userT-> userT.getAge() == 12).findFirst().orElse(getMethod("a"));
        User b =  list.stream().filter(userT11-> userT11.getAge() == 12).findFirst().orElseGet(()->getMethod("b"));
        //有值
        User c =  list.stream().filter(userT2-> userT2.getAge() == 16).findFirst().orElse(getMethod("c"));
        User d =  list.stream().filter(userT22-> userT22.getAge() == 16).findFirst().orElseGet(()->getMethod("d"));
        System.out.println("a:"+a);
        System.out.println("b:"+b);
        System.out.println("c:"+c);
        System.out.println("d:"+d);
    }
 
    public static User getMethod(String name){
        System.out.println(name + "执行了方法");
 
        return null;
    }
}

其他

创建实体类

@Data
@AllArgsConstructor
public class Grade {

  private String stuNo;

  private String stuName;

  private String stuClass;

  private Double chineseScore;

  private Double englishScore;

  private Double mathScore;

  @Override
  public String toString() {
    return "Grade{" +
      "stuNo='" + stuNo + '\'' +
      ", stuName='" + stuName + '\'' +
      ", stuClass='" + stuClass + '\'' +
      ", chineseScore=" + chineseScore +
      ", englishScore=" + englishScore +
      ", mathScore=" + mathScore +
      '}' + "\n";
  }
}

方法类

/**
 * Steam流处理List
 *
 * 1、源头:源数据(集合、数组)转化为Stream流对象
 * 2、管道(流):对Stream流对象中的元素进行一系列过滤、排序、分组等中间操作,并返回一个Steam流对象
 * 3、终点:按照需求,对Stream流进行遍历,收集、统计等操作
 */
public class StreamList {

  /**
   * 筛选语文成绩大于60分的同学
   * @param gradeList 成绩列表
   * @return 语文成绩大于60分的列表
   */
  public List<Grade> filterByStream(List<Grade> gradeList) {
    return gradeList.stream()
      .filter(Objects::nonNull)
      .filter(grade -> grade.getChineseScore() > 60)
      .collect(Collectors.toList());
  }

  /**
   * 按照语文成绩排序
   * @param gradeList 成绩列表
   * @return 按照语文成绩排序完成的列表
   */
  public List<Grade> sortByStream(List<Grade> gradeList) {
    return gradeList.stream()
      .filter(Objects::nonNull)
      .sorted((Comparator.comparingDouble(Grade::getChineseScore)))
      .collect(Collectors.toList());
  }

  /**
   * 按照班级分组(获取每个班级的成绩列表)
   * @param gradeList 成绩列表
   * @return 按照成绩分组,返回的Map
   */
  public Map<String, List<Grade>> groupByStream(List<Grade> gradeList) {
    return gradeList.stream()
      .filter(Objects::nonNull)
       .collect(Collectors.groupingBy(Grade::getStuClass));
  }

  /**
   * 求语文成绩的平均分
   * @param gradeList 成绩列表
   * @return 语文成绩的平均分
   */
  public Double avgBySteam(List<Grade> gradeList) {
    return gradeList.stream()
      .filter(Objects::nonNull)
      .mapToDouble(Grade::getChineseScore)
      .average().orElseThrow(IllegalArgumentException::new);
  }

  /**
   * 求英语成绩平均分
   * @param gradeList 成绩列表
   * @return 英语成绩的平均分
   */
  public double sumByStream(List<Grade> gradeList) {
    double totalScore = gradeList.stream().
      filter(Objects::nonNull)
      .mapToDouble(Grade::getEnglishScore).sum();
    return totalScore / (double) gradeList.size();
  }
}

测试类

public class TestStreamList {

  private static final List<Grade> gradeList;

  static {
    Grade aliceGrade = new Grade("202201", "alice", "101", 90.0, 89.0, 70.0);
    Grade bobGrade = new Grade("202202", "bob", "101", 91.0, 87.0, 75.0);
    Grade michaleGrade = new Grade("202203", "michale", "102", 91.0, 87.0, 75.0);
    Grade janeGrade = new Grade("202204", "jane", "102", 89.0, 80.0, 78.0);
    Grade jackGrade = new Grade("202205", "jack", "102", 93.0, 67.0, 78.0);
    gradeList = Arrays.asList(aliceGrade, bobGrade, michaleGrade, janeGrade, jackGrade);
  }

  @Test
  public void TestFilterByStream() {
    StreamList streamList = new StreamList();
    final List<Grade> grade = streamList.filterByStream(gradeList);
    System.out.println(grade);
  }

  @Test
  public void TestSortByStream() {
    StreamList streamList = new StreamList();
    final List<Grade> grade = streamList.sortByStream(gradeList);
    System.out.println(grade);
  }

  @Test
  public void TestGroupByStream() {
    StreamList streamList = new StreamList();
    Map<String, List<Grade>> stringListMap = streamList.groupByStream(gradeList);
    System.out.println(stringListMap);
  }

  @Test
  public void TestAvgByStream() {
    StreamList streamList = new StreamList();
    final Double aveGrade = streamList.avgBySteam(gradeList);
    System.out.println(aveGrade);
  }

  @Test
  public void TestSumByStream() {
    StreamList streamList = new StreamList();
    final double sumGrade = streamList.sumByStream(gradeList);
    System.out.println(sumGrade);
  }
}

测试结果