Java 中能够运用 java.util.Stream 对一个调集(完成了java.util.Collection接口的类)做各种操作,例如:求和、过滤、排序等等。

这些操作可能是中心操作——回来一个 Stream 流,或者是终端操作——回来一个结果。

流操作并不会影响本来的调集,能够简略认为,流操作是把调集中的一个元素逐一仿制放到一个首尾相接的流动的水槽中。

Stream 流支撑同步履行,也支撑并发履行。假如咱们直接获取 stream 流,得到的是同步履行的 stream 流;假如调用办法 parallelStream,则得到一个能够并发履行的 Stream 流。

❗❗ 注意:Map不支撑 Stream 流,可是它的 KeyValue 支撑,由于它们完成了 Set 接口。

事前预备

演示 Stream 流的提前预备,创立几个类以供测验

  • 新建一个东西类,便利创立调集。
  • 新建两个类,例如开发中常见的数据库实体类和 DTO 类。
public class MyUtil {
​
  private static List<String> list = new ArrayList<>();
  private static List<Student> students = new ArrayList<>();
​
  static {
    list.add("abc");
    list.add("xyz");
    list.add("fgh");
    list.add("abc");
    list.add("def");
    list.add("xyz");
    list.add("efg");
​
    Student s1 = new Student();
    s1.setAge("16");
    s1.setId(UUID.randomUUID().toString());
    s1.setName("张三");
    s1.setMajor("计算机科学与技能");
    Student s2 = new Student();
    s2.setAge("18");
    s2.setId(UUID.randomUUID().toString());
    s2.setName("李四");
    s2.setMajor("物联网工程");
    Student s3 = new Student();
    s3.setAge("20");
    s3.setId(UUID.randomUUID().toString());
    s3.setName("王五");
    s3.setMajor("网络工程");
    students.add(s1);
    students.add(s2);
    students.add(s3);
   }
​
  public static List<String> getList() {
    return list;
   }
  public static List<Student> getStudents() {
    return students;
   }
}
​
public class Student {
​
  private String id;
  private String name;
  private String age;
  private String major;
  
}
​
public class StudentDTO {
​
  private String name;
  private String major;
}

Filter

filter 能够协助咱们过滤流中的某些元素,其办法签名如下

/*
过滤操作,
Predicate 相当于一个谓词,即断语流中的元素满意某个条件,回来一个 布尔值
*/
Stream<T> filter(Predicate<? super T> predicate);

详细运用办法如下:

public class Main {
​
  public static void main(String[] args) {
    List<String> list = MyUtil.getList();
    System.out.println("过滤操作之前:");
    System.out.println(list);
    // 过滤不以 a 最初的字符串,collect() 将流中的元素放到一个新的调集中
    List<String> newList = list.stream().filter(s -> !s.startsWith("a")).collect(Collectors.toList());
    System.out.println("-------------------------");
    System.out.println("过滤操作之后:");
    System.out.println(newList);
   }
}
​
======== 输出 =========
过滤操作之前:
[abc, xyz, fgh, abc, def, xyz, efg]
-------------------------
过滤操作之后:
[xyz, fgh, def, xyz, efg]

Sorted

sorted 能够协助咱们排序流中的元素,办法签名如下:

/*
中心操作,传入一个 Comparator,对流中的元素进行排序,假如不传入,则运用默认的 Comparable 排序
对原调集不影响
*/
Stream<T> sorted(Comparator<? super T> comparator);

详细运用办法如下:

public class Main {
​
  public static void main(String[] args) {
    List<String> list = MyUtil.getList();
    System.out.println("排序操作之前:");
    System.out.println(list);
    List<String> newList = list.stream().sorted().collect(Collectors.toList());
    System.out.println("-------------------------");
    System.out.println("排序操作之后:");
    System.out.println(newList);
    System.out.println("自定义排序:");
    // 倒序排序。 forEach 办法能够用传入的办法 逐一 处理流中的元素
    list.stream().sorted((s1, s2)-> -s1.compareTo(s2)).forEach(System.out::println);
   }
}
​
======== 输出 =========
排序操作之前:
[abc, xyz, fgh, abc, def, xyz, efg]
-------------------------
排序操作之后:
[abc, abc, def, efg, fgh, xyz, xyz]
自定义排序:
xyz
xyz
fgh
efg
def
abc
abc

Map

Map 操作能够协助咱们将流中的一类元素映射为另一类元素,最典型的运用就是能够用来将数据库实体类转换为供前端运用的 DTO 类。办法签名如下:

/*
中心操作,能够将一个目标转化为另一个目标
例如做 DTO 数据转换
*/
<R> Stream<R> map(Function<? super T, ? extends R> mapper);

详细运用办法如下:

public class Main {
​
  public static void main(String[] args) {
    List<Student> students = MyUtil.getStudents();
    System.out.println("map 操作之前");
    System.out.println(students);
    // collect 办法能够将流中的元素收集到一个 Collection 中,假如有去除重复元素的需求,能够考虑收集到 Set 中
    List<StudentDTO> dtos = students.stream().map(student -> {
      StudentDTO dto = new StudentDTO();
      dto.setName(student.getName());
      dto.setMajor(student.getMajor());
      return dto;
     }).collect(Collectors.toList());
    System.out.println("-------------------------");
    System.out.println("map 操作之后");
    System.out.println(dtos);
   }
}
​
======== 输出 =========
map 操作之前
[Student{id='cb5726cd-e73a-443e-95e5-155aa6e876ae', name='张三', age='16', major='计算机科学与技能'}, Student{id='94478bae-b2ee-4c43-bac0-12f45f4099cd', name='李四', age='18', major='物联网工程'}, Student{id='5fdd9e19-f7cf-4c61-b506-0ef58a36dcbe', name='王五', age='20', major='网络工程'}]
-------------------------
map 操作之后
[StudentDTO{name='张三', major='计算机科学与技能'}, StudentDTO{name='李四', major='物联网工程'}, StudentDTO{name='王五', major='网络工程'}]

Match

/*
终端操作,能够用来匹配操作,回来一个 boolean 值
能够便利地匹配调集中是否存在某种元素
*/
// 只需调集中有一个匹配,就回来 true
boolean anyMatch(Predicate<? super T> predicate);
// 调集中一切元素都匹配,才回来 true
boolean allMatch(Predicate<? super T> predicate);
// 调集中一切元素都不匹配,回来 true
boolean noneMatch(Predicate<? super T> predicate);

详细运用办法如下:

public class Main {
​
  public static void main(String[] args) {
    List<String> list = MyUtil.getList();
    System.out.println("调集中的一切元素是否都以 a 最初");
    System.out.println(list.stream().allMatch(s -> s.startsWith("a")));
​
    System.out.println("调集中是否存在元素以 a 最初");
    System.out.println(list.stream().anyMatch(s -> s.startsWith("a")));
​
    System.out.println("调集中的元素是否都不以 a 最初(相当于 allMatch 的取反):");
    System.out.println(list.stream().noneMatch(s -> s.startsWith("a")));
   }
}
​
======== 输出 =========
调集中的一切元素是否都以 a 最初
false
调集中是否存在元素以 a 最初
true
调集中的元素是否都不以 a 最初(相当于 allMatch 的取反):
false

Count

/*
终端操作,回来 stream 流中及调集中的元素个数,回来一个 long 类型
*/
long count();

详细运用办法如下:

public class Main {
​
  public static void main(String[] args) {
    List<String> list = MyUtil.getList();
    System.out.println(list);
    System.out.println("调集中的个数:" + list.size());
​
    long count = list.stream().filter(s -> s.startsWith("a")).count();
    System.out.println("调集中以 a 最初的元素个数:" + count);
   }
}
​
======== 输出 =========
[abc, xyz, fgh, abc, def, xyz, efg]
调集中的个数:7
调集中以 a 最初的元素个数:2

Reduce

/*
终端操作,能够理解为减少调集的个数,对调集中的元素不断进行累加,最终只得到一个元素
Optional 包括一个目标,能够避免空指针异常
*/
Optional<T> reduce(BinaryOperator<T> accumulator);

详细运用办法如下:

public class Main {
​
  public static void main(String[] args) {
    List<String> list = MyUtil.getList();
    // 能够理解为减少调集的个数,对调集中的元素不断进行累加,最终只得到一个元素
    // 例如对数字调集进行累加进行求和
    String s = list.stream().reduce((s1, s2) -> s1 + "###" + s2).get();
    System.out.println(s);
   }
}
​
======== 输出 =========
abc###xyz###fgh###abc###def###xyz###efg

总结

能够看到,stream 流操作并没有什么运用难度,但假如不熟悉 Lambda 表达式开发和函数引用,则运用起来可能会稍微吃力些。

置于并发流的运用,只需要运用调集的办法parallelStream(),就能够获得一个并发流,在编写代码上基本和同步流没什么区别,因此学会上面的基本用法基本足够了,实际运用过程中,根据实际情况决议怎么运用即可。