Stream流操作

1.说明

stream流操作是java8引入的一个新概念,是一种对Java集合运算和表达的高阶抽象。主要是java8用来处理集合的,使我们的代码更简洁高效。
特点:

  • 大大提高编码效率和降低代码的复杂度
  • 不是数据结构,不会保存数据
  • 要有终端操作流才会进行处理,也就是开始流动,如果没有终端操作的话,则流的处理操作不会执行
  • 有串行和并行两种,但是并行的时候处理的数组是无序的,所以一般对排序没有要求的时候才会使用并行

2.分类

流操作主要分为中间操作和终端操作两大类,其中中间操作又分为无状态和有状态两小类,终端操作又分为非短路操作和短路操作。

  • 无状态:指元素的处理不受之前元素的影响
  • 有状态:指该操作只有拿到之前的元素之后才能继续下去
  • 非短路操作:指必须处理所有的元素才能得到结果
  • 短路操作:指遇到一些符合/不符合条件的元素就可以获取结果,不一定非要处理完所有的元素

分类树状图:
image

3.使用

3.1 流的创建

stream流的创建大致有五种创建方法:
(1).使用Collection下的 stream() 和 parallelStream() 方法
其中stream()就是创建一个普通的流,而parallelStream()则是一个并行的流,另外也可以通过Stream的parallel()方法将普通的流改变为并行的流。

List<Person> people = Person.initPerson();
Stream<Person> stream = people.stream();
Stream<Person> parallelStream = people.parallelStream();

(2).使用Arrays 中的 stream() 方法,将数组转成流

Integer[] age = {12, 13, 14, 15, 16, 17, 18};
Stream<Integer> ageStream = Arrays.stream(age);

(3).使用Stream中的静态方法:of()、iterate()、generate()

Stream<Integer> ofStream = Stream.of(12, 13, 14, 15, 16, 17, 18);
ofStream.forEach(System.out::println);
Stream<Integer> iterateStream = Stream.iterate(2, s -> s * (s - 1) + 1).limit(6);
iterateStream.forEach(System.out::println);
Stream<Double> generateStream = Stream.generate(Math::random).limit(6);
generateStream.forEach(System.out::println);

运行结果:
image
(4).使用 BufferedReader.lines() 方法,将每行内容转成流

BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\admin\\Desktop\\123.sql"));
// 这里直接将流通过collect()转化为了List
List<String> collect = reader.lines().collect(Collectors.toList());
collect.forEach(System.out::println);

运行结果:
image
(5).使用 Pattern.splitAsStream() 方法,将字符串分隔成流

Pattern pattern = Pattern.compile(",");
List<String> collect = pattern.splitAsStream("a,b,c,d").collect(Collectors.toList());
collect.forEach(System.out::println);

运行结果:
image

3.2 中间操作的使用

例子中用到的Person类为:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Arrays;
import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {

    private String name;
    private Integer age;
    private Double score;

    public static List<Person> initPerson() {
        return Arrays.asList(new Person("张三", 14, 80.0),
                new Person("李四", 15, 70.0),
                new Person("王二", 16, 80.0),
                new Person("麻子", 15, 80.0),
                new Person("张三", 14, 80.0));
    }

    @Override
    public String toString() {
        return "name:" + name + ",age:" + age + ",score:" + score;
    }

    @Override
    public boolean equals(Object a) {
        return hashCode() == a.hashCode();
    }

    @Override
    public int hashCode() {
        return toString().hashCode();
    }

}

(1)filter中间操作
filter中间操作是过滤操作,筛选出一些符合条件的数据。样例:

List<Person> people = Person.initPerson();
long count = people.stream().filter(person -> person.getAge().equals(14)).count();
System.out.println(count);

运行结果:
image
从上面的结果可以看出,其筛选出了两个age为14的数据。
(2)map中间操作
map中间操作是对数据进行映射,处理,相关的扩展还有mapToInt,mapToLong,mapToDouble这三个操作会降数据分别转化为int,long,double。样例:

List<Person> people = Person.initPerson();
List<String> collect = people.stream().map(Person::toString).collect(Collectors.toList());
collect.forEach(System.out::println);
people.stream().mapToInt(u -> u.toString().length()).forEach(System.out::println);
people.stream().mapToLong(Person::getAge).forEach(System.out::println);
people.stream().mapToDouble(Person::getAge).forEach(System.out::println);

运行结果:
image
(3)flatMap中间操作
flatMap中间操作和map中间操作一样,不过区别是flatMap操作的返回类型只能为Stream流,也就是从一个流转化为另一个流。
(4)peek中间操作
peek中间操作是传递一个消费性函数接口作为参数对数据及逆行消费。样例:

List<Person> people = Person.initPerson();
people.stream().peek(s -> {
    if (s.getAge() > 15)
        s.setAge(17);
}).forEach(System.out::println);
System.out.println("----------------------------------------------------------");
System.out.println(people);

运行结果:
image
(5)unorder中间操作
unorder中间操作,消除相遇顺序,主要用于并行流,提高并行流的性能,因为并行流在处理有序数据结构时,对性能会有很大的影响。
PS:不过这边进行样例的时候发现好像加了unorder的比没加unorder的用时还要旧,不确定是不是因为数据太少。增加数据之后进行测试,发现增加unorder的用时变化不大,而没加unorder的用时增加了些。

List<Person> people = Person.initPerson();
long time = new Date().getTime();
people.stream().parallel().unordered().map(Person::getAge).forEach(System.out::println);
System.out.println("******************************************************");
people.stream().parallel().unordered().map(Person::getAge).forEach(System.out::println);
long time1 = new Date().getTime();
System.out.println(time1 - time);
System.out.println("******************************************************");
long time2 = new Date().getTime();
people.stream().parallel().map(Person::getAge).forEach(System.out::println);
System.out.println("******************************************************");
people.stream().parallel().map(Person::getAge).forEach(System.out::println);
long time3 = new Date().getTime();
System.out.println(time3 - time2);

运行结果:
image
(6)distinct中间操作
distinct中间操作通过流中元素的 hashCode() 和 equals() 去除重复元素,样例:

List<Person> people = Person.initPerson();
people.forEach(System.out::println);
System.out.println("------------------------------------------------------");
// distinct中间操作,进行去重操作
people.stream().distinct().forEach(System.out::println);

运行结果:
image
(7)sorted中间操作
sorted中间操作是一个排序操作,能够进行定制排序和自然排序,定制排序就是传一个比较器参数。样例:

List<Person> people = Person.initPerson();
people.stream().sorted(Comparator.comparing(s1 -> s1.getAge(), (a1,a2) -> a2 - a1)).forEach(System.out::println);
System.out.println("------------------------------------------------------");
people.stream().sorted(Comparator.comparing(Person::getAge).thenComparing(Person::getScore).reversed()).forEach(System.out::println);

运行结果:
image
(8)limit中间操作
limit中间操作就是获取n个元素,就像是sql中的limit。样例:

List<Person> people = Person.initPerson();
people.stream().limit(1).forEach(System.out::println);

运行结果:
image
(9)skip中间操作
skip中间操作是跳过n个元素,与limit相互配合时可以实现分页功能。样例:

List<Person> people = Person.initPerson();
people.stream().skip(2).forEach(System.out::println);
System.out.println("------------------------------------------------------");
people.stream().skip(2).limit(2).forEach(System.out::println);

运行结果:
image

3.3 终端操作

(1)anyMatch终端操作
anyMatch终端操作是进行判断是否有数据满足条件,只要有一条数据满足条件就会返回true。样例:

List<Person> people = Person.initPerson();
System.out.println(people.stream().map(Person::getAge).anyMatch(s1 -> s1 > 15));
System.out.println(people.stream().map(Person::getAge).anyMatch(s1 -> s1 > 17));

运行结果:
image
(2)allMatch终端操作
allMatch终端操作是判断数据是否满足条件,必须全部的数据满足条件才会返回true,只要有一个不满足条件就会返回false。样例:

List<Person> people = Person.initPerson();
System.out.println(people.stream().map(Person::getAge).allMatch(s1 -> s1 > 15));
System.out.println(people.stream().map(Person::getAge).allMatch(s1 -> s1 > 13));

运行结果:
image
(3)noneMatch终端操作
noneMatch终端操作时判断数据是否全部不满足条件,只有全部不满足条件才会返回true,否则返回false。样例:

List<Person> people = Person.initPerson();
System.out.println(people.stream().map(Person::getAge).noneMatch(s1 -> s1 > 16));
System.out.println(people.stream().map(Person::getAge).noneMatch(s1 -> s1 > 15));

运行结果:
image
(4)findFirst终端操作
findFirst终端操作是返回第一个满足条件的数据。样例:

List<Person> people = Person.initPerson();
System.out.println(people.stream().parallel().filter(s1 -> s1.getAge() > 14).findFirst());

运行结果:
image
(5)findAny终端操作
findAny终端操作时返回任意一个满足条件的数据,不一定是第一个。样例:

List<Person> people = Person.initPerson();
System.out.println(people.stream().parallel().filter(s1 -> s1.getAge() > 14).findAny());

运行结果:
image
(6)forEach终端操作
foreach终端操作时对数据进行遍历消费,说白了就是一个循环操作。样例:

List<Person> people = Person.initPerson();
people.stream().parallel().forEach(System.out::println);

运行结果:
image
(7)forEachOrdered终端操作
forEachOrdered终端操作也是一个循环,不过与forEach终端操作不同的是forEachOrdered能保证循环是按照顺序进行循环的。样例:

List<Person> people = Person.initPerson();
people.stream().parallel().forEachOrdered(System.out::println);

运行结果:
image
(8)toArray终端操作
toArray终端操作是将流转化为数组的操作,如果不写参数则转化为object数组,写参数则转为当前所写参数的数组。样例:

List<Person> people = Person.initPerson();
Object[] objects = people.stream().toArray();
for (Object object : objects) {
    System.out.println(object);
}
System.out.println("------------------------------------------------------");
Person[] persons = people.stream().toArray(Person[]::new);
for (Person person : persons) {
    System.out.println(person);
}

运行结果:
image
(9)min终端操作
min终端操作是获取数据中的最小值,返回值是Optional。样例:

List<Person> people = Person.initPerson();
Optional<Person> min = people.stream().min(Comparator.comparing(Person::getAge));
System.out.println(min);

运行结果:
image
(10)max终端操作
max终端操作是获取数据中的最大值,返回值也是Optional。样例:

List<Person> people = Person.initPerson();
Optional<Person> max = people.stream().max(Comparator.comparing(Person::getAge));
System.out.println(max);

运行结果:
image
(11)count终端操作
count终端操作是获取流中的数据量。样例:

List<Person> people = Person.initPerson();
System.out.println(people.stream().count());

运行结果:
image
(12)reduce终端操作
reduct终端操作是根据指定的计算模型将Stream中的值计算得到一个最终结果,其分为一个参数,两个参数和三个参数的方法。一个参数:就是进行计算的逻辑,两个参数:一个初始值,一个计算逻辑,三个参数:也是一个初始值,一个计算逻辑,一个合并逻辑,合并逻辑只会针对并发时冲突的情况。样例:

List<Person> people = Person.initPerson();
// 获取所有数据年龄的和
Integer sum = people.stream().map(Person::getAge).reduce((s1, s2) -> s1 + s2).get();
System.out.println(sum);
// 获取所有数据的年龄的和,并其初始值为10
Integer sum1 = people.stream().map(Person::getAge).reduce(10, (s1, s2) -> s1 + s2);
System.out.println(sum1);

// 计算所有年龄的和,初始值为0,如果冲突的话则取其中最大的,当发生冲突时求的就不是和了
Integer sum2 = people.stream().map(Person::getAge).reduce(0, (s1, s2) -> s1 + s2, (s1, s2) -> s1 > s2 ? s1 : s2);
System.out.println(sum2);

运行结果:
image
(13)collect终端操作
collect终端操作是收集操作,可以说是对处理过后的流进行一些归纳处理。其收集器有多种,具体如下:

收集器 功能描述
toList 转化为list集合
toSet 转化为set集合
toColection 转化为collection集合
toMap 转化为map
counting 计算数量
summingInt 计算和,根据值得类型有不同的派生,比如summingLong等
averagingInt 计算平均值,根据值的类型不同也会有不同的派生,比如averagingLong等
joining 根据字符串进行拼接流中的元素
maxBy 根据比较器选择出值最大的元素
minBy 根据比较器选择出值最小的元素
groupingBy 按照给定的分组函数进行分组,返回值类型为map
partitioningBy 根据给定的分区函数的值进行分区,会将key分为true和false的map
collectingAndThen 对流的结果进行二次加工转换
reducing 从给定的初始值逐一处理,知道最后只剩一个结果为止,和reduce终端操作类似

各种收集器的样例:

List<Person> people = Person.initPerson();
// toList收集器,转为list数组
List<Person> collect = people.stream().collect(Collectors.toList());
System.out.println(collect);
// toSet收集器,转为set集合
Set<Person> collect1 = people.stream().collect(Collectors.toSet());
System.out.println(collect1);
// toCollection收集器,转为collection集合
List<Person> collect2 = people.stream().collect(Collectors.toCollection(ArrayList<Person>::new));
System.out.println(collect2);
// toMap收集器,转为map
Map<String, Person> collect3 = people.stream().collect(Collectors.toMap(Person::getName, Function.identity(), (s1, s2) -> s2));
System.out.println(collect3);
// counting收集器,计算数量
Long collect4 = people.stream().collect(Collectors.counting());
System.out.println(collect4);
// summingInt收集器,计算和,根据值的类型不同有不同的派生,比如summingLong等
Integer collect5 = people.stream().collect(Collectors.summingInt(Person::getAge));
System.out.println(collect5);
// averagingInt收集器,计算平均值,根据值的类型不同也会有不同的派生,比如averagingLong
Double collect6 = people.stream().collect(Collectors.averagingInt(Person::getAge));
System.out.println(collect6);
// joining收集器,进行拼接流中的元素
String collect7 = people.stream().map(Person::toString).collect(Collectors.joining("||"));
System.out.println(collect7);
// maxBy收集器,根据比较器选择出值最大的元素
Person person = people.stream().collect(Collectors.maxBy((s1, s2) -> s1.getAge() - s2.getAge())).get();
System.out.println(person);
// minBy收集器,根据比较器选择出值最小的元素
Person person1 = people.stream().collect(Collectors.minBy((s1, s2) -> s1.getAge() - s2.getAge())).get();
System.out.println(person1);
// groupingBy收集器,按照给定的分组函数进行分组
Map<String, List<Person>> collect8 = people.stream().collect(Collectors.groupingBy(Person::getName));
System.out.println(collect8);
// partitioningBy收集器,根据给定的分区函数的值进行分区,会将key分为true和false两个map
Map<Boolean, List<Person>> collect9 = people.stream().collect(Collectors.partitioningBy(s -> s.getAge() > 15));
System.out.println(collect9);
// collectingAndThen收集器,对其结果进行二次加工转换
List<Person> collect10 = people.stream().collect(Collectors.collectingAndThen(Collectors.partitioningBy(s -> s.getAge() > 15), sm -> sm.get(true)));
System.out.println(collect10);
// reducing收集器,从给定的初始值逐一处理,直到最后只剩一个结果位置,和reduce终端操作类似
Integer collect11 = people.stream().map(Person::getAge).collect(Collectors.reducing(10, (s1, s2) -> s1 + s2));
System.out.println(collect11);

运行结果:
image

posted @   _mcj  阅读(75)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示