在本节中将介绍Stream API支持的许多操做,这些操做能够完成更复杂的数据查询,如筛选、切片、映射、查找、匹配和归约。还有一些特殊的流如:数值流、来自文件和数组等多种来源的流。java
筛选和切片数组
1.用谓词筛选缓存
Streams接口支持filter方法,该操做会接受一个谓词做为参数,并返回一个包含全部符合谓词的元素的流。例如筛选出全部素菜:dom
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.截断流spa
流支持limit(n)方法,该方法会返回一个不超过给定长度的流,所需的长度做为参数传递给limit,若是流是有序的,则最多返回前n个元素。例如筛选热量超过300卡路里的前3道菜:code
List<Dish> limit3 = menu.stream().filter(c->c.getCalories()>300).distinct().limit(3).collect(toList());
4.跳过元素对象
流还支持skip(n)方法,该方法会返回一个扔掉了前n个元素的流,若是流中元素U不足n个,则返回一个空流。例如:跳过超过300卡路里的头两道菜,并返回剩下的。blog
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。
二、映射:map、flatMap。
三、查找:findFirst、findAny。
四、匹配:allMatch、anyMatch、noneMatch。
五、这些方法都利用了短路:找到结果就当即中止计算,没有必要处理整个流。
六、归约:reduce、聚合 计算最大 最小值。
七、filter和map等是无状态的,他们并不存储任何状态。reduce等操做须要存储状态才能计算一个值。sorted和distinct等操做也要存储状态,由于他们须要把六中的全部元素缓存起来才能返回一个新的流。这种操做称为有状态操做。
八、流油三种基本的原始类型特化:IntStream、DoubleStream和LongStream。
九、流不尽能够从集合建立,也能够从值、数组、文件以及iterate与generate等方法建立。
十、无限流是没有固定大小的流。