Java集合Stream流操作的基本使用教程分享
码指星斗 人气:0Java 中可以使用 java.util.Stream
对一个集合(实现了java.util.Collection
接口的类)做各种操作,例如:求和、过滤、排序等等。
这些操作可能是中间操作——返回一个 Stream 流,或者是终端操作——返回一个结果。
流操作并不会影响原来的集合,可以简单认为,流操作是把集合中的一个元素逐个复制放到一个首尾相接的流动的水槽中。
Stream 流支持同步执行,也支持并发执行。如果我们直接获取 stream 流,得到的是同步执行的 stream 流;如果调用方法 parallelStream
,则得到一个可以并发执行的 Stream 流。
注意:Map
不支持 Stream 流,但是它的 Key
和 Value
支持,因为它们实现了 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()
,就可以获得一个并发流,在编写代码上基本和同步流没什么区别,因此学会上面的基本用法基本足够了,实际使用过程中,根据实际情况决定如何使用即可。
加载全部内容