在本节中将介绍Stream API支持的许多操作,这些操作可以完成更复杂的数据查询,如筛选、切片、映射、查找、匹配和归约。还有一些特殊的流如:数值流、来自文件和数组等多种来源的流。

筛选和切片

  1.用谓词筛选

    Streams接口支持filter方法,该操作会接受一个谓词作为参数,并返回一个包含所有符合谓词的元素的流。例如筛选出所有素菜:

List<Dish> vegetarianMenu = menu.stream().filter(Dish::isVegetarian).collect(toList());

  2.筛选各异的元素

    流海支持一个叫做distinct的方法,它会返回一个元素各异(根据流所生成的元素的hashCode和equals方法的实现)的流。例如筛选所有的偶数并确保没有重复的:

        List<Integer> nums = Arrays.asList(1,2,3,13,12,2,1,2,2,1,2,2,3,4,5);
List<Integer> oddNums = nums.stream().filter(s->s%2==0).distinct().collect(toList());

  3.截断流

    流支持limit(n)方法,该方法会返回一个不超过给定长度的流,所需的长度作为参数传递给limit,如果流是有序的,则最多返回前n个元素。例如筛选热量超过300卡路里的前3道菜:

List<Dish> limit3 = menu.stream().filter(c->c.getCalories()>300).distinct().limit(3).collect(toList());

  4.跳过元素

    流还支持skip(n)方法,该方法会返回一个扔掉了前n个元素的流,如果流中元素U不足n个,则返回一个空流。例如:跳过超过300卡路里的头两道菜,并返回剩下的。

List<Dish> skip2 = menu.stream().filter(c->c.getCalories()>300).distinct().skip(2).collect(toList());

映射

  比如在SQL中,你可以选择从表中选择一列,Stream API也通过map和flatMap方法提供了类似的工具。

  1.对流中每一个元素应用函数

    流支持map方法,它接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素。例如下面把Dish::getName传给了map方法,来提取流中的菜名:

List<String> names = menu.stream().map(Dish::getName).collect(toList());

    因为getName返回一个String,所以map方法输出的流的类型就是Stream<String>。例如下面把List<String> 映射为List<Integer> 值是String的长度。

List<String> strs = Arrays.asList("lambda","action","java 8","stream");
List<Integer> ints = strs.stream().map(String::length).collect(toList());

    如果要找出每道菜的名称有多长可以再加上一个map:

List<Integer> nameLength = menu.stream().map(Dish::getName).map(String::length).collect(toList());

  2.流的扁平化

    通过下面这个例子介绍流的扁平化:给定字符串数组:["hello","world"],返回字符数组["h","e","l","o","w","r","d"]。

第1次尝试:你可能觉得很容易,distinct一下就好了

List<String> words = Arrays.asList("hello","world");

List<String[]> collect = words.stream().map(w -> w.split("")).distinct().collect(toList());

但是,传递给map方法的lambda为每个单词返回了一个Stirng[],因此map返回的流实际上是Stream<String[]>类型的,而我们想要的是Stream<String>类型的

第2次尝试:map和Arrays.stream()

首先要活的一个字符流,而不是字符串数组流,有一个叫做Arrays.stream()的方法可以接收一个数组并产生一个流:例如

        String[] words = {"hello","world"};
Stream<String> stream = Arrays.stream(words);

使用这个方法应用到前面的流水线里看看

        List<Stream<String>> collect1 = words.stream()
.map(w -> w.split(""))
.map(Arrays::stream)
.distinct()
.collect(toList());

还是不行,因为现在得到的是一个流的列表List<Stream<String>>。

第3次尝试:使用flatMap

        List<String> collect2 = words.stream()
.map(w -> w.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(toList());

使用flatMap的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所有使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化一个流。

映射练习:

  1.给定一个数字列表,返回每个数的平方构成的列表:

List<Integer> numbers = Arrays.asList(1,2,3,56,78,9);
List<Integer> collect3 = numbers.stream().map(a -> a * a).collect(toList());

  2.给定两个数字列表,返回所有的参数对。

        List<Integer> num1 = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> num2 = Arrays.asList(1, 2, 3, 4, 5); List<int[]> collect4 = num1.stream()
.flatMap(i -> num2.stream().map(j -> new int[]{i, j}))
.collect(toList());

  3.扩展前一个例子,只返回总和可以被3整除的。

        List<int[]> collect5 = num1.stream()
.flatMap(i -> num2.stream().filter(j -> (i + j) % 3 == 0).map(j -> new int[]{i, j}))
.collect(toList());

查找和匹配

  查看数据集中的某些元素是否匹配一个给定的属性,Stream API通过allMatch、anyMatch、noneMatch、findFirst和findAny方法提供了这样的工具

  1.至少匹配一个元素 anyMatch

        if(menu.stream().anyMatch(m->m.getCalories()>400)){
System.out.println("有卡路里大于400的食物");
}

  2.匹配所有元素 allMatch

        if(menu.stream().allMatch(Dish::isVegetarian)){
System.out.println("所有菜都是素菜");
}

  3.没有匹配 noneMatch

        if(menu.stream().noneMatch(m->m.getCalories() < 100)){
System.out.println("所有菜都不小于100卡路里");
}

anyMatch、allMatch、noneMatch这三个操作都用到了短路,就是Java中的 && || 运算符短路在流中的版本。

短路:有些操作不需要处理整个流就可以得到结果。例如一个用and连接起来的大布尔表达式,不管表达式有多长,只要找到一个为false就推断整个表达式为false。

对于流而言,某些操作(allMatch、anyMatch、noneMatch、findFirst、findAny)不用处理整个流就可以得到结果,limit也是一个短路操作。

查找元素

  findAndy方法返回当前流中的任意元素:

Optional<Dish> any = menu.stream().filter(Dish::isVegetarian).findAny();

Optional<T>类是一个容器类,代表一个值存在或不存在。例如这个例子,可能会什么都没找到。

  isPresent():optional存在值时返回true,否则返回false。

  ifPresent(Consumer<T> block)会在值存在的时候执行给定的代码块,(Consumer接口:传递一个T参数,消费这个T 什么也不返回)。

  T get():值存在时返回值,否则抛出一个NoSuchElement异常。

  T orElse(T other):会在值存在时返回值,否则返回一个默认值。

例如:如果找到了输出这个名字,否则什么也不错

menu.stream().filter(Dish::isVegetarian).findAny().ifPresent(d->System.out.println(d.getName()));

  findFrist方法返回第一个元素:

menu.stream().filter(Dish::isVegetarian).findFirst().ifPresent(d->System.out.println(d.getName()));

findFrist和findAny:找到第一个元素在并行上限制更多,如果不关心返回的元素是哪个就是用findAny,因为它在是用并行流时限制较少。

归约 reduce

  例如查找所有菜的总卡路里,或菜中最高的卡路里是哪个,这类查询需要将流中所有的元素反复结合起来,得到一个值。这样的查询可以被归类为归约操作。

  求和:

在是用reduce方法之前,来看看for-each循环来对数字列表中的元素求和:

        List<Integer> nums1 = Arrays.asList(1,2,3,4,5);

        int sum  = 0;
for(int i : nums1){
sum +=i;
}

是用reduce来求和如下:

int reduceSum = nums1.stream().reduce(0, (a, b) -> a + b);

这里的第一个参数 就如上面设置的初始值一样,第二个参数就是一个BinaryOperator<T>来将两个元素结合起来产生一个新值。

在Java 8 中,Integer类有一个现有的静态sum方法来对两个数求和,因此可以改写成:

Integer reduce = nums1.stream().reduce(0, Integer::sum);

reduce还有一个重载版本,它不用接受初始值,但会返回一个Optional对象:

Optional<Integer> reduce = nums1.stream().reduce(Integer::sum);

  最大值和最小值:

Optional<Integer> max = nums1.stream().reduce(Integer::max);
Optional<Integer> min = nums1.stream().reduce(Integer::min);

当然也可以写成 (x,y) -> x< y ? x : y;而不是Integer::min,不过后者更好读。

流操作:无状态和有状态

  map或feilter等操作会从输入流中获取每一个元素,并在输出流得到0或1个结果。这些操作一般都是无状态的:他们没有内部状态。

  但reduce、sum、max等操作需要内部状态类累计结果,不管流中又多少元素要处理,内部状态都是有界的。

  相反,sort或distinct等操作一开始都和filter、map差不多--都是接受一个流,再生成一个流(中间操作),但有一个关键的区别。从流中排序和删除重复项时都需要知道先前的历史,我们把这些操作叫做有状态操作。

到目前学到的流的方法如下:

  中间操作:

    filter、distinct、skip、limit、map、flatMap、sorted。

  终端操作:

    anyMatch、noneMatch、allMatch、findAny、findFirst、forEach、collect、reduce、count。

小练习:

public class Trader {
private final String name;
private final String city;
}
public class Transaction {
private final Trader trader;
private final int year;
private final int value;
}
        Trader raoul = new Trader("Raoul", "Cambridge");
Trader mario = new Trader("Mario","Milan");
Trader alan = new Trader("Alan","Cambridge");
Trader brian = new Trader("Brian","Cambridge");
List<Transaction> transactions = Arrays.asList(
new Transaction(brian, 2011, 300),
new Transaction(raoul, 2012, 1000),
new Transaction(raoul, 2011, 400),
new Transaction(mario, 2012, 710),
new Transaction(mario, 2012, 700),
new Transaction(alan, 2012, 950)
);
        //1.找出2011年发生的所有交易,并按交易额排序(从低到高)
List<Transaction> collect = transactions.stream()
.filter(t -> t.getYear() == 2011)
.sorted(Comparator.comparing(Transaction::getValue))
.collect(toList());
System.out.println(collect);
        //2.交易员都在哪些不同的城市工作过?
List<String> collect1 = transactions.stream()
.map(m -> m.getTrader().getCity())
.distinct()
.collect(toList());
System.out.println(collect1);
        //3.查找所有来自于剑桥的交易员,并按姓名排序。
List<Trader> collect2 = transactions.stream()
.map(m -> m.getTrader())
.filter(m -> m.getCity() == "Cambridge")
.distinct()
.sorted(Comparator.comparing(Trader::getName))
.collect(toList());
        //4.返回所有交易员的姓名字符串,按字母顺序排序
String reduce = transactions.stream()
.map(t -> t.getTrader().getName())
.distinct()
.sorted()
.reduce("", (n1, n2) -> n1 + n2);//效率不高是stirng 拼接 下一节joining
        //5.有没有交易员是在米兰工作的
boolean milan = transactions.stream().anyMatch(c -> c.getTrader().getCity().equals("Milan"));
        //6.打印生活在剑桥的交易员的所有交易额
transactions.stream()
.filter(t->t.getTrader().getCity().equals("Cambridge"))
.map(Transaction::getValue)
.forEach(System.out::println);
        //7.所有交易中,最高的交易额是多少
Optional<Integer> reduce1 = transactions.stream()
.map(Transaction::getValue)
.reduce(Integer::max);
        //8.找到交易额最小的交易
Optional<Transaction> reduce2 = transactions.stream()
.reduce((t1, t2) -> t1.getValue() < t2.getValue() ? t1 : t2);
        //流还支持min和max方法
Optional<Transaction> smallTransaction = transactions.stream()
.min(Comparator.comparing(Transaction::getValue));
smallTransaction.ifPresent(System.out::println);

数值流

  前面使用reduce方法计算了元素的总和,例如:

Integer reduce3 = transactions.stream().map(c -> c.getValue()).reduce(0, Integer::sum);

这段代码的问题是,它有一个暗含的装箱成本,每个Integer都必须拆箱成一个原始类型后再进行求和,要是可以像下面这样直接调用sum方法不是更好?

int sum3 = transactions.stream().map(c->c.getValue()).sum();

这是不可能的,因为map方法会生成一个Stream<T>,虽然流中的元素是Integer类型,但Streams接口没有定义sum方法。Stream API提供了原始类型流特化,专门支持处理数值流的方法。

原始类型流特化

  Java 8 引入了三个原始类型特化流接口来解决这个问题:IntStream、DoubleStream、LongStream,分别将流中的元素特化为int、long和double,从而避免了暗含的装箱成本。

  1.映射到数值流

    将流转换为特化版本的常用方法是mapToInt、mapToDouble和mapToLong,这些方法返回一个特化流,而不是Stream<T>。例如:

int sum = transactions.stream().mapToInt(Transaction::getValue).sum();

mapToInt返回一个IntStream而不是Stream<Integer>,然后就可以调用IntStream中的sum方法,如果流是空的,sum默认返回0.还支持其他方法如max、min、average等。

  2.转换回对象流

    将特化流转回非特化流,可以使用boxed方法:

        IntStream intStream = transactions.stream().mapToInt(Transaction::getValue);
Stream<Integer> stream = intStream.boxed();

  3.默认值OptionalInt

    Optional对于三中原始流特化,也分别有一个Optional原始类型特化版本:OptionalInt、OptionalDouble、OptionalLong。例如要找到最大元素:

OptionalInt max = transactions.stream().mapToInt(Transaction::getValue).max();

如果没有最大值可以给一个默认值:

int i = transactions.stream().mapToInt(Transaction::getValue).max().orElse(1);

数值范围

  在Java 8中引入了两个可以用于IntStream和LongStream的静态方法,帮助生成这种1到100之间数字的范围:range和rangeClosed。这两个方法第一个参数 起始值,第二个参数结束值。但range是不包含结束值的,而rangeClosed则包含结束值。就是< 和<=的区别。

        IntStream evenNumbers = IntStream.rangeClosed(1,100).filter(i->i%2==0);
System.out.println(evenNumbers.count());//50

如果是range方法 则只有49个结果 因为它不包含最后100这个数字。

range示例:取出1到100之间的勾股数:

        Stream<double[]> stream1 = IntStream.rangeClosed(1, 100)
.boxed()
.flatMap(a -> IntStream.rangeClosed(a, 100)
.mapToObj(
b -> new double[]{a, b, Math.sqrt(a * a + b * b)}
).filter(t -> t[2] % 1 == 0));
stream1.limit(3).forEach(t -> System.out.println(t[0] + "," + t[1] + "," + t[2]));

构建流

  1.由值创建流

    Stream.of方法可以显示的创建一个流,它可以接受任意数量的参数。例如:创建一个字符串流,将字符串转换为大写,再打印出来:

        Stream<String> stringStream = Stream.of("Java 8", "Lambdas", "in", "Action");
stringStream.map(String::toUpperCase).forEach(System.out::println);

    还可以使用mepty得到一个空流:

Stream<String> emptyString = Stream.empty();

  2.由数组创建流

    Arrays.Stream可以从数组创建一个流,它接受一个数组作为参数,例如你可以讲一个原始类型int的数组转换成一个IntStream:

        int[] nums4 = {2,3,5,56,6,4,4,45,234,2};
IntStream stream2 = Arrays.stream(nums4);

  3.由文件生成流

    Files.lines可以从文件得到一个流,其中的每个元素都是该文件的一行。

        Stream<String> lines = Files.lines(Paths.get("/Users/baidawei/Desktop/test.txt"), Charset.defaultCharset());
lines.forEach(c->System.out.println(c.toString()));

  4.由函数生成流:创建无限流

    Stream.iterate和Stream.generate这两个静态方法可以创建所谓的无限流:不像从固定集合创建的流那样有固定大小的流。由这两个产生的流会用给定的函数按需创建值,因此可以无穷的计算下去,一般来说应该使用limit来对这种流加以限制。

    4.1 迭代

        Stream.iterate(0,n->n+2)
.limit(10)
.forEach(System.out::println);

      iterate 第一个参数是起始值,第二个参数是一个lambda表达式(UnaryOperator<T>)类型的,没有终止条件,按需计算。所以需要limit截断

    4.2 生成

      与iterate类似,generate也可以让你按需生成一个无限流。但generate不是依次对每个新生成的值应用函数的。它接受一个Supplier<T>类型的Lambda提供新的值:

       Stream.generate(Math::random)
.limit(5)
.forEach(System.out::println);

      我们使用的供应源(Math::radom)是无状态的:它不会在任何地方记录任何值。

小结:

  1. 、筛选和切片:filter、distinct、skip、limit。

  2、映射:map、flatMap。

  3、查找:findFirst、findAny。

  4、匹配:allMatch、anyMatch、noneMatch。

  5、这些方法都利用了短路:找到结果就立即停止计算,没有必要处理整个流。

  6、归约:reduce、聚合 计算最大 最小值。

  7、filter和map等是无状态的,他们并不存储任何状态。reduce等操作需要存储状态才能计算一个值。sorted和distinct等操作也要存储状态,因为他们需要把六中的所有元素缓存起来才能返回一个新的流。这种操作称为有状态操作。

  8、流油三种基本的原始类型特化:IntStream、DoubleStream和LongStream。

  9、流不尽可以从集合创建,也可以从值、数组、文件以及iterate与generate等方法创建。

  10、无限流是没有固定大小的流。

Java 8 (4) Stream 流 - 使用的更多相关文章

  1. Java 8创建Stream流的5种方法

    不知不觉间,Java已经发展到13了,来不及感慨时间过得真的太快了,来不及学习日新月异的技术更新,目前大多数公司还是使用的JDK8版本,一方面是版本的稳定,另一方面是熟悉,所以很多公司都觉得不升级也挺 ...

  2. java中的Stream流

    java中的Stream流 说到Stream便容易想到I/O Stream,而实际上,谁规定"流"就一定是"IO流"呢?在Java 8中,得益于Lambda所带 ...

  3. Java 8 (3) Stream 流 - 简介

    什么是流? 流是Java API的新成员,它允许你以声明性方式处理数据集合(通过查询语言来表达,而不是临时编写一个实现).就现在来说你可以先把它当做是一个遍历数据集的高级迭代器.此外,流还支持并行,你 ...

  4. Java学习:Stream流式思想

    Stream流 Java 8 API添加了一种新的机制——Stream(流).Stream和IO流不是一回事. 流式思想:像生产流水线一样,一个操作接一个操作. 使用Stream流的步骤:数据源→转换 ...

  5. 双层for循环用java中的stream流来实现

    //双重for循环for (int i = 0; i < fusRecomConfigDOList.size(); i++) { for (int j = 0; j < fusRecomC ...

  6. Java 8 (6) Stream 流 - 并行数据处理与性能

    在Java 7之前,并行处理集合非常麻烦.首先你要明确的把包含数据的数据结构分成若干子部分,然后你要把每个子部分分配一个独立的线程.然后,你需要在恰当的时候对他们进行同步来避免竞争,等待所有线程完成. ...

  7. Java 8 (5) Stream 流 - 收集数据

    在前面已经使用过collect终端操作了,主要是用来把Stream中的所有元素结合成一个List,在本章中,你会发现collect是一个归约操作,就像reduce一样可以接受各种做法作为参数,将流中的 ...

  8. java 数据类型:Stream流 对象转换为集合collect(Collectors.toList()) ;常用方法count,limit,skip,concat,max,min

    集合对象.stream() 获取流对象,对元素批处理(不改变原集合) 集合元素循环除了用for循环取出,还有更优雅的方式.forEach 示例List集合获取Stream对象进行元素批处理 impor ...

  9. 还看不懂同事的代码?超强的 Stream 流操作姿势还不学习一下

    Java 8 新特性系列文章索引. Jdk14都要出了,还不能使用 Optional优雅的处理空指针? Jdk14 都要出了,Jdk8 的时间处理姿势还不了解一下? 还看不懂同事的代码?Lambda ...

随机推荐

  1. P2910 [USACO08OPEN]寻宝之路Clear And Present Danger 洛谷

    https://www.luogu.org/problem/show?pid=2910 题目描述 Farmer John is on a boat seeking fabled treasure on ...

  2. Ubuntu 16.04下减小/释放/清理VirtualBox虚拟硬盘文件的大小

    一般在VirtualBox中安装Windows,然后用无缝模式进行某些特定软件的使用. 而VirtualBox的虚拟硬盘会越用越大,并且VirtualBox没有自带清理工具,相比VMware来说,VM ...

  3. oracle常用函数积累

    --oracle常用函数积累-- --1.字符串长度:LENGTH ,语法: CONCAT(string) --示例 select LENGTH('AA_BB') from dual;--结果:5 - ...

  4. java STW stop the world 哈哈就是卡住了

    java  STW  stop the world 哈哈就是卡住了 学习了:http://www.jb51.net/article/125400.htm

  5. mysql 將時間戳直接轉換成日期時間

    from_unixtime()是MySQL裏的時間函數 Sql代碼 select uid,userid,username,email,FROM_UNIXTIME(addtime,'%Y年%m月%d') ...

  6. web 开发之js---js获取select标签选中的值

    var obj = document.getElementByIdx_x(”testSelect”); //定位id var index = obj.selectedIndex; // 选中索引 va ...

  7. Kernel Live-patching (by quqi99)

    作者:张华  发表于:2016-02-27 版权声明:能够随意转载.转载时请务必以超链接形式标明文章原始出处和作者信息及本版权声明 ( http://blog.csdn.net/quqi99 ) GC ...

  8. win32gui.EnumWindows my.os.EnumWindows.py

    import win32guidef _MyCallback(hwnd, extra): windows = extra temp = [] temp.append(hex(hwnd)) temp.a ...

  9. Hibernate - Query简易

    package cn.demo; import java.util.List; import org.hibernate.Query; import org.hibernate.Session; im ...

  10. iOS开发系列- 视频MPMoviePlayerController

    MPMoviePlayerController 在iOS中播放视频可以使用MediaPlayer.framework种的MPMoviePlayerController类来完成,它支持本地视频和网络视频 ...