流是Java API的新成員否副,它允許你以聲明性方式處理數(shù)據(jù)集合(通過查詢語句來表達,而不是臨時編寫一個實現(xiàn))
Java 8中的Stream API可以讓你寫出這樣的代碼: 聲明性——更簡潔,更易讀 可復(fù)合——更靈活 可并行——性能更好
首先看一下使用流和不使用流的區(qū)別筹淫,需求: 把集合中年齡小于等于20的人的名字取出來并排序
不使用流: public List?beforeJava7(List?users){ // 取年齡 <= 20的用戶 List?tmpList = new ArrayList<>(); for (User user : users) { if (user.getAge() <= 20){ tmpList.add(user); } } // 排序 Collections.sort(tmpList, new Comparator() { public int compare(User u1, User u2) { return u1.getName().compareTo(u2.getName()); } }); // 取名字 List?userNames = new ArrayList<>(); for(User user : tmpList){ userNames.add(user.getName()); } return userNames; }
使用流: public List?java8(List?users){ //為了利用多核架構(gòu)并行執(zhí)行這段代碼喷斋,只需要把stream()換成parallelStream(): List?userNames = users.stream() .filter(user -> user.getAge() <= 20) .sorted(Comparator.comparing(User::getName)) .map(User::getName) .collect(Collectors.toList()); return userNames; } 流的定義 從支持?jǐn)?shù)據(jù)處理操作的源生成的元素序列。 讓我們一步步剖析這個定義:
元素序列 就像集合一樣庸推,流也提供了一個接口,可以訪問特定元素類型的一組有序值浇冰。因為集合是數(shù)據(jù)結(jié)構(gòu)贬媒,所以它的主要目的是以特定的時間/空間復(fù)雜度存儲和訪問元 素(如ArrayList 與 LinkedList)。但流的目的在于表達計算肘习,集合講的是數(shù)據(jù)际乘,流講的是計算。
源 流會使用一個提供數(shù)據(jù)的源漂佩,如集合脖含、數(shù)組或輸入/輸出資源。 請注意投蝉,從有序集合生成流時會保留原有的順序养葵。由列表生成的流,其元素順序與列表一致瘩缆。
數(shù)據(jù)處理操作 流的數(shù)據(jù)處理功能支持類似于數(shù)據(jù)庫的操作关拒,以及函數(shù)式編程語言中的常用操作,如filter庸娱、 map着绊、 reduce、 find熟尉、 match归露、 sort等。流操作可以順序執(zhí)行斤儿,也可并行執(zhí)行剧包。
流操作有兩個重要的特點腮考。
流水線 很多流操作本身會返回一個流,這樣多個操作就可以鏈接起來玄捕,形成一個大的流水線踩蔚。流水線的操作可以 看作對數(shù)據(jù)源進行數(shù)據(jù)庫式查詢。
內(nèi)部迭代 與使用迭代器顯式迭代的集合不同枚粘,流的迭代操作是在背后進行的馅闽。
看一段能夠顯示這些概念的代碼,需求是: 把集合中年齡小于等于20的人的名字取出來并排序 public List?java8(List?users){ List?userNames = users.stream() .filter(user -> user.getAge() <= 20) .sorted(Comparator.comparing(User::getName)) .map(User::getName) .collect(Collectors.toList()); return userNames; }
本例中馍迄,我們先是對users調(diào)用stream方法福也,由用戶列表得到一個流。 數(shù)據(jù)源是用戶列表攀圈,它給流提供一個元素序列暴凑。接下來,對流應(yīng)用一系列數(shù)據(jù)處理操作: filter赘来、 map现喳、 sorted和collect。除了collect之外犬辰,所有這些操作都會返回另一個流嗦篱,這樣它們就可以接成一條流 水線,于是就可以看作對源的一個查詢幌缝。最后灸促, collect操作開始處理流水線,并返回結(jié)果(它 和別的操作不一樣涵卵,因為它返回的不是流浴栽,在這里是一個List)。在調(diào)用collect之前轿偎,沒有任 何結(jié)果產(chǎn)生典鸡,實際上根本就沒有從users里選擇元素。你可以這么理解:鏈中的方法調(diào)用都在排 隊等待贴硫,直到調(diào)用collect椿每。 流與集合的區(qū)別 集合與流之間的差異就在于什么時候進行計算。集合是一個內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)英遭,它包含數(shù)據(jù)結(jié)構(gòu)中目前所有的值——集合中的每個元素都得先算出來才能添加到集合中间护。(你可以往集合里加?xùn)|西或者刪東西,但是不管什么時候挖诸,集合中的每個元素都是放在內(nèi)存里的汁尺,元素都得先算出來才能成為集合的一部分。)
相比之下多律,流則是在概念上固定的數(shù)據(jù)結(jié)構(gòu)(你不能添加或刪除元素)痴突,其元素則是按需計算的搂蜓。 從另一個角度來說,流就像是一個延遲創(chuàng)建的集合:只有在消費者要求的時候才會計算值
以質(zhì)數(shù)為例辽装,要是想創(chuàng)建一個包含所有質(zhì)數(shù)的集合帮碰,那這個程序算起來就沒完沒了了,因為總有新的質(zhì)數(shù)要算拾积,然后把它加到集合里面殉挽。
而流的話,僅僅從流中提取需要的值拓巧,而這些值——在用戶看不見的地方斯碌,只會按需生成。
流只能被消費一次肛度,如果被消費多次傻唾,則會拋出異常: java.lang.IllegalStateException: stream has already been operated upon or closed
如下代碼所示: 這段代碼的意思是遍歷 lists 集合 List?lists = Arrays.asList("java8","lambda","stream"); Stream?stringStream = lists.stream(); Consumer?consumer = (x) -> System.out.println(x); stringStream.forEach(consumer); //stream has already been operated upon or closed stringStream.forEach(consumer);
外部迭代與內(nèi)部迭代 使用Collection接口需要用戶去做迭代(比如用for-each),這稱為外部迭代承耿。 相反冠骄,Streams庫使用內(nèi)部迭代——它幫你把迭代做了,還把得到的流值存在了某個地方瘩绒,你只要給出 一個函數(shù)說要干什么就可以了猴抹。?
?流操作 java.util.stream.Stream中的Stream接口定義了許多操作。它們可以分為兩大類锁荔。
中間操作
終端操作
在上述例子中,filter蝙砌,sorted阳堕,map 等都是中間操作,collect等是終端操作 中間操作可以連成一條流水線择克,終端觸發(fā)流水線執(zhí)行并關(guān)閉它恬总。
中間操作: 諸如filter或sorted等中間操作會返回另一個流。這讓多個操作可以連接起來形成一個查詢肚邢。重要的是壹堰,除非流水線上觸發(fā)一個終端操作,否則中間操作不會執(zhí)行任何處理骡湖。這是因為中間操作一般都可以合并起來贱纠,在終端操作時一次性全部處理。
終端操作: 終端操作會從流的流水線生成結(jié)果响蕴。其結(jié)果是任何不是流的值谆焊,比如List、 Integer浦夷,甚至void辖试。 總而言之辜王,流的使用一般包括三件事:
一個數(shù)據(jù)源(如集合)來執(zhí)行一個查詢;
一個中間操作鏈罐孝,形成一條流的流水線呐馆;
一個終端操作,執(zhí)行流水線莲兢,并能生成結(jié)果 使用流 篩選 filter()方法 Streams接口的filter方法汹来,該操作會接受一個謂詞(一個返回boolean的函數(shù))作為參數(shù),并返回一個包括所有符合謂詞的元素的流怒见。 filter(Predicate<? super T> predicate)
該方法接受一個 Predicate 作為參數(shù)俗慈。 如下代碼所示, /** * 把用戶集合中性別是男的用戶選擇出來 * user1, users2, users3是三種不同的寫法遣耍,結(jié)果都一樣 * @param users 原始用戶集合 * @return 性別是男性的用戶 */ public List?filterOfStream(List?users){ List?users1 = users.stream().filter(User::isMen).collect(Collectors.toList()); List?users2 = users.stream().filter(u -> u.getGender().equals("男")).collect(Collectors.toList()); Predicate?predicate = (u) -> u.getGender().equals("男"); List?users3 = users.stream().filter(predicate).collect(Collectors.toList()); //return users1; //return users2; return users3; }
distinct()方法: 該方法對流中重復(fù)的元素去重 /** * distinct(): 去除流中重復(fù)的元素 * 打印集合中的偶數(shù)闺阱,并且不能重復(fù) */ public void distinctOfStream(){ List?lists = Arrays.asList(1, 2, 3, 4, 2, 6, 4, 7, 8, 7); //2468 lists.stream().filter(x -> x % 2 == 0).distinct().forEach(System.out::print); // 另一種寫法 Consumer?consumer = (x) -> System.out.print(x); //2468 lists.stream().filter(x -> x % 2 == 0).distinct().forEach(consumer); }
limit()方法: 該方法會返回一個不超過給定長度的流。如果流是有序的舵变,則最多會返回前n個元素酣溃。 如果是無序的,如set,limit的結(jié)果不會以任何順序排列。 /** * limit():返回流中指定長度的流 */ public void limitOfStream(){ List?lists = Arrays.asList(1, 2, 3, 4, 2, 6, 4, 7, 8, 7); //獲取 lists 中前三個元素, 有序 // 123 lists.stream().limit(3).forEach(System.out::print); }
skip(n)方法: 該方法會跳過前 n 個元素壁查,返回 n+1 后面的元素的一個流 /** * skip(n):跳過前n個元素兜蠕,返回n后面的元素 */ public void skipOfStream(){ List?lists = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); //跳過前5個元素,從第6個元素開始打印 //678910 lists.stream().skip(5).forEach(System.out::print); }
映射
一個非常常見的數(shù)據(jù)處理套路就是從某些對象中選擇信息默责。比如在SQL里,你可以從表中選擇一列。 Stream API也通過map和flatMap方法提供了類似的工具艾恼。
map()方法: 它會接受一個函數(shù)作為參數(shù)。這個函數(shù)會被應(yīng)用到每個元素上麸锉,并將其映射成一個新的元素 注:map不是我們理解的集合Map钠绍,應(yīng)該理解為映射,將一個值映射為另一個值 如下的例子為:取出集合中用戶的名字花沉,返回一個名字集合 /** * @param users 用戶集合 * @return 用戶名字集合 */ public List?mapOfStream(List?users){ List?usersNames = users.stream().map(User::getName).collect(Collectors.toList()); // 另一種寫法 Function function = (user) -> user.getName(); List?usersNames2 = users.stream().map(function).collect(Collectors.toList()); //return usersNames2;
// 獲取每個用戶的名字的長度// 寫法一List userNameLength = users.stream()? ? ? ? ? ? .map(User::getName)// 獲取用戶名.map(String::length)// 獲取每個用戶名的長度.collect(Collectors.toList());// 返回一個集合// 寫法二Function<User, String> name = user -> user.getName();Function<String, Integer> len = s -> s.length();List userNameLength2 = users.stream().map(name).map(len).collect(Collectors.toList());// 寫法三List userNameLength3 = users.stream().map(s -> s.getName()).map(s -> s.length()).collect(Collectors.toList());returnusersNames;
}
flatmap()方法: flatmap方法把一個流中的每個值都換成另一個流柳爽,然后把所有的流連接起來成為一個流。
查找和匹配 另一個常見的數(shù)據(jù)處理套路是看看數(shù)據(jù)集中的某些元素是否匹配一個給定的屬性碱屁。 StreamAPI通過allMatch磷脯、 anyMatch、 noneMatch忽媒、 findFirst和findAny方法提供了這樣的工具争拐。
anyMatch()方法: 該方法的意思是 流中是否有一個元素能匹配給定的謂詞,只要有一個能夠匹配,就返回 true /** * anyMatch(): 流中是否有一個元素能匹配給定的謂詞架曹,只要有一個能夠匹配隘冲,就返回 true */ public void anyMatchOfStream(){ List?lists = Arrays.asList(1, 2, 3, 3, 4, 5); Stream?stream = lists.stream(); if (stream.anyMatch(i -> i == 3)){ System.out.println("包含 3"); }else{ System.out.println("不包含 3"); } }
allMatch()方法: 檢查流中的元素是否都能匹配給定的謂詞,只有所有的值和給定的謂詞相等绑雄,才返回 true /** * allMatch():檢查流中的元素是否都能匹配給定的謂詞 */ public void allMatch(){ List?lists = Arrays.asList(3, 3); if (lists.stream().allMatch(i -> i == 3)){ System.out.println("完全匹配"); }else{ System.out.println("不完全匹配"); } }
noneMatch()方法: 確保流中沒有任何元素與給定的謂詞匹配展辞,沒有匹配,返回 true anyMatch万牺、 allMatch和noneMatch這三個操作都用到了我們所謂的短路罗珍,這就是大家熟悉的Java中&&和||運算符短路在流中的版本。
findAny()方法: findAny方法將返回當(dāng)前流中的任意元素脚粟。它可以與其他流操作結(jié)合使用 public void findAnyOfStream(List?users){ Optional?user = users.stream().filter(u -> u.getName().equals("男")).findAny(); }
findFirst()方法: 該方法返回流中的第一個元素
歸約
reduce()方法: reduce 操作可以實現(xiàn)從Stream中生成一個值覆旱,其生成的值不是隨意的,而是根據(jù)指定的Lambda表達式核无。 /** * reduce() */ public void reduceOfStream(){ List?lists = Arrays.asList(1, 2, 3, 3, 4, 5); // 元素的總和 int sum = lists.stream().reduce(0, (x, y) -> x + y); Optional?sum2 = lists.stream().reduce(Integer::sum); System.out.println("sum = " + sum); System.out.println("sum2 = " + sum2.get()); // 求最大值 int max = lists.stream().reduce(0, (x, y) -> x > y ? x : y); Optional?max2 = lists.stream().reduce(Integer::max); System.out.println("max = " + max); System.out.println("max2 = " + max2.get()); // 最小值 int min = lists.stream().reduce(1, (x, y) -> x > y ? y : x); Optional?min2 = lists.stream().reduce(Integer::min); System.out.println("min = " + min); System.out.println("min2 = " + min2.get()); }
三個重載的方法: reduce(T identity, BinaryOperator?accumulator)
提供一個初始值扣唱,后面是一個Lambda表達式,如計算兩個值的最大值: int max = lists.stream().reduce(0, (x, y) -> x > y ? x : y); Optional?reduce(BinaryOperator?accumulator)
只是提供一個 Lambda表達式团南,返回一個Optional對象 Optional?max2 = lists.stream().reduce(Integer::max); reduce(U identity, BiFunction accumulator, BinaryOperator?combiner)
第三個重載的方法的第三個參數(shù)的意思: Stream是支持并發(fā)操作的噪沙,為了避免競爭,對于reduce線程都會有獨立的result吐根,combiner的作用在于合并每個線程的result得到最終結(jié)果正歼。
數(shù)值流 前面看到了可以使用reduce方法計算流中元素的總和 int sum = lists.stream().reduce(0, Integer::sum);
這段代碼的問題是,它有一個暗含的裝箱成本拷橘。每個Integer都必須拆箱成一個原始類型局义,再進行求和。
Java 8引入了三個原始類型特化流接口來解決這個問題: IntStream冗疮、 DoubleStream和 LongStream旭咽,分別將流中的元素特化為int、 long和double赌厅,從而避免了暗含的裝箱成本。
每個接口都帶來了進行常用數(shù)值歸約的新方法轿塔,比如對數(shù)值流求和的sum特愿,找到最大元素的max。 此外還有在必要時再把它們轉(zhuǎn)換回對象流的方法勾缭。
將流轉(zhuǎn)換為特化版本的常用方法是mapToInt揍障、 mapToDouble和mapToLong。
要把特型流轉(zhuǎn)換成一般流(每個int都會裝箱成一個Integer)俩由,可以使用boxed方法 Stream?stream = intStream.boxed();
數(shù)值的范圍: java 8引入了兩個可以用于IntStream和LongStream的靜態(tài)方法毒嫡,幫助生成數(shù)值的范圍: range和rangeClosed。這兩個方法都是第一個參數(shù)接受起始值幻梯,第二個參數(shù)接受結(jié)束值兜畸。但 range是不包含結(jié)束值的努释,而rangeClosed則包含結(jié)束值。 IntStream evenNumbers = IntStream.rangeClosed(1, 100) .filter(n -> n % 2 == 0); System.out.println(evenNumbers.count());
創(chuàng)建流的方式:
由值創(chuàng)建流 可以使用靜態(tài)方法Stream.of咬摇,通過顯式值創(chuàng)建一個流伐蒂。它可以接受任意數(shù)量的參數(shù)。 以下代碼直接使用Stream.of創(chuàng)建了一個字符串流肛鹏。然后逸邦,你可以將字符串轉(zhuǎn)換為大寫,再一個個打印出來: /**
of() 方法創(chuàng)建流 */ public void ofOfStream(){ Stream?stringStream = Stream.of("java", "lambda", "stream"); stringStream.map(s -> s.toUpperCase()).forEach(System.out::println);
由數(shù)組創(chuàng)建流 可以使用靜態(tài)方法Arrays.stream從數(shù)組創(chuàng)建一個流在扰。它接受一個數(shù)組作為參數(shù)缕减。 int[] numbers = {2, 3, 5, 7, 11, 13}; int sum = Arrays.stream(numbers)
由文件生成流 Java中用于處理文件等I/O操作的NIO API(非阻塞 I/O)已更新,以便利用Stream API芒珠。 java.nio.file.Files中的很多靜態(tài)方法都會返回一個流桥狡。
由函數(shù)生成流: 創(chuàng)建無限流 Stream API提供了兩個靜態(tài)方法來從函數(shù)生成流: Stream.iterate和Stream.generate。 這兩個操作可以創(chuàng)建所謂的無限流:不像從固定集合創(chuàng)建的流那樣有固定大小的流妓局。由iterate 和generate產(chǎn)生的流會用給定的函數(shù)按需創(chuàng)建值总放,因此可以無窮無盡地計算下去!一般來說好爬, 應(yīng)該使用limit(n)來對這種流加以限制局雄,以避免打印無窮多個值。
一般來說存炮,在需要依次生成一系列值的時候應(yīng)該使用iterate炬搭,比如一系列日期: 1月31日, 2月1日穆桂,依此類推宫盔。 與iterate方法類似, generate方法也可讓你按需生成一個無限流享完。但generate不是依次 對每個新生成的值應(yīng)用函數(shù)的灼芭。它接受一個Supplier類型的Lambda提供新的值。
常用的流操作?