stream
一、什么是stream
流(stream)是支持顺序和并行聚合操作的元素序列。
集合(Collection)和流虽然有一些表面上的相似之处,但有着不同的目标。集合(Collection)主要是对元素的有效管理和访问。相比之下,流(Stream)不提供直接访问或操作其元素的方法,而是关注于声明性地描述其源以及将在该源上聚合执行的计算操作。
在Java 8中,stream(java.util.stream)专注于对集合(Collection)对象进行各种聚合操作(aggregate operation),例如过滤集合不需要的数据、集合之间的转换、对集合中的数据分组等等。是java 8中对集合功能的增强。
/**
A sequence of elements supporting sequential and parallel aggregate operations.
(支持顺序和并行聚合操作的元素序列。)
The following example illustrates an aggregate operation using Stream and IntStream:
*/
//对其进行筛选以生成只包含红色小部件的流,然后将其转换为表示每个红色小部件的权重的int值流。然后对该流求和,得到它的和。
int sum = widgets.stream()
.filter(w -> w.getColor() == RED)
.mapToInt(w -> w.getWeight())
.sum();
二、stream(流)的构成
stream一般由三部分构成,source(数据源)、intermediate(中间操作)、terminal(终端操作)。
source(数据源):可能是数据结构、数组、generator function、IO channe
Intermediate(中间操作):0个或多个中间操作(它们将一个流转换成另一个流)。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历,只有在遇到terminal的时候融合起来,一次循环完成。
(举例:在对于一个 Stream 进行多次转换操作 (Intermediate 操作),每次都对 Stream 的每个元素进行转换,而且是执行多次,这样时间复杂度就是 N(转换次数)个 for 循环里把所有操作都做掉的总和吗?
不是,只有在遇到terminal的时候融合起来,一次循环完成。可以这样简单的理解,Stream 里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在 Terminal 操作的时候循环 Stream 对应的集合,然后对每个元素执行所有的函数。
Terminal(终端操作):一个终端操作(有且只有一个),只有操作terminal的时候,才会开始流的遍历。当执行完这个操作之后,这个“流”将无法再被使用。
三、Stream特性
- 不是数据结构:它没有内部存储,它只是用操作管道从 source(数据结构、数组、generator function、IO channel)抓取数据。
- 绝不修改自己的源数据:例如 Stream 的 filter 操作会产生一个不包含被过滤元素的新 Stream,而不是从 source 删除那些元素。
- 不支持索引访问:stream不支持索引访问,但是很容易生成新的数组和集合 。
- 惰性化:很多 Stream 操作是向后延迟的,一直到它弄清楚了最后需要多少数据才会开始。Intermediate 操作永远是惰性化的。
- 并行能力:当一个 Stream 是并行化的,就不需要再写多线程代码,所有对它的操作会自动并行进行的。
- 可以是无限的:集合有固定大小,Stream 则不必。limit(n) 和 findFirst() 这类的 short-circuiting 操作可以对无限的 Stream 进行运算并很快完成。
- 参数情况:通常大部分 Stream 的操作以 lambda 表达式为参数。
四、Stream的使用
4.1 流的构造与使用
// 1.返回元素为指定值的顺序有序流。
Stream stream = Stream.of("a", "b", "c");
// 2. Arrays 数组
String [] strArray = new String[] {"a", "b", "c"};
stream = Stream.of(strArray);
stream = Arrays.stream(strArray);
// 3. Collections 集合
List<String> list = Arrays.asList(strArray);
stream = list.stream();
4.2 常用中间操作符
map
Stream map(Function<? super T, ? extends R> mapper)
返回由将给定函数应用于此流的元素的结果组成的流。就是将输入流中的每一个元素按照一定的规则成输出流的另外一个元素(1:1映射),并且可以改变返回的类型,返回各种类型的集合,或者对数字类型的,返回求和,最大,最小等的操作。
/**
返回由将给定函数应用于此流的元素的结果组成的流。
*/
<R> Stream<R> map(Function<? super T,? extends R> mapper)
举例:
//将学生的姓名独自输出为List
List<String> studentNameList = studentList.stream().map(Student::getName).collect(Collectors.toList());
// 输出一个数组的平方数平方数
List<Integer> nums = Arrays.asList(1, 2, 3, 4);
List<Integer> squareNums = nums.stream().
map(n -> n * n).
collect(Collectors.toList());
flatMap
-
Stream flatMap(Function<? super T,? extends Stream<? extends R>> mapper):
接收一个Fucntion的函数式接口,Function接收第一个参数是泛型参数,第二个参数是一个Stream流(应用于生成新值流的每个元素);
返回一个流,该流中每个元素,都被映射流的元素所替代。每个映射流的内容替换到之前的流后,映射流关闭。(如果映射流为空,则使用空流。)
简单的说:flatMap操作的效果是对流的元素应用一对多的转换,然后将生成的元素展平到一个新的流中。
举个栗子:
// 一个订单中有多个item,获取多个订单下的全部item的id List<MyOrder> orderList = new ArrayList<>(); List<Item> itemList1 = new ArrayList<>(); Item item1 = new Item(1); Item item2 = new Item(2); List<Item> itemList2 = new ArrayList<>(); Item item3 = new Item(3); Item item4 = new Item(4); itemList1.add(item1); itemList1.add(item2); itemList2.add(item3); itemList2.add(item4); MyOrder myOrder1 = new MyOrder(1, itemList1); MyOrder myOrder2 = new MyOrder(2, itemList2); orderList.add(myOrder1); orderList.add(myOrder2); List<Integer> itemIdList = orderList.stream().flatMap(myOrder -> myOrder.getItemList().stream().map(item -> item.getItemId())).collect(toList()); System.out.println(itemIdList); // 输出结果为:[1, 2, 3, 4]
fliter
-
Stream filter(Predicate<? super T> predicate)
参数:接收一个非干扰、无状态的谓语(Predicate),该谓语用于当前流中的每个元素是否符合该谓语的条件限制。即该参数就是判断条件,符合条件的则将会被返回。
返回:返回一个流
应用:根据条件筛选需要的数据,例如List集合实现交集、差集等操作,获取姓名为刘开头的,获取分数大于100的数据等等。并且可以写多个Predicate条件,可以用与或非门(and、or、!)组合使用。
举个栗子:
List<Student> list1 = new ArrayList<>(); list1.add(new Student("name1","1001")); list1.add(new Student("name2","1002")); list1.add(new Student("name3","1003")); List<Student> list2 = new ArrayList<>(); list2.add(new Student("name3","1003")); list2.add(new Student("name4","1004")); Set<Student> list1Set = new HashSet<>(list1); Set<Student> list2Set = new HashSet<>(list2); // 交集, 获取List1中和List2中相同的数据 List<Student> intersection = list1.stream().filter(list2Set::contains).collect(toList()); // 差集 (list1 - list2) List<Student> reduce1 = list1.stream().filter(item -> !list2Set.contains(item)).collect(toList()); Predicate<String> startsWith_S = str -> str.startsWith("S"); Predicate<String> contains_m = str -> str.contains("m"); //与门and的使用 names = asList("Larry", "Jeremy", "Sam", "Simon", "Mike"); result = names.stream() .filter(startsWith_S.and(contains_m)) .collect(C ollectors.toList());
limit
-
Stream limit(long maxSize)
limit 限流操作,返回由此流的元素组成的流,截断后的长度不超过maxSize。比如数据流中有10个 我只要出前3个就可以使用。
-
Stream distinct()
distint 去重操作,对于有序流来说,不同元素的选择是稳定的(遇到重复的元素,保留在遇到顺序中第一个出现的元素)。对于无序流,不提供稳定性保证。
-
Stream peek(Consumer<? super T> action)
返回一个由该流的元素组成的流,当元素从结果流中被消耗时,另外对每个元素执行所提供的操作。即peek 挑出操作,可以对被操作的数据进行额外的某些操作,如:读取、编辑修改等。
注意:此方法主要用于支持调试,在调试中,您希望在元素流过管道中的某个点时查看它们。
Stream.of("one", "two", "three", "four") .filter(e -> e.length() > 3) .peek(e -> System.out.println("被过滤出来的值: " + e)) .map(String::toUpperCase) .peek(e -> System.out.println("被map操作的值 : " + e)) .collect(Collectors.toList());
被过滤出来的值: three 被map操作的值 : THREE 被过滤出来的值: four 被map操作的值 : FOUR
skip
-
Stream skip(long n)
在丢弃流的前n个元素后,返回由该流的其余元素组成的流。如果此流包含少于n个元素,则将返回空流。这是一个有状态的中间操作。
-
Stream sorted(Comparator<? super T> comparator)
sorted(unordered) 排序操作,对元素排序,前提是实现Comparable接口,当然也可以自定义比较器。
根据提供的比较器排序,返回由该流的元素组成的流。对于有序流,排序是稳定的。对于无序流,不提供稳定性保证。
举个栗子:
//先按姓名升序排序,再按年龄升序排序 list.stream() .sorted((e1, e2) -> { if (e1.getAge() == e2.getAge()){ return e1.getName().compareTo(e2.getName()); }else{ return Integer.compare(e1.getAge(), e2.getAge()); } }).forEach(System.out::println);
4.3 常用终止操作符
collect
-
<R,A> R collect(Collector<? super T,A,R> collector)
collect 收集操作,使用收集器对此流的元素执行可变的缩减操作。这个操作非常重要,官方的提供的Collectors 提供了非常多收集器,可以说Stream 的核心在于Collectors。
Collector:实现各种有用的缩减操作,例如:将原始积累到集合中。
举个栗子:
//Collectors.toList() List<String> list = people.stream().map(Person::getName).collect(Collectors.toList()); Set<String> set = people.stream().map(Person::getName).collect(Collectors.toCollection(TreeSet::new)); //Collectors.joining(",") 将元素转换为字符串并连接,用逗号分隔 String joined = things.stream().map(Object::toString).collect(Collectors.joining(",")); //Collectors.groupingBy:分组 //按部门分组 Map<Department, List<Employee>> byDept= employees.stream().collect(Collectors.groupingBy(Employee::getDepartment)); //Collectors.summingInt 计算总额 int total = employees.stream().collect(Collectors.summingInt(Employee::getSalary))); // Collectors.partitioningBy 划分依据 // 将学生分为及格和不及格 Map<Boolean, List<Student>> passingFailing =students.stream().collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD)); // list转换成map (当key有重复数据会报错 Map<String, User> maps2 = list.stream().collect(Collectors.toMap(User::getName, Function.identity())); // list转换成map 我们不知道 key 是否有重复时,可以用 (k1,k2)->k1 来设置,如果有重复的key,则保留key1,舍弃key2 Map<Integer, User> maps = list.stream().collect(Collectors.toMap(User::getAge, a -> a, (k1, k2) -> k1)); // list转换成map 要求map的顺序要按照list的执行 Map<String, User> maps3 = list.stream().collect(Collectors.toMap(User::getName,Function.identity(),(k1, k2) -> k1,LinkedHashMap::new));
其他
-
A[] toArray(IntFunction<A[]> generator)
toArray 数组操作,将数据流的元素转换成数组。
举个栗子:
Person[] men = people.stream().filter(p -> p.getGender() == MALE).toArray(Person[]::new);
-
findFirst、findAny 查找操作,查找第一个、查找任何一个 返回的类型为Optional。
-
noneMatch、allMatch、anyMatch 匹配操作,数据流中是否存在符合条件的元素 返回值为bool 值。
-
min、max 最值操作,需要自定义比较器,返回数据流中最大最小的值。
-
reduce 规约操作,将整个数据流的值规约为一个值,count、min、max底层就是使用reduce。
-
forEach、forEachOrdered 遍历操作,这里就是对最终的数据进行消费了。
-
long count()
count 统计操作,统计最终的数据个数。这是缩减一种的特殊情况。相当于:mapToLong(e->1L).sum();
五、stream流中的元素顺序问题
待续
参考:
https://blog.csdn.net/weixin_38003389/article/details/88596257
https://mp.weixin.qq.com/s/vxZthSjG3zNYNFd2oDZK3g
https://mp.weixin.qq.com/s/g3zk1LTDDyEGRDkMP5b9Qw
https://developer.ibm.com/zh/articles/j-lo-java8streamapi/