千家信息网

Java8 Stream API有什么作用

发表于:2024-11-19 作者:千家信息网编辑
千家信息网最后更新 2024年11月19日,这篇文章主要介绍"Java8 Stream API有什么作用",在日常操作中,相信很多人在Java8 Stream API有什么作用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对
千家信息网最后更新 2024年11月19日Java8 Stream API有什么作用

这篇文章主要介绍"Java8 Stream API有什么作用",在日常操作中,相信很多人在Java8 Stream API有什么作用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答"Java8 Stream API有什么作用"的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

Stream API 作用

为集合而生,简化集合操作、支持集合并发操作。

从迭代器到Stream

String contents = new String(Files.readAllBytes(Paths.get(alice.txt)), StandardCharsets.UTF_8);List words = Arrays.asList(contents.split("[\\P{L}]+"));

迭代器

int count = 0;for (String w : words) {        if (w.length() > 12) count++;}

Stream

long count = words.stream().filter(w -> w.length() > 12).count();// 并发统计很容易,如下只需要将stream()换成parallelStream()即可:long count = words.parallelStream().filter(w -> w.length() > 12).count();

Stream与集合的区别

  • Stream自己不会存储元素,元素被存放于底层集合中或者根据需要被产生出来;

  • Stream操作符不会改变源Stream对象,它会返回一个持有结果新Stream对象;

  • Stream操作符可能是延迟执行的,这意味着它们会等到需要结果的时候才执行。例如只需要前5个长单词,那么filter方法将在第5次匹配后停止过滤。

Stream使用流程

  1. 创建一个Stream(从集合、数组、迭代器、生成器等数据结构);

  2. 通过一个或者多个Stream操作符,将初始Stream转换为另一个Stream;

  3. 使用一个Stream终止操作符来产生一个结果,该操作会强制它之前的操作立即执行,且在此之后,该Stream就不会再被使用了。

创建Stream

  • 集合:Collection的stream()方法,会产生一个Stream对象;

  • 数组:Stream.of(T... values) 接受一个可变长度参数列表,参数可以是一个数组或者多个同类型独立元素,产生一个Stream对象;

  • Arrays.stream(array, from, to)将数组的一部分转化为Stream;

  • 空Stream:Stream silence = Stream.empty();

  • 无限Stream:
    Stream echos = Stream.generate(() -> "Echo");
    Stream randoms = Stream.generate(Math::random);
    Stream integers = Stream.iterate(BigInteger.ZERO, n -> n.add(BigInteger.ONE));

  • JDK示例:
    Stream words = Pattern.compile("[\\P{L}]+").splitAsStream(contents);
    Stream lines = Files.lines(path);

操作Stream

常见Stream操作函数

  • filter:过滤元素,并产生一个新的Stream对象;

  • map:转换;

  • flatMap:一对多转换,即每个元素映射的结构都是另一个Stream,最终将所有Stream合并为一个Stream;

示例

// filter:只获取长单词List wordList = ... ;Stream words = wordList.stream();Stream longWords = words.filter(w -> w.length() > 12);
// map:将所有单词转换为小写Stream lowercaseWords = words.map(w -> w.toLowerCase()); // 或者使用方法引用Stream lowercaseWords = words.map(String::toLowerCase()); // 获取每个单词的首字母  Stream firstChars = words.map(w -> w.charAt(0));
// flatMap:获取每个单词中的字母Stream letters = words.flatMap(w -> Arrays.stream(s.toCharArray()));

Stream提取与组合

  • Stream.limit(n),返回一个包含源Stream前n个元素的新Stream,如果源Stream长度m小于n则返回前m个元素;

  • Stream.skip(n),丢弃掉源Stream的前n个元素,返回包含剩余元素的新Stream;

  • 组合两个Stream为一个新的Stream,假设stream1和stream2为两个Character的Stream:Stream combined = Stream.concat(stream1, stream2);

  • Stream.peek(action),会产生另一个与源Stream具有相同元素的Stream,但是在获取每个元素时,都会调用action参数指定的函数,这样是为了便于调试。

Stream有状态转换

Stream的filter和map、flatMap等操作都是无状态的转换,因为在转换每一个元素时无需考虑之前转换过的元素;
Stream的distinct操作,会根据源Stream中的元素,返回一个元素顺序相同,但是没有重复元素的新Stream,是有状态的转换,因为它在转换每个元素时都需要检查该元素是否之前已读取过。

Stream聚合操作

Stream的聚合操作会将Stream聚合成为一个值,以便程序中使用,例如Stream.count()、Stream.max()、Stream.min()、Stream.findFirst()、Stream.findAny()等。
聚合方法都是终止操作,执行后流就关闭了,不能再应用其它操作了。

聚合操作示例:

Optional longest = words.max(String::compareToIgnoreCase);if (longest.isPresent()) {        Sysout.out.println(longest.get());}
Optional startWithQ = words.filter(s -> s.startsWith("Q")).findFirst();
// 并行提高执行效率Optional startWithQ = words.parallel().filter(s -> s.startsWith("Q")).findAny();
// 并行提高执行效率Optional startWithQ = words.parallel().anyMatch(s -> s.startsWith("Q"));

终止Stream

一个Stream对象在执行终止操作后,就不能再执行其他操作了。

Optional类型

Optional对象是对T类型对象的封装,或者表示不是任何对象。
Optional类本身实现非常简单,常用操作有:

  1. of(T value)

  2. ofNullable(T value)

  3. isPresent()

  4. ifPresent(consumer)

  5. orElse(T other)

  6. orElseGet(Supplier other)

  7. map(Function mapper)

  8. flatMap(Function mapper)用于组合可选函数,例如,如果f()返回Optional,T有一个返回Optional的方法g(),就可以组合调用:Optional op = f().flatMap(T::g)
    用法和其余的看源码最直观。

Stream聚合操作

聚合,即将Stream中的元素聚合成为一个值,例如:求和、求积、计数、字符串追加、最大值、最小值、并积、交积等,只要操作数x,y,z之间有一个操作op,满足(x op y) op z = x op (y op z),那么op操作就是可聚合的;

聚合示例

// 求和Stream values = ...;Optional sum = values.reduce((x, y) -> x + y);// 如果有一个标识e,使得e op x = x,那么标识e就可以作为计算的起点,对于加法来说0就是这个标识,所以另外一种形式:Optional sum = values.reduce(0, (x, y) -> x + y);// 或者Optional sum = values.reduce(0, Integer::sum);
// 求字符串Stream中所有字符串总长度Stream words = ...;Optional sum = words.reduce(0, (total, word) -> total + word.length(), (total1, total2) - > total1 + total2);// 第二个参数accumulator,是为了聚合计算;// 第三个参数combiner,是为了并行聚合计算后,对并行结果进行再聚合;

Stream收集结果

流处理完成以后,我们对于处理结果有两种用途:

  • 执行聚合操作,将整个Stream聚合成为一个值,例如:sum、count、max、min等;

  • 收集Stream处理结果,获取Stream中的每个元素,或打印、或转储、或执行其他计算;

第二种"收集Stream处理结果",由两种操作:

  • collect,两种方式:

  1. R collect(Collector collector); // 使用一个现成的预定义的Collector进行收集,Collectors工具类还为各种常用的收集类型提供了各个工厂方法。

示例1:
Stream stream = ...;
List list = stream.collect(Collectors.toList());
Set set = stream.collect(Collectors.toSet());
TreeSet treeSet = stream.collect(Collectors.toCollection(TreeSet::new));//控制得到的set类型
String result = stream.collect(Collectors.join());//将Stream中所有字符串拼接
String result = stream.collect(Collectors.join(", "));//将Stream中所有字符串拼接且以", " 分隔
// 将Stream结果聚合成为一个包含总和、最大值、最小值、平均值的结果,可以使用Collectors.summaring{Int | Long | Double}方法中的一种,这些方法会接受一个将Stream对象的元素映射为一个数字的函数,并产生一个{Int | Long | Double}SummaryStatistics类型的结果,其中包含了获取总和、最大值、最小值、平均值:
IntSummaryStatistics summary = words.collect(Collectors.summarizingInt(String::length));
double arerageWordLength = summary.getAverage();
double maxWordLength = summary.getMax();

  1. R collect(Supplier supplier, BiConsumer accumulator, BiConsumer combiner); // 自定义supplier、accumulator、combiner

supplier - a function that creates a new result container. For a parallel execution, this function may be called multiple times and must return a fresh value each time.
accumulator - an associative, non-interfering, stateless function for incorporating an additional element into a result.
combiner - an associative, non-interfering, stateless function for combining two values, which must be compatible with the accumulator function.
示例:
Stream stream = ...;
HashSet result = stream.collect(HashSet::new, HashSet::add, HashSet::addAll);

  • 遍历获取元素,两种方式:

  • void forEach(Consumer action); 非顺序遍历Stream元素,对于并行stream则并行遍历,顺序无法保证,但能提升遍历效率

  • void forEachOrdered(Consumer action);顺序遍历元素,保证顺序性,但是牺牲了性能

将结果收集到Map中:

// 普通toMap Collector,如果键冲突则会跑出IllegalStateExceptionStream persons = ...;Map idToName = persons.collect(Collectors.toMap(Person::getId, Person::getName));Map idToPerson = persons.collect(Collectors.toMap(Person::getId, Function.identity()));
// 定义键冲突解决策略,保留旧值Stream locales = Stream.of(Locale.getAvailableLocales());Map languages = locales.collect(Collectors.toMap(lan - > lan.getDisplayCountry(),        lan -> lan.getDisplayLanguage(),        (existingValue, newValue) - > existingValue));Map idToPerson = persons.collect(Collectors.toMap(Person::getId, Function.identity()));
// 定义键冲突解决策略,以Set方式保留所有值Stream locales = Stream.of(Locale.getAvailableLocales());Map languages = locales.collect(Collectors.toMap(lan - > lan.getDisplayCountry(),        lan -> Collections.singleton(lan.getDisplayLanguage(),        (existingValue, newValue) - > {                Set mergedSet = new HashSet<>(existingValue);                mergedSet.addAll(newValue);                return mergedSet;        };));
// 通过指定第四个参数,将结果收集到TreeMap中去Stream locales = Stream.of(Locale.getAvailableLocales());Map languages = locales.collect(Collectors.toMap(lan - > lan.getDisplayCountry(),        lan -> lan.getDisplayLanguage(),        (existingValue, newValue) - > existingValue),        TreeMap::new));

分组和分片

预定义的分组分片

  • Collectors.groupingBy
    Stream收集到Map过程中,通过分组,来简化相同键的合并问题,示例如下:
    Map> countryToLocales = locales.collect(Collectors.groupingBy(Locale::getCountry));
    函数Locale::getCountry是进行分组的分类函数。

  • Collectors.partitioningBy
    当分组函数是一个Prdicate函数时,即要将Stream中的元素分为是/非两组时,使用partitioningBy会更高效些,示例如下:
    Map> englishAndOtherLocales = locales.collect(Collectors.partitioningBy(lan -> lan.getLanguage().equals("en")));
    将元素分为两组:一组使用英语,一组使用其他语言。获取英语分组列表:
    List englishLocales = englishAndOtherLocales.get(true);

  • Collectors.groupingByConcurrent
    会获取一个并发Map,当用于并行流时可以并发地插入值。这与toConcurrentMap方法完全类似。

分组分片downstream处理

  • downstream
    方法groupingBy会产生一个值为列表的map的对象,如果希望对这个列表进行转换,例如转为Set,则可基于downstream实现,例如:
    Map> countryToLocaleSet = locales.collect(Collectors.groupingBy(Locale::getCountry, Collectors.toSet()));

  • 其他downstream

  • counting()
    Map countryToLocaleCount = locales.collect(Collectors.groupingBy(Locale::getCountry, Collectors.counting())); // 计算每个国家有多少种语言

  • summing{Int | Long | Double}(ToIntFunction mapper)
    Map stateToCityPopucation = cities.collect(Collectors.groupingBy(City::getState, Collectors.summingInt(City::getPopulation()))); // 计算每个下属所有城市人数总和

  • maxBy(Comparator comparator) 和minBy(Comparator comparator)
    Map stateToLargestCity = cities.collect(Collectors.groupingBy(City::getState, Collectors.maxBy(Comparator.comparing(City::getPopulation)))); // 计算每个州中人口最多的城市

  • mapping(Function mapper, Collector downstream)
    Map> stateToLargestCityName = cities.collect(Collectors.groupingBy(City::getState, Collectors.mapping(City::getName, Collectors.maxBy(Comparator.comparing(String::length)))))// 找出每个州中,名字最长的城市名称
    Map> countryToLanguages = locales.collect(Collectors.groupingBy(Locale::getDisplayCountry, mapping(Locale::getDisplayLanguage, Collectors.toSet())))

  • 如果grouping或者mapping函数的返回结果是int、long、double,可以将元素收集到summaryStatistics对象中:
    Map statistics = locales.collect(Colloctors.groupingBy(Locale::getState, summarizingInt(City::getPopulation)));

  • reducing方法可对downstream元素进行一次普通聚合

  • Collector reducing(BinaryOperator op)

  • Collector reducing(identity, BinaryOperator op)

  • Collector reducing(identity, Function mapper, BinaryOperator op)
    示例:
    Map stateToCityNames = cities.collect(groupingBy(City:getState, reducing("", City::getName, (s, t) -> s.length == 0 ? t : s + "," + t)))
    等价于:
    Map stateToCityNames = cities.collect(groupingBy(City:getState, mapping( City::getName, joining(","))));

原始类型流

背景

将整型收集到一个Stream的流中,需要将每个整数封装成一个包装对象,这是一个低效的做法,double/float/long/short/byte/boolean也一样,所以,Stream API专门设计了IntStream、LongStream、DoubleStream用于存放基本数据类型;

  • IntStream:用于存放int、short、char、byte和boolean类型的值;

  • LongStream:用于存放long型的值;

  • DoubleStream:用于存放double、float类型的值;

示例

  • 创建IntStream,使用IntStream.of和Arrays.stream
    IntStream stream = IntStream.of(1,1,2,3,5);
    int[] array = new int[]{1,1,2,3,5};
    stream = Arrays.stream(array, 0, array.length);

  • IngStream和LongStream的range和rangeClosed方法:
    IntStream zeroTo99 = IntStream.range(0, 100); //不包括上线100
    IntStream zeroTo100 = IntStream.rangeClosed(0, 100);//包括上线100

  • 对于对象流,可使用mapToInt、mapToLong、mapToDouble转换为对应的原始类型的流。例如:
    Stream words = ...;
    IntStream lengths = words.mapToInt(String::length);

  • 原始类型流的常用方法:
    sum、max、min、average、summaryStatistics等。

  • Random类的ints、longs、doubles分别产生对应的原始类型随机数字流。

并行流

  • 默认情况下,除了Collection.parallelStream()外,流操作创建的都是串行流

  • 开启并行流(在终止方法执行前)

Collection.parallelStream()、Stream.parallel()

  • 当无需考虑Stream元素顺序时,Stream.unordered() 会使并行操作更好地执行,例如distinct(),因为对于一个有序的流,distinct()操作总会保留所有相同元素的第一个,同样的操作还有limit();

到此,关于"Java8 Stream API有什么作用"的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注网站,小编会继续努力为大家带来更多实用的文章!

0