在本節中將介紹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等方法建立。
十、無限流是沒有固定大小的流。