您好,登錄后才能下訂單哦!
今天小編給大家分享一下java8流怎么使用的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。
在本節中,我們來看看如何選擇流中的元素:用謂詞篩選,篩選出各不相同的元素,忽略流
中的頭幾個元素,或將流截短至指定長度。
filter()方法:
該操作會接受一個謂詞作為參數,并返回一個包含所有符合謂詞元素的流.
例如,你可以像下圖所示的這樣,篩選出所有素菜,創建一張素食菜單:
@Test public void test2() { List<Dish> vegetarianMenu = menu.stream() .filter(Dish::isVegetarian) .collect(toList()); System.out.println(vegetarianMenu); }
distinct()方法:
它會返回一個元素各異的流(根據流所生成元素的hashCode 和 equals 方法實現),
例如,以下代碼會篩選出列表中所有的偶數,并確保沒有重復。
@Test public void test3() { List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4); numbers.stream() .filter(i -> i % 2 == 0) .distinct() .forEach(System.out::println); }
limit()方法:
該方法會返回一個不超過給定長度的流。所需的長度作為參數傳遞
給 limit 。如果流是有序的,則最多會返回前 n 個元素。
List<Dish> dishes = menu.stream() .filter(d -> d.getCalories() > 300) .limit(3) .collect(toList());
你可以看到,該方法只選出了符合謂詞的頭三個元素,
然后就立即返回了結果。
請注意 limit 也可以用在無序流上,比如源是一個 Set 。這種情況下, limit 的結果不會以
任何順序排列
流還支持 skip(n) 方法,返回一個扔掉了前 n 個元素的流。如果流中元素不足 n 個,則返回一
個空流。請注意, limit(n) 和 skip(n) 是互補的!
@Test public void test4() { List<Dish> vegetarianMenu = menu.stream().filter(dish -> dish.getCalories() > 300) .skip(2) .collect(toList()); System.out.println(vegetarianMenu); }
一個非常常見的數據處理套路就是從某些對象中選擇信息.比如在sql中,你可以選擇一列.
Stream API也通過 map()和flatMap()方法提供了類似的工具.
map()
它會接受一個函數作為參數,這個函數會被應用到每個元素上.并將其映射成一個新的元素.
例如,下面的代碼把方法引用 Dish::getName 傳給了 map 方法,
來提取流中菜肴的名稱:
@Test public void test5() { List<String> vegetarianMenu = menu.stream() .map(Dish::getName) .collect(toList()); System.out.println(vegetarianMenu); }
因為 getName 方法返回一個 String ,所以 map 方法輸出的流的類型就是 Stream 。
讓我們看一個稍微不同的例子來鞏固一下對 map 的理解。給定一個單詞列表,你想要返回另
一個列表,顯示每個單詞中有幾個字母。怎么做呢?你需要對列表中的每個元素應用一個函數。
這聽起來正好該用 map 方法去做!
@Test public void test6() { List<String> words = Arrays.asList("Java 8", "Lambdas", "In", "Action"); List<Integer> integers = words.stream() .map(String::length) .collect(toList()); System.out.println(integers); }
結果:
[6, 7, 2, 6]
現在讓我們回到提取菜名的例子。如果你要找出每道菜的名稱有多長,怎么做?你可以像下
面這樣,再鏈接上一個 map :
@Test public void test7() { List<Integer> integers = menu.stream() .map(Dish::getName) .map(String::length) .collect(toList()); System.out.println(integers); }
結果:
[4, 4, 7, 12, 4, 12, 5, 6, 6]
你已經看到如何使用 map 方法返回列表中每個單詞的長度了。讓我們拓展一下:對于一張單
詞 表 , 如 何 返 回 一 張 列 表 , 列 出 里 面 各 不 相 同 的 字 符 呢 ? 例 如 , 給 定 單 詞 列 表
[“Hello”,”World”] ,你想要返回列表 [“H”,”e”,”l”, “o”,”W”,”r”,”d”] 。
你可能首先想到這樣做
@Test public void test9() { List<String> words = Arrays.asList("Hello","World"); List<String[]> list = words.stream() .map(word -> word.split("")) .distinct() .collect(toList()); System.out.println(list); }
這個方法的問題在于,傳遞給 map 方法的Lambda為每個單詞返回了一個 String[] ( String
列表)。因此, map 返回的流實際上是 Stream
[[Ljava.lang.String;@1def03a, [Ljava.lang.String;@122cdd0]
使用 flatMap()
@Test public void test11() { List<String> words = Arrays.asList("Hello","World"); List<String> list = words.stream() .map(word -> word.split("")) .flatMap(Arrays::stream) .distinct() .collect(toList()); System.out.println(list); }
結果:
[H, e, l, o, W, r, d]
要想搞清楚 flatMap() 的效果,我們首先來看看 Arrays.stream() 方法
@Test public void test10() { List<String> words = Arrays.asList("Hello","World"); List<Stream<String>> list = words.stream() .map(word -> word.split("")) .map(Arrays::stream) .distinct() .collect(toList()); System.out.println(list); }
結果:
[java.util.stream.ReferencePipeline$Head@1591d15, java.util.stream.ReferencePipeline$Head@1ae6ba4]
可以看出,Arrays.stream() 的方法可以接受一個數組并產生一個流.
而 flatMap(Arrays::stream) 的效果就是 把兩個流合并起來,即扁平化為一個流.
flatMap() 的參數是 Stream類型.
super T, ? extends R> mapper); <R> Stream<R> flatMap(Function<? super
另一個常見的數據處理套路是看看數據集中的某些元素是否匹配一個給定的屬性。Stream
API通過 allMatch 、 anyMatch 、 noneMatch 、 findFirst 和 findAny 方法提供了這樣的工具
anyMatch()
@Test public void test12() { boolean b = menu.stream().anyMatch(Dish::isVegetarian); if(b){ System.out.println("有素菜!"); } }
anyMatch 方法可以回答“流中是否有一個元素能匹配給定的謂詞”。
anyMatch 方法返回一個 boolean ,因此是一個終端操作.
allMatch()
@Test public void test13() { boolean b = menu.stream() .allMatch(dish -> dish.getCalories() < 1000); if (b) { System.out.println("沒有高熱量事物,吃!"); } }
noneMatch()
和 allMatch 相對的是 noneMatch 。它可以確保流中沒有任何元素與給定的謂詞匹配
@Test public void test14() { boolean b = menu.stream().noneMatch(dish -> { return dish.getCalories() > 1000; }); if (b) { System.out.println("沒有高熱量事物,吃!"); } }
短路求值
有些操作不需要處理整個流就能得到結果。例如,假設你需要對一個用 and 連起來的大布
爾表達式求值。不管表達式有多長,你只需找到一個表達式為 false ,就可以推斷整個表達式
將返回 false ,所以用不著計算整個表達式。這就是 短路。
對于流而言,某些操作(例如 allMatch 、 anyMatch 、 noneMatch 、 findFirst 和 findAny )
不用處理整個流就能得到結果。只要找到一個元素,就可以有結果了。同樣, limit 也是一個
短路操作:它只需要創建一個給定大小的流,而用不著處理流中所有的元素。在碰到無限大小
的流的時候,這種操作就有用了:它們可以把無限流變成有限流.
findAny()
@Test public void test15() { Optional<Dish> any = menu.stream() .filter(dish -> dish.getCalories() == 450) .findAny(); System.out.println(any); any.ifPresent(dish -> { System.out.println(dish.getCalories()); }); }
結果:
Optional[Dish{name='salmon', vegetarian=false, calories=450, type=FISH}] 450
可以看到 findAny() 返回一個 Optional 類型的值,
Optional 簡介
Optional 類( java.util.Optional )是一個容器類,代表一個值存在或不存在。在
上面的代碼中, findAny 可能什么元素都沒找到。Java 8的庫設計人員引入了 Optional ,這
樣就不用返回眾所周知容易出問題的 null 了。
Optional 里面幾種可以迫使你顯式地檢查值是否存在或處理值不存在的情形的方法也不錯。
* isPresent() 將在 Optional 包含值的時候返回 true , 否則返回 false 。
* ifPresent(Consumer block) 會在值存在的時候執行給定的代碼塊。我們在第3章
介紹了 Consumer 函數式接口;它讓你傳遞一個接收 T 類型參數,并返回 void 的Lambda
表達式。
* T get() 會在值存在時返回值,否則拋出一個 NoSuchElement 異常。
* T orElse(T other) 會在值存在時返回值,否則返回一個默認值。
FindFirst()
用來找到流中的第一個元素,它的工作方式類似于 findany
@Test public void test16() { List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5); Optional<Integer> firstSquareDivisibleByThree = someNumbers.stream() .map(x -> x * x) .filter(x -> x % 3 == 0) .findFirst(); // 9
結果:
Optional[9]
何時使用 findFirst 和 findAny????
**你可能會想,為什么會同時有 findFirst 和 findAny 呢?答案是并行。找到第一個元素
在并行上限制更多。如果你不關心返回的元素是哪個,請使用 findAny ,因為它在使用并行流
時限制較少。**
到目前為止,你所見到的終端操作都返回一個boolean(allMatch之類),void(forEach),或Optional對象(findOny等).你也見過了可以用collect來將流中的元素合成一個List.
接下來,你將會看到如何把一個流中的元素組合起來,比如“計算菜單中的總卡路里”或“菜單中卡路里最高的菜是哪一個”.
此類查詢需要將流反復組合起來,得到一個值比如Integer,這樣的查詢可被歸類為 歸約 操作.
用函數式編程的術語來說,這稱為折疊(fold),因為你可以講這個流看作一張長長的紙(你的流),反復折疊成一個小方塊,這就是折疊操作的結果.
reduce()
先看使用java8之前的 for-each 的做法
int sum = 0; for (int
numbers中的每一個元素都使用加法運算反復迭代來得到結果.通過反復使用加法,你把一個數字列表歸約成了一個數字.
要是還能把所有的數字相乘,而不必去復制粘貼這段代碼,豈不是很好?這正是 reduce 操
作的用武之地,它對這種重復應用的模式做了抽象。你可以像下面這樣對流中所有的元素求和
@Test public void test17() { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); Integer reduce = numbers.stream().reduce(0, (a, b) -> a + b); System.out.println(reduce);//15
reduce 接受兩個參數:
* 一個初始值,這里是0
* 一個 BinaryOperator 來將兩個元素結合起來產生一個新值,這里我們用的是
lambda (a, b) -> a + b
上面的操作效果我們可以理解為:把初始值和后面的函數每一次計算的結果相加求和.
@Test public void test17() { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); Integer reduce = numbers.stream() .reduce(1, (a,b)->a*b); System.out.println(reduce);//120
這個操作效果我們可以理解為:把初始值和后面的函數每一次計算的結果相乘.
通過這兩個例子我們可以看到:reduce兩個參數的運算方式取決于后面Lambda的運算方式
Lambda反復結合每個元素,直到流被歸約成一個值.
在java8中Integer類多了幾個靜態方法,
/** * Adds two integers together as per the + operator. * * @param a the first operand * @param b the second operand * @return the sum of {@code a} and {@code b} * @see java.util.function.BinaryOperator * @since public static int sum(int a, int b) { return a + b; } /** * Returns the greater of two {@code int} values * as if by calling {@link Math#max(int, int) Math.max}. * * @param a the first operand * @param b the second operand * @return the greater of {@code a} and {@code b} * @see java.util.function.BinaryOperator * @since public static int max(int a, int b) { return Math.max(a, b); } /** * Returns the smaller of two {@code int} values * as if by calling {@link Math#min(int, int) Math.min}. * * @param a the first operand * @param b the second operand * @return the smaller of {@code a} and {@code b} * @see java.util.function.BinaryOperator * @since public static int min(int a, int b) { return
這不正好可以讓我們結合Lambda表達式嗎!(可能java8的設計者特意增加了這幾個操作適應Lambda編程需要),總之我們代碼看起來可以更簡潔
@Test public void test17() { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); Integer reduce = numbers.stream() .reduce(0, Integer::sum); System.out.println(reduce); }
無初始值
reduce還有一個重載的載體,它不接受初始值,但是會返回一個 Optional 對象.
@Test public void test18() { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); Optional<Integer> optional = numbers.stream() .reduce(Integer::sum); System.out.println(optional); }
為什么它返回一個 Optional 呢?考慮到流中沒有值的情況,reduce操作便無法返回其和,因為沒有初始值.這就是為什么結果被包裹在一個Optional對象里,以表明結果可能不存在.
@Test public void test19() { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); Optional<Integer> max = numbers.stream() .reduce(Integer::max); Optional<Integer> min = numbers.stream().reduce(Integer::min); System.out.println("max="+max+",min="+min); }
給定兩個元素能夠返回最大值的Lambda,redce會考慮流中的新值和下一個元素,并產生一個新的最大值,直到整個流消耗完.
相比于java8之前的迭代求和,我們使用reduce的好處在于,這里的迭代被內部迭代抽象掉了,這讓內部迭代可以選擇并行執行reduce操作. 而迭代求和的例子要更新共享變量sum,這不是那么容易并行化的.
如果你加入了同步,可能會發現線程競爭抵消掉了并行本應該帶來的性能提升.
這種計算的并行化需要另一種方法:將輸入分塊,再分塊求和,最后再合并起來.
這正式reduce所提供的,使用流來對所有的元素并行求和時,你的代碼幾乎不用修改: stream() 換成了 parallelStream().
我們先會議一下目前為止講到的所有流操作
public class TradeTest 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年發生的所有交易,并按交易額排序(從低到高)。 @Test public void test1(){ List<Transaction> list = transactions.stream() .filter(transaction -> transaction.getYear() == 2011) .sorted(Comparator.comparing(Transaction::getValue)) .collect(toList()); System.out.println(list); } //(2) 交易員都在哪些不同的城市工作過? @Test public void test2(){ List<String> list = transactions.stream() .map(Transaction::getTrader) .map(Trader::getCity) .distinct() .collect(toList()); System.out.println(list); List<String> list1 = transactions.stream() .map(transaction -> transaction.getTrader().getCity()) .distinct() .collect(toList()); System.out.println(list1); } //(3) 查找所有來自于劍橋的交易員,并按姓名排序。 @Test public void test3(){ List<Trader> list = transactions.stream() .map(Transaction::getTrader) .filter(trader -> trader.getCity().equals("Cambridge")) .sorted(Comparator.comparing(Trader::getName)) .collect(toList()); System.out.println(list); List<Trader> list1 = transactions.stream() .filter(transaction -> transaction.getTrader().getCity().equals("Cambridge")) .map(Transaction::getTrader) .sorted(Comparator.comparing(Trader::getName)) .collect(toList()); System.out.println(list1); } //(4) 返回所有交易員的姓名字符串,按字母順序排序。 @Test public void test4(){ List<String> list = transactions.stream() .map(Transaction::getTrader) .map(Trader::getName) .sorted((s1, s2) -> s1.compareTo(s2)) //.distinct() .collect(toList()); System.out.println(list); List<String> list1 = transactions.stream() .map(transaction -> transaction.getTrader().getName()) .sorted((s1, s2) -> s1.compareTo(s2)) .collect(toList()); System.out.println(list1); } //(5) 有沒有交易員是在米蘭工作的? @Test public void test5(){ boolean b = transactions.stream() .map(Transaction::getTrader) .anyMatch(trader -> trader.getCity().equals("Milan")); System.out.println(" 有沒有交易員是在米蘭工作的?="+b); boolean b1 = transactions.stream() .anyMatch(transaction -> transaction.getTrader().getCity().equals("Milan")); System.out.println(" 有沒有交易員是在米蘭工作的?="+b1); } //(6) 打印生活在劍橋的交易員的所有交易額。 @Test public void test6(){ List<Integer> list = transactions.stream() .filter(transaction -> transaction.getTrader().getCity().equals("Cambridge")) .map(Transaction::getValue) .collect(toList()); System.out.println(list); } // (7) 所有交易中,最高的交易額是多少? @Test public void test7(){ Optional<Integer> reduce = transactions.stream() .map(Transaction::getValue) .reduce(Integer::max); System.out.println(reduce); } //(8) 找到交易額最小的交易。 @Test public void test8(){ Optional<Integer> reduce = transactions.stream() .map(Transaction::getValue) .reduce(Integer::min); System.out.println(reduce); } //(9) 計算交易總額 @Test public void test9(){ Integer reduce = transactions.stream() .map(transaction -> transaction.getValue()) .reduce(0, Integer::sum); System.out.println(reduce); } }
我們先看下這段代碼有什么問題
//(9) 計算交易總額 @Test public void test9(){ Integer reduce = transactions.stream() .map(transaction -> transaction.getValue()) .reduce(0, Integer::sum); System.out.println(reduce); }
這段代碼的問題是,它有一個暗含的裝箱成本。每個 Integer 都必須拆箱成一個原始類型,
再進行求和
Java 8引入了三個原始類型特化流接口來解決這個問題:
IntStream,DoubleStream,LongStream 分別將流中的元素特化為 int ,double, long ,從而避免了暗含的裝箱成本.
每個接口都帶來了進行常用數值歸約的新方法,比如對數值流求和的 sum ,找到最大元素的 max 。
此外還有在必要時再把它們轉換回對象流的方法。
映射到數值流
將流轉化為特化版本的常用方法為 mapToInt(),mapToDouble(),mapToLong().
這些方法和前面說的 map 方法的工作方式一樣,只是它們返回的是一個特化流,而不是 Stream
@Test public void test20() { int sum = menu.stream() //返回一個Stream<Dish> .mapToInt(Dish::getCalories) //返回一個IntStream
請注意,如果流是空的, sum 默認返回 0 。 IntStream 還支持其他的方便方法,如max 、 min 、 average 等。
轉換回對象流 boxed()
同樣,一旦有了數值流,你可能會想把它轉換回非特化流。
IntStream intStream = menu.stream().mapToInt(Dish::getCalories); Stream<Integer> stream = intStream.boxed();
默認值 OptionalInt
要找到 IntStream 中的最大元素,可以調用 max 方法,它會返回一個 OptionalInt :
OptionalInt optionalInt = menu.stream() .mapToInt(Dish::getCalories) .max();
現在,如果沒有最大值的話,你就可以顯式處理 OptionalInt 去定義一個默認值了:
@Test public void test21() { OptionalInt optionalInt = menu.stream() .mapToInt(Dish::getCalories) .max(); int max = optionalInt.orElse(0); System.out.println(max); }
數值范圍
和數字打交道時,有一個常用的東西就是數值范圍。比如,假設你想要生成1和100之間的所有數字.
java8引入了兩個可用于 IntStream 和LongStream的靜態方法,range 和 rangeclosed.
這兩個方法都是第一個參數接受起始值,第二個參數接受結束值,但range是不包含結束值的, rangeclosed包含結束值
@Test public void test22() { IntStream intStream = menu.stream() .mapToInt(Dish::getCalories); IntStream intStream1 = IntStream.range(1, 100) .filter(n -> n % 2 == 0); System.out.println(intStream1.count()); }
本節將介紹如何從值序列、數組、文件來創建流,甚至由生成函數來創建無限流!
Stream.of()
你可以通過靜態方法 Stream.of() 通過顯示值創建一個流,他可以接受任意數量的參數.
@Test public void test23() { Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action"); stream.map(String::toUpperCase).forEach(System.out::println); }
Arrays.stream()
@Test public void test25() { int[] numbers = {2, 3, 5, 7, 11, 13}; IntStream intStream = Arrays.stream(numbers); int
Arrays.stream() 有很多重載方法.
java中用于處理文件等I/O操作的NIO API已經更新,以便利用Stream API.
java.nio.file.Files 中的很多靜態方法都會返回一個流。例如,一個很有用的方法是
Files.lines ,它會返回一個由指定文件中的各行構成的字符串流.
統計一個文件中有多少各不相同的詞:
@Test public void test26() { try { Stream<String> stream = Files.lines(Paths.get("data.txt")); //Arrays.stream()把字符串數組轉成流 long count = stream.flatMap(line -> Arrays.stream(line.split(" "))) .distinct() .count(); System.out.println(count); } catch
你可以使用Files.lines得到一個流,其中每個元素都是給定文件中的一行.
然后你可以對每一行進行split方法拆分單詞,應該注意的是:你應該如何使用 flatMap()產生一個扁平的單詞流.而不是給每一行生成一個單詞流.
Stream API 提供了兩個靜態方法來從函數生成流: Stream.iterate和Stream.generate.
這兩個操作可以創建所謂的無限流:不像從固定集合創建的流那樣有固定大小的流。由 iterate
和 generate 產生的流會用給定的函數按需創建值,因此可以無窮無盡地計算下去!一般來說,
應該使用 limit(n) 來對這種流加以限制,以避免打印無窮多個值.
迭代 iterate
@Test public void test27() { Stream.iterate(0,n->n+2) .limit(10) .forEach(System.out::println); }
iterate 方法接受一個初始值,還有一個作用于每次產生新值上的Lambda( UnaryOperator 類型),
這里,我們使用Lambda n -> n + 2 ,返回的是前一個元
素加上2。因此, iterate 方法生成了一個所有正偶數的流.
這種iterate基本上是順序的,因為結果取決于前一次應用,請注意,此操作將生成一個無限流——這個流沒有結尾,因為值是按需計算的,可以永遠計算下去。我們說這個流是無界的。
生成 generate
與 iterate 方法類似, generate 方法也可讓你按需生成一個無限流。但 generate 不是依次
對每個新生成的值應用函數的。它接受一個 Supplier 類型的Lambda提供新的值。我們先來
看一個簡單的用法:
Stream.generate(Math::random) .limit(5) .forEach(System.out::println);
以上就是“java8流怎么使用”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。