公司最近一直推崇拉姆達。自己懷着好奇心學習
Lambda在集合中的使用
列表的遍歷
“Lambda 表達式”是一個匿名函數,它可以包含表達式和語句,並且可用於創建委托或表達式樹類型.
所有 Lambda 表達式都使用 Lambda 運算符 =>,該運算符讀為“goes to”.該 Lambda 運算符的左邊是輸入參數(如果有),右邊包含表達式或語句塊.Lambda 表達式 x => x * x 讀作“x goes to x times x”.可以將此表達式分配給委托類型提起對於集合的遍歷,恐怕下面的這種方式已經是一種思維定式了吧:
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循環,在這種循環方式中,循環變量是不必要的:
for(String name : friends) { System.out.println(name); }
這種方式,在實現細節上使用的是iterator接口和它的hasNext(),next()方法。
無論使用哪種for循環,它們仍然使用了外部遍歷器(External Iterator)。即在for循環中,你總是有辦法通過諸如break,continue等方式來控制遍歷的過程。
與外部遍歷器相對的,是內部遍歷器(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之所以被稱為內部遍歷器,原因在於一旦它開始執行了,那么遍歷操作就不能夠被輕易中斷。
同時,借助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表達式的左端參數部分。關於方法引用的詳細情況,會在以后進行介紹。
與使用外部遍歷不同,使用內部遍歷的好處在於:
- 放棄使用顯式的for循環,因其與生俱來的順序執行的特性會阻礙並行化。換言之,使用內部遍歷時,程序的並行程度能夠很容易地被提高。
- 聲明式的代碼比命令式的代碼更簡潔,更具有可讀性,更優雅。
列表的變換
將一個集合通過某種計算得到另一個集合是一種常用的操作,也是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實例。
Lambda表達式的重用
比如,當需要對不止一個集合進行操作時:
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方法中。剩下的事情,就和之前的代碼一樣了。
Predicate和Function
目前,已經出現了兩種類型的函數式接口(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方法返回的對象類型時Optional,這是因為待操作的集合可能是空的。
- 當集合只有一個元素時,reduce會立即將該元素作為實際結果以Optional類型返回,不會調用傳入的Lambda表達式。
- reduce方法是會按照集合的順序對其元素進行兩兩操作的,可以額外傳入一個值作為“基礎值”或者“默認值”,那么在第一次進行兩兩操作時,第一個操作對象就是這個額外傳入的值,第二個操作對象是集合中的第一個元素。
比如,以下代碼為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
簡單的基本操作
二、流的操作:
流的操作可以歸結為幾種:
1、遍歷操作(map):
使用map操作可以遍歷集合中的每個對象,並對其進行操作,map之后,用.collect(Collectors.toList())會得到操作后的集合。
1.1、遍歷轉換為大寫:
List<String> output = wordList.stream().
map(String::toUpperCase).
collect(Collectors.toList());
1.2、平方數:
List<Integer> nums = Arrays.asList(1, 2, 3, 4);
List<Integer> squareNums = nums.stream().
map(n -> n * n).
collect(Collectors.toList());
2、過濾操作(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.1、得到其中不為空的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());
3、循環操作(forEach):
如果只是想對流中的每個對象進行一些自定義的操作,可以使用forEach:
List<String> forEachLists = new ArrayList<>();
forEachLists.add("a");
forEachLists.add("b");
forEachLists.add("c");
forEachLists.stream().forEach(s-> System.out.println(s));
4、返回特定的結果集合(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
5、排序(sort/min/max/distinct):
sort可以對集合中的所有元素進行排序。max,min可以尋找出流中最大或者最小的元素,而distinct可以尋找出不重復的元素:
5.1、對一個集合進行排序:
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.2、得到其中長度最大的元素:
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.3、對一個集合進行查重:
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。
6、匹配(Match方法):
有的時候,我們只需要判斷集合中是否全部滿足條件,或者判斷集合中是否有滿足條件的元素,這時候就可以使用match方法:
allMatch:Stream 中全部元素符合傳入的 predicate,返回 true
anyMatch:Stream 中只要有一個元素符合傳入的 predicate,返回 true
noneMatch:Stream 中沒有一個元素符合傳入的 predicate,返回 true
6.1、判斷集合中沒有有為‘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.2、判斷集合中是否全不為空:
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時,這個方法是不會返回的,而只是執行下一次遍歷,看如下的測試代碼:
- List<String> list = Arrays.asList("123", "45634", "7892", "abch", "sdfhrthj", "mvkd");
- list.stream().forEach(e ->{
- if(e.length() >= 5){
- return;
- }
- System.out.println(e);
- });
上述代碼的輸出結果是如下圖所示:

可以看出return起到的作用和continue是相同的。
想知道這是為什么,在Stack Overflow中找到一個答案,主要是說foreach()不是一個循環,不是設計為可以用break以及continue來中止的操作。
https://blog.csdn.net/abcwywht/article/details/77991868
https://blog.csdn.net/dm_vincent/article/details/40340291
本文基本來自這兩個網站 對學習拉姆達的初學者很有用處 想升級自己的可以好好學學
