「Lambda 表達式」是一個匿名函數,它能夠包含表達式和語句,而且可用於建立委託或表達式樹類型.
全部 Lambda 表達式都使用 Lambda 運算符 =>,該運算符讀爲「goes to」.該 Lambda 運算符的左邊是輸入參數(若是有),右邊包含表達式或語句塊.Lambda 表達式 x => x * x 讀做「x goes to x times x」.能夠將此表達式分配給委託類型提起對於集合的遍歷,恐怕下面的這種方式已是一種思惟定式了吧:php
final List<String> friends = Arrays.asList("Brian", "Nate", "Neal", "Raju", "Sara", "Scott"); for(int i = 0; i < friends.size(); i++) { System.out.println(friends.get(i)); }
可是仔細想一想,以上的代碼彷佛出現了過多的細節,好比循環變量i的出現。在作簡單的遍歷操做時,循環變量其實是沒必要要的,只有在對某個特定位置的元素執行某個特殊操做時,循環變量的使用纔有意義。因此,在Java中引入了加強的for循環,在這種循環方式中,循環變量是沒必要要的:java
for(String name : friends) { System.out.println(name); }
這種方式,在實現細節上使用的是iterator接口和它的hasNext(),next()方法。閉包
不管使用哪一種for循環,它們仍然使用了外部遍歷器(External Iterator)。即在for循環中,你老是有辦法經過諸如break,continue等方式來控制遍歷的過程。app
與外部遍歷器相對的,是內部遍歷器(Internal Iterator)。在Java 8中,Iterable接口被加強了,如今該接口擁有一個forEach方法用來實現內部遍歷器。forEach方法會接受一個Consumer接口類型做爲參數,該接口是一個函數式接口(Functional Interface),它是內部遍歷器的實現方式。關於函數式接口,能夠參考上一篇文章。函數
friends.forEach(new Consumer<String>() { public void accept(final String name) { System.out.println(name); } });
很顯然,上述代碼中使用的匿名類在Java 8中並非最好的方案,在這種場景下Lambda表達式是更好的選擇:工具
friends.forEach((final String name) -> System.out.println(name));
forEach方法自己是一個高階函數,由於它接受了一個Lambda表達式做爲其參數,而Lambda表達式在本質上則是一個函數。在Lambda表達式的左邊,聲明瞭一個String類型的變量name,它表明了集合中的元素。而箭頭右邊的代碼則表達了對於該元素應該執行何種操做。forEach之因此被稱爲內部遍歷器,緣由在於一旦它開始執行了,那麼遍歷操做就不可以被輕易中斷。oop
同時,藉助Java編譯器的類型推導(Type Inference)特性,Lambda表達式可以被進一步簡化以下:學習
friends.forEach((name) -> System.out.println(name));
此時,編譯器可以經過運行時的上下文知道這個name變量的類型是String。測試
另外,當Lambda表達式左端只接受一個變量的時候,括號也是能夠省略的:網站
friends.forEach(name -> System.out.println(name));
可是用類型推導有一個很差的地方,就是參數不會自動被final修飾。所以,在Lambda表達式右端,是能夠對參數進行修改的,然而這種行爲是不被倡導的。
上面的代碼已經足夠簡潔了,可是還有更簡潔的方法,那就是使用方法引用:
friends.forEach(System.out::println);
使用這種方式甚至不須要寫出Lambda表達式的左端參數部分。關於方法引用的詳細狀況,會在之後進行介紹。
與使用外部遍歷不一樣,使用內部遍歷的好處在於:
將一個集合經過某種計算獲得另外一個集合是一種經常使用的操做,也是Lambda表達式的用武之地。 好比,以將一個名字集合轉換爲首字母大寫了的名字集合爲例。
爲了避免改變原集合,最「天然」的方式以下:
final List<String> uppercaseNames = new ArrayList<String>(); for(String name : friends) { uppercaseNames.add(name.toUpperCase()); }
以上代碼使用了外部遍歷器,即for循環來完成集合操做。而將命令式代碼轉變爲聲明式代碼(也就是函數式)的首要任務就是觀察遍歷的使用方式,儘量地將外部遍歷更改成內部遍歷:
final List<String> uppercaseNames = new ArrayList<String>(); friends.forEach(name -> uppercaseNames.add(name.toUpperCase())); System.out.println(uppercaseNames);
好了,如今咱們使用了forEach來代替for循環。可是感受代碼並無變的簡潔多少。 咱們可使用其餘的函數式接口(Functional Interface)來實現集合的轉換。事實上,map方法比forEach方法更勝任這一類轉換工做:
friends.stream() .map(name -> name.toUpperCase()) .forEach(name -> System.out.print(name + " ")); System.out.println();
這裏使用了一個新的方法叫作stream()。在Java 8中,全部的集合類型都擁有這個方法。該方法的返回值是一個Stream類型的實例,該實例將集合自己包含在內(即上述的friends集合被包含在了stream實例中)。
能夠將它理解成一個創建在集合上的iterator,它提供了除了forEach以外的更加高級的方法,如上述的map()。map方法的做用在於,它可以將接受的一個輸入序列轉換成一個輸出序列(即完成轉換工做)。這也意味着map方法是存在返回值的,因此後續的forEach方法操做的集合便是map方法返回的集合。map也可連續操做如 list2.parallelStream().map(String::toUpperCase).map(obj->obj+"===").forEach(System.out::println);將第一個map操做完的值給第二個map繼續操做 阿西吧也能夠 .filter(過濾操做).filter(在次進行過濾操做)
集合的轉換操做能夠是任意的,好比須要獲得每一個名字的長度:
friends.stream() .map(name -> name.length()) .forEach(count -> System.out.print(count + " ")); // 5 4 4 4 4 5
使用方法引用可以對上面的代碼進行簡化:
friends.stream() .map(String::toUpperCase) .forEach(name -> System.out.println(name));
回顧以前咱們提到過的,當一個方法接受函數式接口做爲參數時,能夠傳入Lambda表達式或者方法/構造器的引用進行調用。而以上的String::toUpperCase就是一個方法應用。
注意到對該方法進行引用時,省略了其參數信息。這是由於Java編譯器在爲該方法引用生成實例時,會進行類型推導自動地將集合中的元素做爲參數傳入到該方法中。
好比,當咱們須要獲得名字集合中全部以N開頭的名字時,最「天然」的實現方式立刻就會反映以下:
final List<String> startsWithN = new ArrayList<String>(); for(String name : friends) { if(name.startsWith("N")) { startsWithN.add(name); } }
可是,咱們可讓這一切變得更加簡單和優雅:
final List<String> startsWithN = friends.stream() .filter(name -> name.startsWith("N")) .collect(Collectors.toList());
對於filter方法,它期待的參數是一個返回boolean類型的Lambda表達式。對於被操做的集合中的每一個元素而言,若是Lambda表達式返回的是true,那麼就意味着filter後獲得的stream實例中是包含該元素的,反之亦然。最後,能夠經過調用stream實例的collect方法來將stream實例轉換成一個List實例。
好比,當須要對不止一個集合進行操做時:
final long countFriendsStartN = friends.stream().filter(name -> name.startsWith("N")).count(); final long countComradesStartN = comrades.stream().filter(name -> name.startsWith("N")).count(); final long countEditorsStartN = editors.stream().filter(name -> name.startsWith("N")).count();
顯而易見,Lambda表達式須要被重用。咱們能夠將Lambda表達式給保存到一個變量中,就像Java處理其餘任何類型的變量同樣。問題來了?Lambda表達式的類型是什麼呢,在Java這種靜態類型語言中,咱們不能單單使用諸如var,val就來表明一個Lambda表達式。
對於filter方法接受的Lambda表達式,它是符合Predicate接口類型的,所以能夠聲明以下:
final Predicate<String> startsWithN = name -> name.startsWith("N"); final long countFriendsStartN = friends.stream().filter(startsWithN).count(); final long countComradesStartN = comrades.stream().filter(startsWithN).count(); final long countEditorsStartN = editors.stream().filter(startsWithN).count();
可是,問題又來了!若是在某些狀況下須要檢測的不是以N開頭,而是以別的字母如B開頭呢? 那麼,就須要再建立一個Lambda表達式並保存到變量中:
final Predicate<String> startsWithN = name -> name.startsWith("N"); final Predicate<String> startsWithB = name -> name.startsWith("B"); final long countFriendsStartN = friends.stream().filter(startsWithN).count(); final long countFriendsStartB = friends.stream().filter(startsWithB).count();
顯然,這並非長久之計。不能由於須要檢測的首字母不一樣,就建立額外的Lambda表達式。咱們須要它進行進一步的抽象。
第一種方法:
public static Predicate<String> checkIfStartsWith(final String letter) { return name -> name.startsWith(letter); }
經過一個帶參數的方法來獲得須要的Lambda表達式。這個方法就是傳說中的高階函數,由於它返回了一個Lambda表達式做爲返回值,而Lambda表達式本質上是一個函數。
另外,這裏也體現了Java 8中關於Lambda表達式的另一個特性:閉包和做用域。在以上返回的Lambda表達式中引用了一個letter變量,而這個letter變量則是checkIfStartsWith方法接受的參數,就像JavaScript等擁有閉包特性的語言那樣,Java也具備這種特性了。
可是,在Java中利用閉包對變量進行訪問時,有須要注意的問題。咱們只能訪問被final修飾的變量或者本質上是final的變量。正如上面checkIfStartsWith聲明的參數被final修飾那樣。
這是由於,Lambda表達式可能在任什麼時候候被執行,也可能被任何其餘線程執行。因此爲了保證不出現競態條件(Race Condition),須要保證Lambda表達式中引用到的變量不會被改變。
final long countFriendsStartN = friends.stream().filter(checkIfStartsWith("N")).count(); final long countFriendsStartB = friends.stream().filter(checkIfStartsWith("B")).count();
利用上述能夠根據要求動態生成Lambda表達式的高階函數,就能夠按照上面這個樣子來進行代碼重用了。
實際上,使用static來實現以上的高階函數並非一個好主意。能夠將做用域縮小一些:
final Function<String, Predicate<String>> startsWithLetter = (String letter) -> { Predicate<String> checkStartsWith = (String name) -> name.startsWith(letter); return checkStartsWith; };
startsWithLetter變量表明的是一個Lambda表達式,該表達式接受一個String做爲參數,返回另一個Lambda表達式。這也就是它的類型Function>所表明的意義。
目前來看,使用這種方式讓代碼更加複雜了,可是將它簡化以後就成了下面這個樣子:
final Function<String, Predicate<String>> startsWithLetter = (String letter) -> (String name) -> name.startsWith(letter);
還能夠經過省略參數類型進行進一步的簡化:
final Function<String, Predicate<String>> startsWithLetter = letter -> name -> name.startsWith(letter);
乍一看也許以爲上面的形式太複雜,其實否則,你只是須要時間來適應這種簡練的表達方式。
那麼,咱們須要實現的代碼就能夠這樣寫了:
final long countFriendsStartN = friends.stream().filter(startsWithLetter.apply("N")).count(); final long countFriendsStartB = friends.stream().filter(startsWithLetter.apply("B")).count();
使用startsWithLetter.apply("N")
的結果是獲得了Lambda表達式,它做爲參數被傳入到了filter方法中。剩下的事情,就和以前的代碼同樣了。
目前,已經出現了兩種類型的函數式接口(Functional Interface)。它們分別是filter方法使用的Predicate和map方法使用的Function。其實從Java 8的源代碼來看,它們的概念其實是至關簡單的:
@FunctionalInterface
public interface Predicate<T> { boolean test(T t); // others... } @FunctionalInterface public interface Function<T, R> { R apply(T t); // others... }
Predicate能夠看作是Function的一個特例,即Function表明的就是Predicate。
好比,當咱們須要打印出集合中第一個以某字母開頭的元素時,最「天然」的實現以下:
public static void pickName( final List<String> names, final String startingLetter) { String foundName = null; for(String name : names) { if(name.startsWith(startingLetter)) { foundName = name; break; } } System.out.print(String.format("A name starting with %s: ", startingLetter)); if(foundName != null) { System.out.println(foundName); } else { System.out.println("No name found"); } }
雖然是最「天然」的實現方式,可是它太太太醜陋了。從將foundName設置成null開始,這段代碼充斥着一些代碼的「壞味道」。正由於變量被設置成了null,爲了不臭名昭著的NullPointerException,咱們必須在使用它以前進行空檢查。除此以外,聲明瞭可變變量,使用了冗長的外部遍歷,沒有儘可能實現不可變性也是這段代碼具備的問題。
然而,任務自己是很簡單的。咱們只是想打印集合中第一個符合某種條件的元素而已。
此次,使用Lambda表達式來實現:
public static void pickName( final List<String> names, final String startingLetter) { final Optional<String> foundName = names.stream() .filter(name ->name.startsWith(startingLetter)) .findFirst(); System.out.println(String.format("A name starting with %s: %s", startingLetter, foundName.orElse("No name found"))); }
以上代碼出現了幾個新概念: 在調用filter後,調用了findFirst方法(找到符合條件的第一個元素),這個方法返回的對象類型時Optional。關於這個Optional,能夠將它理解成一個可能存在,也可能不存在的結果。這樣的話,就能夠避免對返回結果進行空檢查了。對於結果是否真的存在,可使用isPresent()方法進行判斷,而get()方法用於嘗試對結果的獲取。當結果不存在時,咱們也可使用orElse()來指定一個替代結果,正如上面使用的那樣。
另外,當結果存在時,經過使用ifPresent方法也能夠運行某一段代碼,運行的代碼能夠經過Lambda表達式聲明:
foundName.ifPresent(name -> System.out.println("Hello " + name));
可是,對於使用Lambda表達式實現的pickName方法,它作的工做是否會比命令式的實現方式更多呢?由於能夠發現,在命令式實現中,當咱們發現了第一個符號條件的元素以後,for循環會被當即終止。而findFirst是否也會執行類型的操做,當發現第一個符號條件的元素後,及時中斷剩下的操做呢?答案是確定的,關於這一點會在後面的文章中會進行介紹。
和前面的種種操做不一樣,對於集合的歸約(Collection Reduction),元素與元素再也不是獨立的,它們會經過某種歸約操做聯繫在一塊兒。
好比獲得名字集合的總字符數,就是一種典型的求和歸約。能夠實現以下:
System.out.println("Total number of characters in all names: " + friends.stream() .mapToInt(name -> name.length()) .sum());
經過stream實例的mapToInt方法,咱們能夠很方便地將一個字符串集合轉換成一個整型數集合。而後調用sum方法獲得整型數集合的和值。這裏有一些實現細節,好比mapToInt方法獲得的是一個Stream類型的子類型IntStream的實例,sum方法就是定義在IntStream類型之上。與IntStream相似,還有LongStream和DoubleStream類型,這些類型的存在是爲了提供一些類型相關的操做,讓代碼調用更簡潔。
好比,和sum()方法相似地,還有max(),min(),average()等一系列方法用來實現經常使用的歸約。
//計算 count, min, max, sum, and average for numbers
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
IntSummaryStatistics stats = numbers
.stream()
.mapToInt((x) -> x)
.summaryStatistics();
System.out.println("List中最大的數字 : " + stats.getMax());
System.out.println("List中最小的數字 : " + stats.getMin());
System.out.println("全部數字的總和 : " + stats.getSum());
System.out.println("全部數字的平均值 : " + stats.getAverage());
可是歸根到底,這些方法最終使用到的是一個叫作reduce()的方法。reduce方法的工做原理,能夠這樣歸納:在對一個集合中的元素按照順序進行兩兩操做時,根據某種策略來獲得一個結果,獲得的結果將做爲一個元素參與到下一次操做中,最終這個集合會被歸約成爲一個結果。這個結果也就是reduce方法的返回值。
所以,當咱們須要尋找並打印一個集合中最長的名字時(長度相同時,打印第一個),能夠以下實現:
final Optional<String> aLongName = friends.stream() .reduce((name1, name2) -> name1.length() >= name2.length() ? name1 : name2); aLongName.ifPresent(name -> System.out.println(String.format("A longest name: %s", name)));
咱們來分析一下Lambda表達式:
(name1, name2) -> name1.length() >= name2.length() ? name1 : name2)
是否是符合咱們歸納的關於reduce方法的工做原理。
第一次執行兩兩操做時,name1和name2表明的是集合中的第一個和第二個元素,當第一個元素的長度大於等於第二個元素時,將第一個元素保留下來,不然保留第二個元素。 第二次執行兩兩操做時,name1表明的是上一次操做中被保留下來的擁有較長長度的元素,name2表明的是第三個元素。 以此類推...最後獲得的結果就是集合中第一個擁有最長長度的元素了。
實際上,reduce方法接受的Lambda表達式的行爲被抽象成了BinaryOperator接口:
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> { // others... } @FunctionalInterface public interface BiFunction<T, U, R> { /** * Applies this function to the given arguments. * * @param t the first function argument * @param u the second function argument * @return the function result */ R apply(T t, U u); // others... }
源碼也反映了BinaryOperator和另外一個函數式接口BiFunction之間的關係,當BiFunction接口中接受的三個參數類型一致時,也就成爲了一個BinaryOperator接口。所以,前者其實是後者的一個特例。
另外須要注意的幾點:
好比,如下代碼爲reduce方法傳入了默認值:
final String steveOrLonger = friends.stream() .reduce("Steve", (name1, name2) -> name1.length() >= name2.length() ? name1 : name2);
在過去,咱們使用for循環來鏈接一個集合中的全部元素:
for(String name : friends) { System.out.print(name + ", "); } System.out.println();
上述代碼的問題是,在最後一個名字後面也出現了討人厭的逗號!爲了修復這個問題:
for(int i = 0; i < friends.size() - 1; i++) { System.out.print(friends.get(i) + ", "); } if(friends.size() > 0) System.out.println(friends.get(friends.size() - 1));
嗯,結果是正確了,可是你能忍受如此醜陋的代碼嗎?
爲了解決這個很是很是常見的問題,Java 8中終於引入了一個StringJoiner類。 能夠經過調用String類型的join方法完成這個操做:
System.out.println(String.join(", ", friends));
StringJoiner其實還可以對元素的鏈接操做進行更多的控制。好比爲每一個元素添加前綴,後綴而後再進行鏈接。具體的使用方法能夠去參考API文檔。
固然,使用reduce方法也可以完成對於集合元素的鏈接操做,畢竟集合元素的鏈接也是一種歸約。只不過,正如前面看到的那樣,reduce方法太過於底層了。針對這個問題,Stream類型還定義了一個collect方法用來完成一些常見的歸約操做:
System.out.println(friends.stream().map(String::toUpperCase).collect(Collectors.joining(", ")));
可見collect方法並不本身完成歸約操做,它會將歸約操做委託給一個具體的Collector,而Collectors類型則是一個工具類,其中定義了許多常見的歸約操做,好比上述的joining Collector
簡單的基本操做
2、流的操做:
流的操做能夠歸結爲幾種:
一、遍歷操做(map):
使用map操做能夠遍歷集合中的每一個對象,並對其進行操做,map以後,用.collect(Collectors.toList())會獲得操做後的集合。
1.一、遍歷轉換爲大寫:
List<String> output = wordList.stream().
map(String::toUpperCase).
collect(Collectors.toList());
1.二、平方數:
List<Integer> nums = Arrays.asList(1, 2, 3, 4);
List<Integer> squareNums = nums.stream().
map(n -> n * n).
collect(Collectors.toList());
二、過濾操做(filter):
使用filter能夠對象Stream中進行過濾,經過測試的元素將會留下來生成一個新的Stream
List<Person> phpProgrammers = new ArrayList<Person>();
phpProgrammers.add(new Person("Jarrod", "Pace", "PHP programmer", "male", 34, 1550));
phpProgrammers.add(new Person("Clarette", "Cicely", "PHP programmer", "female", 23, 1200));
phpProgrammers.add(new Person("Victor", "Channing", "PHP programmer", "male", 32, 1600));
//根據本身的規則判斷集合中是否存在某些數據
boolean isExits = phpProgrammers.stream().anyMatch(obj -> (obj.getFirstName().equals("Quinn") && obj.getLastName().equals("Tamara")));
System.out.println(isExits+"===isExits");
//根據本身的規則判斷集合中是否存在某些數據添加到新的集合並打印
List<Person> lists = phpProgrammers.parallelStream().filter(php->(php.getFirstName().equals("Quinn") && php.getLastName().equals("Tamara")))
.collect(Collectors.toList());
lists.forEach(li->System.out.println(li.getFirstName()));
2.一、獲得其中不爲空的String
List<String> filterLists = new ArrayList<>();
filterLists.add("");
filterLists.add("a");
filterLists.add("b");
List afterFilterLists = filterLists.stream()
.filter(s -> !s.isEmpty())
.collect(Collectors.toList());
三、循環操做(forEach):
若是隻是想對流中的每一個對象進行一些自定義的操做,可使用forEach:
List<String> forEachLists = new ArrayList<>();
forEachLists.add("a");
forEachLists.add("b");
forEachLists.add("c");
forEachLists.stream().forEach(s-> System.out.println(s));
四、返回特定的結果集合(limit/skip):
limit 返回 Stream 的前面 n 個元素;skip 則是扔掉前 n 個元素:
List<String> forEachLists = new ArrayList<>();
forEachLists.add("a");
forEachLists.add("b");
forEachLists.add("c");
forEachLists.add("d");
forEachLists.add("e");
forEachLists.add("f");
List<String> limitLists = forEachLists.stream().skip(2).limit(3).collect(Collectors.toList());
注意skip與limit是有順序關係的,好比使用skip(2)會跳過集合的前兩個,返回的爲c、d、e、f,而後調用limit(3)會返回前3個,因此最後返回的c,d,e
五、排序(sort/min/max/distinct):
sort能夠對集合中的全部元素進行排序。max,min能夠尋找出流中最大或者最小的元素,而distinct能夠尋找出不重複的元素:
5.一、對一個集合進行排序:
List<Integer> sortLists = new ArrayList<>();
sortLists.add(1);
sortLists.add(4);
sortLists.add(6);
sortLists.add(3);
sortLists.add(2);
List<Integer> afterSortLists = sortLists.stream().sorted((In1,In2)->
In1-In2).collect(Collectors.toList());
5.二、獲得其中長度最大的元素:
List<String> maxLists = new ArrayList<>();
maxLists.add("a");
maxLists.add("b");
maxLists.add("c");
maxLists.add("d");
maxLists.add("e");
maxLists.add("f");
maxLists.add("hahaha");
int maxLength = maxLists.stream().mapToInt(s->s.length()).max().getAsInt();
System.out.println("字符串長度最長的長度爲"+maxLength);
5.三、對一個集合進行查重:
List<String> distinctList = new ArrayList<>();
distinctList.add("a");
distinctList.add("a");
distinctList.add("c");
distinctList.add("d");
List<String> afterDistinctList = distinctList.stream().distinct().collect(Collectors.toList());
其中的distinct()方法能找出stream中元素equal(),即相同的元素,並將相同的去除,上述返回即爲a,c,d。
六、匹配(Match方法):
有的時候,咱們只須要判斷集合中是否所有知足條件,或者判斷集合中是否有知足條件的元素,這時候就可使用match方法:
allMatch:Stream 中所有元素符合傳入的 predicate,返回 true
anyMatch:Stream 中只要有一個元素符合傳入的 predicate,返回 true
noneMatch:Stream 中沒有一個元素符合傳入的 predicate,返回 true
6.一、判斷集合中沒有有爲‘c’的元素:
List<String> matchList = new ArrayList<>();
matchList.add("a");
matchList.add("a");
matchList.add("c");
matchList.add("d");
boolean isExits = matchList.stream().anyMatch(s -> s.equals("c"));
6.二、判斷集合中是否全不爲空:
List<String> matchList = new ArrayList<>();
matchList.add("a");
matchList.add("");
matchList.add("a");
matchList.add("c");
matchList.add("d");
boolean isNotEmpty = matchList.stream().noneMatch(s -> s.isEmpty());
則返回的爲false
List<String> list1 = new ArrayList();
list1.add("1111");
list1.add("2222");
list1.add("3333");
List<String> list2 = new ArrayList();
list2.add("3333");
list2.add("qqqq");
list2.add("bbb33");
list2.add("qnnjnj");
list2.add("qhghgh");
// 交集
List<String> intersection = list1.parallelStream().filter(item -> list2.contains(item)).collect(toList());
System.out.println("---獲得交集 intersection---");
intersection.parallelStream().forEach(System.out :: println);
// 差集 (list1 - list2)
List<String> reduce1 = list1.parallelStream().filter(item -> !list2.contains(item)).collect(toList());
System.out.println("---獲得差集 reduce1 (list1 - list2)---");
reduce1.parallelStream().forEach(System.out :: println);
// 差集 (list2 - list1)
List<String> reduce2 = list2.parallelStream().filter(item -> !list1.contains(item)).collect(toList());
System.out.println("---獲得差集 reduce2 (list2 - list1)---");
reduce2.parallelStream().forEach(System.out :: println);
// 並集
List<String> listAll = list1.parallelStream().collect(toList());
List<String> listAll2 = list2.parallelStream().collect(toList());
listAll.addAll(listAll2);
System.out.println("---獲得並集 listAll---");
listAll.parallelStream().forEach(System.out :: println);
// 去重並集
List<String> listAllDistinct = listAll.stream().distinct().collect(toList());
System.out.println("---獲得去重並集 listAllDistinct---");
listAllDistinct.parallelStream().forEach(System.out :: println);
System.out.println("---原來的List1---");
list1.parallelStream().forEach(System.out :: println);
System.out.println("---原來的List2---");
list2.parallelStream().forEach(System.out :: println);
獲取元素的
https://blog.csdn.net/wangmuming/article/details/72743790
在這篇文章中,咱們將向您展現如何使用java 8 Stream 對列表分組,計數,求和和排序。
Collectors
今天使用lambda表達式處理集合時,發現對return、break以及continue的使用有點迷惑,因而本身動手測試了一下,才發如今使用foreach()處理集合時不能使用break和continue這兩個方法,也就是說不能按照普通的for循環遍歷集合時那樣根據條件來停止遍歷,而若是要實如今普通for循環中的效果時,可使用return來達到,也就是說若是你在一個方法的lambda表達式中使用return時,這個方法是不會返回的,而只是執行下一次遍歷,看以下的測試代碼:
上述代碼的輸出結果是以下圖所示:
能夠看出return起到的做用和continue是相同的。
想知道這是爲何,在Stack Overflow中找到一個答案,主要是說foreach()不是一個循環,不是設計爲能夠用break以及continue來停止的操做。
https://blog.csdn.net/abcwywht/article/details/77991868
https://blog.csdn.net/dm_vincent/article/details/40340291
本文基原本自這兩個網站 對學習拉姆達的初學者頗有用處 想升級本身的能夠好好學學