流提供了数据视图,让你可以在比集合更高的概念层上指定操作。使用流只要指定做什么,而不是怎么做。将操作的调度执行留给实现。

要点:

  1、迭代器使用了一种明确的遍历策略,同时也阻止了高效的并发执行

  2、你可以从集合、数组、生成器或迭代器创建流

  3、使用过滤器filter来选择元素,使用map进行元素转换

  4、对于转换流的其他操作还包括limit、distinct、sorted

  5、要从Stream中获得结果,请使用规约操作(reduction operation),如count、max、min、findFirst、findAny。这些方法中的一些会返回Optional类型值。

  6、Optional类型是作为处理null值而提供的一个安全替代者。想要安全地使用它,需要借助于ifPersent和orElse方法。

  7、你可以收集集合、数组、字符串或者map中的Stream结果。

  8、Collections类的groupingBy方法和partitioningBy方法允许你将流的内容划分成组,然后获得每个组的结果。

  9、针对基本数据类型如int、long、double,java提供了专门的流

  10、并行流自动将流操作并行化

1、从迭代到stream

1 //统计一本书中所有长单词
2 String contents = new String(Files.readAllBytes(Paths.get("alice.txt")), StandardCharsets.UTF_8);//将文件读入字符串
3 List<String> words = Arrays.asList(contents.split("\PL+"));//拆分成单词
4 int count = 0;
5 for(String w : words){
6     if(w.length()>12) count++;
7 }
8
9 long count = words.stream().filter(w->w.length()>12).count();

  用words.parallelStream()就会使得流类库并行地进行过滤和计数操作。

  Stream遵循“做什么,而不是怎么去做”的原则。不要指定那个线程,怎么完成,执行顺序和执行线程都会自动由Stream实现,相比自己定义实现则会放弃了优化的机会

  流表面上与集合相似,允许转换和检索数据。然而两者却有明显不同:

  1、流不存储元素,他们存储在底层的集合或者按需生成。

  2、流操作不改变他们的数据源。例如,filter方法不会从一个新流中删除元素,而是生成一个不包含特定元素的新流。

  3、如果可能的话,Stream操作可能是延迟执行的。这以为着直到需要结果的时候,方法才会执行。

  流的典型工作流程

  1、创建一个stream

  2、强初始流转换成其他流的中间操作,可能需要多步操作(filter方法返回另一个流)

  3、应用终止操作产生结果。该操作强迫懒惰操作进行执行。在这之后流就不会再应用到了(count方法将流归纳为一个结果)

2、创建Stream

  用Collection接口的Stream方法将任何集合转化成Stream。用Stream.of方法将一个数组转化为Stream。

1 Stream<String> words = Stream.of(contents.split("\\PL+"));//splits方法返回String[]

  用Arrays.stream(array, from, to)方法将数组的一部分转换成Stream。用Stream.empty方法创建一个空的流。

  创建无限Stream的静态方法。

    generate方法接受一个无参的函数(Supplier<T>接口的对象)

1 //创建一个常量值的Stream
2 Stream<String> echos = Stream.generate(()->"echo");
3 //创建一个含有随机数的Stream:
4 Stream<String> randoms = Stream.generate(Math::random);

    interate方法,接受一个种子值和一个函数(UnaryOperator<T>接口的对象)并会对之前的值重复应用该函数

1 Stream<BigInteger> integers = Stream.iterate(BigInteger.ZERO, n-> n.add(BigInteger.ONE));
2 //Pattern类有一个方法可以按照正则表达式对字符串进行分隔
3 Stream<String> words = Pattern.compile("\\PL+").splitAsStream(contents);
4 //静态方法Files.lines返回了一个包含文件中所有行的结果
5     try(Stream<String> lines = Files.lines(path)){
6       //对lines进行处理
7     }

3、filter、map、flatMap方法

  filter转换生成一个匹配一定条件的新流

//将字符串流转换成另一个只包含长单词的流
List<String> words = ...;
Stream<String> longWords = word.stream().filter(w->w.length()>12);

  filter的参数是一个Predicate<T>对象(从T到boolean的函数)

  我们经常需要将一个流中的值进行某种形式的转换,这是用map方法,并且传递给它一个执行转换的函数

//将单词转换成小写
Stream<Stream> lowercaseWords = words.stream().map(String::toLowerCase);
//产生每个单词第一个字母的流
Stream<String> firstLetters = words.stream().map(s -> s.substring(0,1));

public static Stream<String> letters(String s){List<String> result = new ArrayList<>();for(int i=0;i<s.length();i++){result.add(s.substring(i,i+1));   }      return result.stream();
}
//等同于
Stream<Stream<String>> result = words.stream().map(w->letters(w))
//将返回一个包含多个流的流,如果要展开为一个只包含字符串的流则可以使用flatMap方法而不是map方法
Stream<String> flatResult = words.stream().flatMap(w->letters(w));
//在每个单词上调用letters方法,并展开结果

4、提取子流和组合流

  stream.limit(n)会返回一个包含n个元素的新流(如果原始流的长度小于n,则会返回原始流)

1 Stream<Double> randoms = Stream.generate(Math::random).limit(100);

  stream.skip(n)正好相反,它会丢弃前n个元素。

Stream<String> words = Stream.of(contents.split("\\PL+")).skip(1);
//可以使用Stream类中的静态方法concat将两个流连接起来
Stream<String> combined = Stream.concat(letters("Hello"), letters("World"));
//生成流["H","e","l"......]
//当然第一个流不是无限的,否则第二个流永远没有机会添加到第一个流后面

5、其他流转换

  distinct: 对于Stream中包含的元素进行去重操作(去重逻辑依赖元素的equals方法),新生成的Stream中没有重复的元素;

  对于Stream排序来说,java 9 提供了多个sorted方法。其中一个实现了Comparable接口的流,一个接受一个Comparator对象

1 //最长的单词会出现在第一个位置
2 Stream<String> longestFirst = words.stream().sorted(Comparator.comparing(String::length).reversed());

  peek: 生成一个包含原Stream的所有元素的新Stream,同时会提供一个消费函数(Consumer实例),新Stream每个元素被消费的时候都会执行给定的消费函数;

  

Object[] powers = Stream.iterate(1.0, p->p*2).peek(e->System.out.println("Fetching" +e)).limit(20).toArray();
//每当检索到一个元素就会调用peek里面的方法,一般用于调试,可以在peek调用的方法中设置断点。

6、简单归约

  从流中或的返回值,称为归纳方法(reduction)。规约是终止操作,可以将流归纳为一个可以在程序中使用的非流值。

  count:返回流中元素的数目

  max,min:返回流中的最大值或最小值。

  这些方法会返回一个Optional<T>类型的值,他可能是一个封装类型的值,或这是没有返回值。Optional类型是一种更好的表明缺少返回值的方式。

1 Optional<String> largest = words.max(String::compareToIgnoreCase);
2 System.out.println("largest" +largest.getOrElse(""));

  findFirst方法返回非空集合中的第一个值,通常与filter方法结合起来使用。

1 Optional<String> startsWithQ =words.filter(s -> s.startsWith("Q")).findFirst();

  如果想找到任何一个匹配的元素,而不必是第一个,则使用findAny方法,该方法在对流进行并行执行时非常有效

  如果只想知道流中是否含有匹配元素,则使用anyMatch方法。这个方法接受一个predicate参数,所以不需要使用filter方法

boolean aWordStartsWithQ = words.parallel().anyMatch(s->s.startsWith("Q"));

  如果所有元素都跟predicate匹配的话,allMatch方法将返回true,如果没有元素匹配的话,nonematch方法返回true。

  两个方法都可以通过并行执行提高速度。

7、Optional类型

  Optional<T>对象是一个T类型对象或者空对象的封装。Optional<T>类型是要么指向对象要么为null的T类型引用的安全替代者。

1 //封装的字符串,如果没有的话则为空字符串“”
2 String result = optionalString.orElse("");
3 //也可以调用代码来计算默认值,函数只有在需要时才会被调用
4 String result = optionalString.orElseGet(()->System.getProperty("user.dir"));
5 //提供了一个可以产生异常对象的方法
6 String result = optionalString.orElseThrow(IllegalStateException::new);

  ifPresent方法接受一个函数,如果Optional值存在的话,他会被传递给函数;否则的话,不进行任何处理。

optionalValue.ifPresent(v->Process v);

  如果你希望当值存在时将其添加到一个集合中,可以调用:

optionalValue.ifPresent(v->results.add(v));
//或者简单点
optionalValue.ifPresent(results::add);

  当调用ifPresent方法时,函数不会返回任何值。如果想对函数结果进行处理,则使用map方法:

  Optional<Boolean> added = optionalValue.map(results::add)

  add方法的值有3种可能性:封装到Optional中的true或者false;如果optionalValue存在,或者是一个空的Optional值。

  创建Optional类型值有两个静态方法:Optional.of(result)和Optional.empty()

1 public static Optional<Double> inverse(Double x){
2     return x ==0 ? Optional.empty() : Optional.of(1/x);
3 }

ofNullable方法被设计为null值和可选值之间的一座桥梁,obj不为null,Optional.ofNullable(obj),则会返回Optional.of(obj);否则返回Optional.empty()

Stream的flatMap方法,通过展开方法所返回的流,将两个方法组合起来。

  假设f返回T,g返回U。则可调用s.f().g()将两个方法组合起来,返回T

  如果f返回Optional<T>,g返回Optional<U>,就不能调用s.f().g()

    可以调用  Optional<U> result = s.f().flatMap(T::g);

如计算平方根:

public static Optional<Double> squareRoot(Double x){return x<0? Optional.empty() : Optional.of(Math.sqrt(x));
}
//这样可以计算反转值得平方根
Optional<Double> result = inverse(x).flatMap(MyMath::squareRoot);
//或者
Optional<Double> result = Optional.of(-4.0).flatMap(Demo::inverse).flatMap(Demo::squareRoot);

8、收集结果

  调用iterate方法生成一个能够访问元素的传统迭代器。

  调用forEach方法作用于没一个元素,在并行流上forEach方法可以以任意顺序便利元素,但是想按顺序处理需要用forEachOrdered方法。

stream.forEach(System.out::println);

  调用roArray获得一个含有流中所有元素的数组。

1 String[] result = stream.toArray(String[]::new);

  调用collect方法,传入Collectors类将流放到目标容器中

List<String> result = steam.collect(Collectors.toList());
Set<String> result = stream.collect(Collectors.toSet());
TreeSet<String> result = stream.collect(Collectors.toCollection(TreeSet::new));
//字符串拼接起来
String result = stream.collect(Collectors.joining());
//元素间插入分隔符
String result  = stream.collect(Collectors.joining(","));
//如果流包含字符串以外对象,首先将他们转换成字符串
String result = stream.map(Object::toString).collect(Collectors.joining(","));

  如果想将流规约为总和、平均值、最大值、最小值,则用summarizing方法

1 //用summarzing(Int|Long|Double)方法返回(Int|Long|Double)SummaryStatistics类型结果
2 IntSummaryStatistics summary = stream.collect(Collectors.summaringInt(String::length));
3 double averageWordLength = summary.getAverage();
4 double maxWordLength = summary.getMax();

9、将结果收集到Map中

Map<Integer, String> idToName = people.collect(Collectors.toMap(Person::getId, Person::getName));
//如果设置Map的值为实际people中的对象
Map<Integer, String> idToName = people.collect(Collectors.toMap(Person::getId, Function.identity()));
//如果多元素拥有相同的键,会出异常,则提供第三个参数,更具已有的值和新值,来决定键的值。

解决键冲突问题,可以看P270,上网搜索下Collectors.toMap方法。

使用toConcurrentMap方法可以生成一个并发的map。

10、分组和分片

  使用groupingBy方法,对Map进行分组

1 Map<String, List<Local>> countryToLocales = locales.collect(Collectors.groupingBy(Locale::getCountry));
2 //Locale::getCountry是分组的分类函数
3 List<Locale> swissLocales = countryToLocales.get("CH");

  ※每个语言环境都有一个语言代码(en)和地区码(US)。en_us代表美国英语,en_zh代表中国英语

  当分类函数是一个predicate(断言)函数时(返回一个布尔值的函数),流元素被分成两组列表,一组返回true的元素,另一组是返回false的元素。这时用partitioningBy比使用groupingBy更有效率。

//将所有语言环境分为英语和使用其他语言
Map<Boolean, List<Locale>> englishAndOtherLocals = locals.collect(Collections.partitioningBy(l->l.getLanguage().equals("en")));
List<Locale> englishLocales = englishAndOtherLocales.get(true);

  调用groupingByConcurrent方法,将会得到一个并发映射。当与并行流一起使用时,可以并发地插入值。

11、下游收集器

  groupingBy方法产生一个值为列表的map对象。如果想以某种方式处理这些列表,则提供一个下游(downstream)收集器。

  如:想让map中的值是set类型而不是list类型,则可以使用Collectors.toSet方法:

Map<String, Set<Locale>> countryToLocaleSet = locales.collect(groupingBy(Locale::getCountry, toSet()));//Java 8还提供了以下几个收集器用来将分组元素“归约”成数字//counting会返回所收集元素的总个数。
Map<String, Long> countryToLocaleCounts = locales.collect(groupingBy(Locale::getCountry, counting()));
//summing(Int|long|Double)接受一个函数作为参数,然后将函数应用到downstream元素上,并生成他们的求和。
Map<String, Integer> stateToCityPopulation = cities.collect(groupingBy(City::getState, summingInt(City::getPopulation)));
//计算每个州下属所有城市的人口数//maxBy、minBy接受一个比较器,产生downstream元素中的最大,最小值
Map<String, City> stateToLargestCity = cities.collect(groupingBy(City::getState, maxBy(Comparator.comparing(City::getPopulation))));
//产生每个州人口最多的城市//mapping将函数应用到downstream结果上,但是它需要另一个收集器处理其结果。
Map<String, Optional<String>> stateToLongestCityName = cities.collect(groupingBy(City::getState, mapping(City::getName,maxBy(Comparator.comparing(String::length)))));
//这里讲城市按所属州进行分组,每个州内,我们生成每个城市的名称并按照其最大长度进行归约

Mapping方法还未上一节中,获取一个国家所有语言集合的问题提供了一个更好的解决方案

1 Map<String, Set<String>> countryToLanguages = locales.collect(groupingBy(Locale::getDisplayCountry, mapping(Locale::getDisplayLanguage, toSet())));

  上一节使用的是toMap而不是groupingBy。在这种形式中,不必担心单独集合的合并问题。如果grouping活着mapping函数的返回类型为int、long、double则你可以将元素收集到一个summary statistics对象中

Map<String, IntSummaryStatistics>stateToCityPopulationSummary = cities.collect(groupingBy(City::getState, summarizingImt(Vity::getPopulation)));
//就可以从summary statistic对象中获取函数值得总和,总数、平均、最大小值

  ※组合收集器功能强大,但是会导致非常复杂的表达式。应该只在通过groupingBy或者partitioningBy来处理“downstream”map值时,才使用它们。其他情况下,只需要对流直接应用map、reduce、count、max、min方法即可

12、归约操作

  reduce方法是用来计算流中某个值的一种通用机制,最简单的形式是使用一个二元函数,从前两个元素开始,不断作用到流中的其他元素上。

1 //求和
2 List<Integer> values = ...;
3 Optional<Integer> sum = values.stream().reduce((x,y)->x+y);
4 //可以用reduce(Integer::sum)代替

  reduce方法含有一个归约操作P,那么归约操作将生成v0 P v1 P V2.....,其中,vi P vi+1 表示函数调用 p(vi,vi+1)。操作满足结合率,即与你组合元素的顺序无关。

  有许多结合操作:sum求和、product乘积、string concatenation字符串拼接、maximum最大值、minimum最小值、set union集合并集、intersection交集。

  减法就不是结合操作

  通常存在标识e是的 x P (y P z),可以使用该元素作为计算的起点。例如对加法来说起点就是0(标识)。

1 //带标识的reduce
2 List<Integer> values = ...;
3 Integer sum = values.stream().reduce(0 , (x,y)->x+y);
4 //如果流为空则返回标识值0

  如果有一个对象流,想得到这些对象上某个属性的和,如求一个流中所有字符串的总长度,就不能用reduce方法的简单形式((T,T)->T)应为参数和返回值类型是一样的,需要提供一个累加器函数(total, word) -> total +word.length()该函数会被重复调用形成累加值。但是当开始并行计算时,会出现多个累加值,需要将他们累加起来。

1 int result = words.reduce(0, (total, word) -> total + word.length(),(total1, total2) ->total1+total2);

  在实际中,不会大量地使用聚合方法。简单的方法是映射到一个数字流,使用它的方法进行求和、求最大值、最小值。在上述例子中可以调用words.mapToInt(String::length).sum()。这种方式简单高效,不涉及自动装箱。

  有时候reduce方法还不够通用。假如想要将结果收集到一个BitSet(位组)中。如果收集操作是并行的,那么不能直接将元素放进单个BitSet中,因为BitSet对象不是线程安全的。所以不能使用reduce方法。每部分需要从自己的空集合开始,而reduce仅允许提供一个标识值,作为代替,可以使用collect方法,他接受三个参数

  1、一个提供者,创建目标类型的实例方法,如HashSet的构造函数

  2、一个累加器,将元素添加到目标的方法,如add方法

  3、一个合并器,将两个对象合并成一个的方法,如addAll方法。

BitSet result = stream.collect(BitSet::new, BitSet::set, BitSet::or);

13、

转载于:https://www.cnblogs.com/Greekjone/p/5649063.html

给大忙人看的Java核心技术笔记(8、Stream)相关推荐

  1. 与时俱进的Java——《写给大忙人看的Java核心技术》读后感

    作为一个Android工程师,对书中那一句"成也框架,败也框架"深有同感:刚入门的时候只需要用Picasso或者OKHttp等开源框架实现简单的功能就能得到一个结果,有了经验之后才 ...

  2. 写给大忙人看的java_《写给大忙人看的Java核心技术》读后感

    看了作者的推荐序,里面有一段话我非常有感触: 现在的许多开发人员,一来就马上上手SSH做项目,把功能模块等实现了就OK. 完全就不关注性能的问题,其框架的好处是啥(如spring的容器管理依赖注入切面 ...

  3. 【读书笔记】《写给大忙人看的Java SE 8》——Java8新特性总结

    2019独角兽企业重金招聘Python工程师标准>>> 阅读目录 接口中的默认方法和静态方法 函数式接口和Lambda表达式 Stream API 新的日期和时间 API 杂项改进 ...

  4. Java核心技术笔记 语言基础

    <Java核心技术 卷Ⅰ> 第3章 Java 的基本程序设计结构 一些规则 类命名:CamelCase 驼峰命名法,以及必须是字母开头,后面跟字母和数字的任意组合: 源代码文件名:必须与公 ...

  5. 自学java核心技术笔记(康师傅)

    java初级笔记 文章目录 java初级笔记 第一章 Java技术简介 一.Java的性质 面向对象 可移植性 动态性 二.关于对Java常见误解的解释 1.Java是HTML的拓展 2.Java是专 ...

  6. 写给大忙人看的 Java 基础知识

    众所周知,Java 是一门面向对象的编程语言.它最牛逼的地方就在于它是跨平台的,你可以在 Windows 操作系统上编写 Java 源代码,然后在 Linux 操作系统上执行编译后的字节码,而无需对源 ...

  7. Java核心技术笔记 异常、断言和日志

    <Java核心技术 卷Ⅰ> 第7章 异常.断言和日志 处理错误 捕获异常 使用异常机制的技巧 记录日志 处理错误 如果由于出现错误而是的某些操作没有完成,程序应该: 返回到一种安全状态,并 ...

  8. Java核心技术笔记——第 12 章 反射

    转载自:[https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html] 12 反射 1. 引入反射 通常情况下,调用一个类的方法 ...

  9. Java核心技术笔记 1

    1.空串和null串 空串""s是长度为0的字符串,它是一个java对象,有自己的串长度和内容(空) 因此,可以调用String类对象的方法,如: if( str.length() ...

最新文章

  1. linux切换到顶层命令,一些在Linux下提高工作效率的常用命令
  2. asp伪装成jsp的方法
  3. 蓝桥杯java第八届第二题--纸牌三角形
  4. Openlayers中使用Cluster实现点位元素重合时动态聚合与取消聚合
  5. 你认识的python有你想的那么神吗_Python的10个神奇的技巧
  6. hdu5442(2015长春网络赛F题)
  7. vc 查看硬盘盘符剩余空间
  8. 在Cloud9上搭建Yii开发环境
  9. Android官方开发文档Training系列课程中文版:构建第一款安卓应用之启动另一个Activity
  10. 微信视频不能连接到服务器,微信无法连接到服务器
  11. GeoTools——新建shapefile文件
  12. Linux 中 用cat 输出文件内容
  13. 问题:js中怎么继承属性
  14. 解决php导出excel 长数字变成科学计数法
  15. 快排序和堆排序,最小堆、最大堆
  16. 彻底删除mysql server 2005_sql2005卸载工具(sql server 2005卸载工具)
  17. TWINCAT2安装问题
  18. 【科普】数字货币的基石--区块链
  19. 计算机efs加密,我的电脑文件efs加密了,现在从做系统打不开了怎么处理啊
  20. lua的坑(持续更新,每日更新两篇,9/15),更新到17个

热门文章

  1. vSphere利用NTP为主机同步时间
  2. LAN WAN WLAN 的区别
  3. 自从用完 Gradle 后,有点嫌弃 Maven 了!速度贼快!
  4. Spring框架你敢写精通,面试官就敢问@Autowired注解的实现原理
  5. 再谈 HBase 八大应用场景
  6. OSI模型中的数据链路层和物理层的区分
  7. Andriod --- JetPack (七):Room + ViewModel + LiveData 增删改查实例
  8. 微软成功测试氢燃料电池,为数据中心连续供电 48 小时
  9. 微软停止与华为合作:Windows暂停供应新订单
  10. 动力专业节假日保障预案