理論階段
函數接口
- 函數接口是行為的抽象;
- 函數接口是數據轉換器;
java.util.Function包。定義了四個最基礎的函數接口:
- Supplier<T>: 數據提供器,可以提供 T 類型對象;無參的構造器,提供了 get 方法;
- Function<T,R>: 數據轉換器,接收一個 T 類型的對象,返回一個 R類型的對象; 單參數單返回值的行為接口;提供了 apply, compose, andThen, identity 方法;
- Consumer<T>: 數據消費器, 接收一個 T類型的對象,無返回值,通常用於設置T對象的值; 單參數無返回值的行為接口;提供了 accept, andThen 方法;
- Predicate<T>: 條件測試器,接收一個 T 類型的對象,返回布爾值,通常用於傳遞條件函數; 單參數布爾值的條件性接口。提供了 test (條件測試) , and|or|negate(與或非) 方法。
其中, compose, andThen, and, or, negate 用來組合函數接口而得到更強大的函數接口。
其它的函數接口都是通過這四個擴展而來。
- 在參數個數上擴展: 比如接收雙參數的,有 Bi 前綴, 比如 BiConsumer<T,U>, BiFunction<T,U,R> ;
- 在類型上擴展: 比如接收原子類型參數的,有 [Int|Double|Long][Function|Consumer|Supplier|Predicate]
- 特殊常用的變形: 比如 BinaryOperator , 是同類型的雙參數 BiFunction<T,T,T> ,二元操作符 ; UnaryOperator 是 Function<T,T> 一元操作符。
這些函數接口可以接收哪些值呢?
- 類/對象的靜態方法引用、實例方法引用。引用符號為雙冒號 ::
- 類的構造器引用,比如 Class::new
- lambda表達式
聚合器
每一個流式計算的末尾總有一個類似 collect(Collectors.toList()) 的方法調用。collect 是 Stream 的方法,而參數則是聚合器Collector。已有的聚合器定義在Collectors 的靜態方法里。
那么這個聚合器是怎么實現的呢?大部分聚合器都是基於 Reduce 操作實現的。 Reduce ,名曰推導,含有三個要素: 初始值 init, 二元操作符 BinaryOperator, 以及一個用於聚合結果的數據源S。
Reduce 的算法如下:
- 初始化結果 R = init ;
- 每次從 S 中取出一個值 v,通過二元操作符施加到 R 和 v ,產生一個新值賦給 R = BinaryOperator(R, v);重復 STEP2, 直到 S 中沒有值可取為止。
一個聚合器的實現,通常需要提供四要素:
- 一個結果容器的初始值提供器 supplier ;
- 一個用於將每次二元操作的中間結果與結果容器的值進行操作並重新設置結果容器的累積器 accumulator ;
- 一個用於對Stream元素和中間結果進行操作的二元操作符 combiner ;
- 一個用於對結果容器進行最終聚合的轉換器 finisher(可選) 。
Collectors.CollectorImpl 的實現展示了這一點:
static class CollectorImpl<T, A, R> implements Collector<T, A, R> { private final Supplier<A> supplier; private final BiConsumer<A, T> accumulator; private final BinaryOperator<A> combiner; private final Function<A, R> finisher; private final Set<Characteristics> characteristics; CollectorImpl(Supplier<A> supplier, BiConsumer<A, T> accumulator, BinaryOperator<A> combiner, Function<A,R> finisher, Set<Characteristics> characteristics) { this.supplier = supplier; this.accumulator = accumulator; this.combiner = combiner; this.finisher = finisher; this.characteristics = characteristics; } }
自定義聚合器
public class TestCollector implements Collector<Integer, List<Integer>, List<Integer>> { public Supplier<List<Integer>> supplier() { return () -> { List<Integer> res = combiner().apply(Lists.newArrayList(), null); res.add(4); System.out.println("supplier\t" + res); return res; }; } @Override public BiConsumer<List<Integer>, Integer> accumulator() { return (res, num) -> { System.out.println("accumulator\tres\t" + res + "num\t" + num); if (num > res.get(0)) { res.add(num); } }; } @Override public BinaryOperator<List<Integer>> combiner() { System.out.println("combiner"); return (left, right) -> { System.out.println("combiner\tleft\t" + left + "right\t" + right); if (left != null) { return left; } return right; }; } @Override public Function<List<Integer>, List<Integer>> finisher() { return res -> { System.out.println("finisher\t" + res); res.remove(0); return res; }; } @Override public Set<Characteristics> characteristics() { System.out.println("characteristics"); return Collections.emptySet(); } public static void main(String[] args) { List<Integer> test = Stream.of(1, 4, 3, 4, 5, 6, 7, 3, 9, 4).collect(new TestCollector()); System.out.println("result:" + test); } } //尋找比4大的值 combiner characteristics combiner combiner left []right null supplier [4] accumulator res [4]num 1 accumulator res [4]num 4 accumulator res [4]num 3 accumulator res [4]num 4 accumulator res [4]num 5 accumulator res [4, 5]num 6 accumulator res [4, 5, 6]num 7 accumulator res [4, 5, 6, 7]num 3 accumulator res [4, 5, 6, 7]num 9 accumulator res [4, 5, 6, 7, 9]num 4 characteristics finisher [4, 5, 6, 7, 9] result:[5, 6, 7, 9]
流
Stream 主要有四類接口:
- 流到流之間的轉換:比如 filter(過濾), map(映射轉換), mapTo[Int|Long|Double] (到原子類型流的轉換), flatMap(高維結構平鋪),flatMapTo[Int|Long|Double], sorted(排序),distinct(不重復值),peek(執行某種操作,流不變,可用於調試),limit(限制到指定元素數量), skip(跳過若干元素) ;
- 流到終值的轉換: 比如 toArray(轉為數組),reduce(推導結果),collect(聚合結果),min(最小值), max(最大值), count (元素個數), anyMatch (任一匹配), allMatch(所有都匹配), noneMatch(一個都不匹配), findFirst(選擇首元素),findAny(任選一元素) ;
- 直接遍歷: forEach (不保序遍歷,比如並行流), forEachOrdered(保序遍歷) ;
- 構造流: empty (構造空流),of (單個元素的流及多元素順序流),iterate (無限長度的有序順序流),generate (將數據提供器轉換成無限非有序的順序流), concat (流的連接), Builder (用於構造流的Builder對象)
除了 Stream 本身自帶的生成Stream 的方法,數組和容器及StreamSupport都有轉換為流的方法。比如 Arrays.stream , [List|Set|Collection].[stream|parallelStream] , StreamSupport.[int|long|double|]stream;
流的類型主要有:Reference(對象流), IntStream (int元素流), LongStream (long元素流), Double (double元素流) ,定義在類 StreamShape 中,主要將操作適配於類型系統。
λ表達式語法
λ表達式本質上是一個匿名方法。
λ表達式有三部分組成:參數列表,箭頭(->),以及一個表達式或語句塊。
基本語法: (parameters) -> expression或(parameters) ->{ statements; }
lambda表達式的語法由參數列表、箭頭符號->
和函數體組成。函數體既可以是一個表達式,也可以是一個語句塊:
- 表達式:表達式會被執行然后返回執行結果。 語句塊:語句塊中的語句會被依次執行,就像方法中的語句一樣。
return
語句會把控制權交給匿名方法的調用者break
和continue
只能在循環中使用- 如果函數體有返回值,那么函數體內部的每一條路徑都必須返回值
λ表達式的類型
λ表達式的類型,叫做“目標類型(target type)”。λ表達式的目標類型是“函數接口(functional interface)”。
它的定義是:一個接口,如果只有一個顯式聲明的抽象方法,那么它就是一個函數接口。一般用@FunctionalInterface標注(在編譯期可以驗證你這個接口是否是函數接口)(也可以不標)。
λ表達式的類型是由其上下文推導而來。
Callable<String> c = () -> "done";
PrivilegedAction<String> a = () -> "done";
第一個lambda表達式() -> "done"
是Callable
的實例,而第二個lambda表達式則是PrivilegedAction
的實例。
編譯器負責推導lambda表達式的類型。它利用lambda表達式所在上下文所期待的類型進行推導,這個被期待的類型被稱為目標類型。
lambda表達式對目標類型也是有要求的。編譯器會檢查lambda表達式的類型和目標類型的方法簽名(method signature)是否一致。
當且僅當下面所有條件均滿足時,lambda表達式才可以被賦給目標類型T(@FunctionalInterface驗證的維度)
:
T
是一個函數式接口- lambda表達式的參數和
T
的方法參數在數量和類型上一一對應 - lambda表達式的返回值和
T
的方法返回值相兼容(Compatible) - lambda表達式內所拋出的異常和
T
的方法throws
類型相兼容
lambda表達式並不是第一個擁有上下文相關類型的Java表達式:泛型方法調用和“菱形”構造器調用也通過目標類型來進行類型推導:
List<String> ls = Collections.emptyList(); List<Integer> li = Collections.emptyList();
Map<String, Integer> m1 = new HashMap<>(); Map<Integer, String> m2 = new HashMap<>();
變量捕獲(Variable capture)
捕獲的概念在於解決在λ表達式中我們可以使用哪些外部變量(即除了它自己的參數和內部定義的本地變量)的問題。
與內部類非常相似,但有不同點。不同點在於內部類總是持有一個其外部類對象的引用。λ表達式呢,除非在它內部用到了其外部類對象的方法或者成員,否則它就不持有這個對象的引用。
這個特性對內存管理是一件好事:內部類實例會一直保留一個對其外部類實例的強引用,而那些沒有捕獲外部類成員的lambda表達式則不會保留對外部類實例的引用。要知道內部類的這個特性往往會造成內存泄露。
在Java8以前,如果要在內部類訪問外部對象的一個本地變量,那么這個變量必須聲明為final才行。在Java8中,這種限制被去掉了,代之以一個新的概念,有效只讀-“effectively final”。它的意思是你可以聲明為final,也可以不聲明final但是按照final來用,也就是一次賦值永不改變。換句話說,保證它加上final前綴后不會出編譯錯誤。
Java要求本地變量final或者effectively final的原因是多線程並發問題。內部類、λ表達式都有可能在不同的線程中執行,允許多個線程同時修改一個本地變量不符合Java的設計理念。lambda表達式對值封閉,對變量開放。
lambda表達式不支持修改捕獲變量的另一個原因是我們可以使用更好的方式來實現同樣的效果:使用規約(reduction)。
java.util.stream包提供了各種通用的和專用的規約操作(例如sum、min和max)。
int sum = list.stream() .mapToInt(e -> e.size()) .sum();
sum()
等價於下面的規約操作: int sum = list.stream() .mapToInt(e -> e.size()) .reduce(0 , (x, y) -> x + y);
規約需要一個初始值(以防輸入為空)和一個操作符(在這里是加號),然后用下面的表達式計算結果:0 + list[0] + list[1] + list[2] + ...
方法引用(Method references)
任何一個λ表達式都可以代表某個函數接口的唯一方法的匿名描述符。我們也可以使用某個類的某個具體方法來代表這個描述符,叫做方法引用。
方法引用和lambda表達式擁有相同的特性,我們並不需要為方法引用提供方法體,我們可以直接通過方法名稱引用已有方法。
下面是一組例子,教你使用方法引用代替λ表達式:
//c1 與 c2 是一樣的(靜態方法引用)
Comparator<Integer> c2 = (x, y) -> Integer.compare(x, y);
Comparator<Integer> c1 = Integer::compare;
//下面兩句是一樣的(實例方法引用1)
persons.forEach(e -> System.out.println(e));
persons.forEach(System.out::println);
//下面兩句是一樣的(實例方法引用2)
persons.forEach(person -> person.eat());
persons.forEach(Person::eat);
//下面兩句是一樣的(構造器引用)
strList.stream().map(s -> new Integer(s));
strList.stream().map(Integer::new);
還有一些其它的方法引用:
super::toString //引用某個對象的父類方法
String[]::new //引用一個數組的構造器
默認方法和靜態接口方法
Java8中,接口聲明里可以有方法實現了,叫做默認方法。在此之前,接口里的方法全部是抽象方法。
這實際上混淆了接口和抽象類,但一個類仍然可以實現多個接口,而只能繼承一個抽象類。
這么做的原因是:由於Collection庫需要為批處理操作添加新的方法,如forEach(),stream()等,但是不能修改現有的Collection接口——如果那樣做的話所有的實現類都要進行修改,包括很多客戶自制的實現類。所以只好使用這種妥協的辦法。
如此一來,我們就面臨一種類似多繼承的問題。如果類Sub繼承了兩個接口,Base1和Base2,而這兩個接口恰好具有完全相同的兩個默認方法,那么就會產生沖突。這時Sub類就必須通過重載來顯式指明自己要使用哪一個接口的實現(或者提供自己的實現)。
public class Sub implements Base1, Base2 { public void hello() { Base1.super.hello(); //使用Base1的實現 } }
除了默認方法,Java8的接口也可以有靜態方法的實現
public interface MyInterf { String m1(); default String m2() { return "Hello default method!"; } static String m3() { return "Hello static method in Interface!"; } }
生成器函數(Generator function)
有時候一個流的數據源不一定是一個已存在的集合對象,也可能是個“生成器函數”。一個生成器函數會產生一系列元素,供給一個流。
Stream.generate(Supplier<T> s)就是一個生成器函數。其中參數Supplier是一個函數接口,里面有唯一的抽象方法 <T> get()。
下面這個例子生成並打印5個隨機數:Stream.generate(Math::random).limit(5).forEach(System.out::println);
注意這個limit(5),如果沒有這個調用,那么這條語句會永遠地執行下去。也就是說這個生成器是無窮的。這種調用叫做終結操作,或者短路(short-circuiting)操作。
λ表達式與集合類批處理操作(或者叫塊操作)
集合類的批處理操作API的目的是實現集合類的“內部迭代”,並期望充分利用現代多核CPU進行並行計算。
Java8之前集合類的迭代(Iteration)都是外部的,即客戶代碼。而內部迭代意味着改由Java類庫來進行迭代,而不是客戶代碼。
for(Object o: list) { // 外部迭代
System.out.println(o);
}
可以寫成: list.forEach(o -> {System.out.println(o);}); //forEach函數實現內部迭代
集合類(包括List)現在都有一個forEach方法,對元素進行迭代(遍歷),所以我們不需要再寫for循環了。forEach方法接受一個函數接口Consumer做參數,所以可以使用λ表達式。
Java8為集合類引入了另一個重要概念:流(stream)。一個流通常以一個集合類實例為其數據源,然后在其上定義各種操作。
流的API設計使用了管道(pipelines)模式。對流的一次操作會返回另一個流。stream()如同IO的API或者StringBuffer的append方法那樣,從而多個不同的操作可以在一個語句里串起來。
還有一個方法叫parallelStream(),顧名思義它和stream()一樣,只不過指明要並行處理,以期充分利用現代CPU的多核特性。
//給出一個String類型的數組,找出其中所有不重復的素數
public void distinctPrimary(String... numbers) {
List<String> l = Arrays.asList(numbers);
List<Integer> r = l.stream()
.map(e -> new Integer(e))
.filter(e -> Primes.isPrime(e))
.distinct()
.collect(Collectors.toList());
System.out.println("distinctPrimary result is: " + r);
}
第一步:傳入一系列String(假設都是合法的數字),轉成一個List,然后調用stream()方法生成流。
第二步:調用流的map方法把每個元素由String轉成Integer,得到一個新的流。map方法接受一個Function類型的參數,上面介紹了,Function是個函數接口,所以這里用λ表達式。
第三步:調用流的filter方法,過濾那些不是素數的數字,並得到一個新流。filter方法接受一個Predicate類型的參數,上面介紹了,Predicate是個函數接口,所以這里用λ表達式。
第四步:調用流的distinct方法,去掉重復,並得到一個新流。這本質上是另一個filter操作。
第五步:用collect方法將最終結果收集到一個List里面去。collect方法接受一個Collector類型的參數,這個參數指明如何收集最終結果。在這個例子中,結果簡單地收集到一個List中。我們也可以用Collectors.toMap(e->e, e->e)把結果收集到一個Map中,它的意思是:把結果收到一個Map,用這些素數自身既作為鍵又作為值。toMap方法接受兩個Function類型的參數,分別用以生成鍵和值,Function是個函數接口,所以這里都用λ表達式。
你可能會覺得在這個例子里,List l被迭代了好多次,map,filter,distinct都分別是一次循環,效率會不好。實際並非如此。這些返回另一個Stream的方法都是“懶(lazy)”的,而最后返回最終結果的collect方法則是“急(eager)”的。在遇到eager方法之前,lazy的方法不會執行。
當遇到eager方法時,前面的lazy方法才會被依次執行。而且是管道貫通式執行。這意味着每一個元素依次通過這些管道。例如有個元素“3”,首先它被map成整數型3;然后通過filter,發現是素數,被保留下來;又通過distinct,如果已經有一個3了,那么就直接丟棄,如果還沒有則保留。這樣,3個操作其實只經過了一次循環。
使用階段
基本栗子
String[] atp = {"Rafael Nadal", "Novak Djokovic", "Stanislas Wawrinka", "David Ferrer","Roger Federer", "Andy Murray","Tomas Berdych", "Juan Martin Del Potro"}; List<String> players = Arrays.asList(atp); // 以前的循環方式 for (String player : players) { System.out.print(player + "; "); } // 使用 lambda 表達式以及函數操作(functional operation) players.forEach((player) -> System.out.print(player + "; ")); // 在 Java 8 中使用雙冒號操作符(double colon operator) players.forEach(System.out::println);
匿名內部類栗子
// 使用匿名內部類 btn.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { System.out.println("Hello World!"); } }); // 或者使用 lambda expression btn.setOnAction(event -> System.out.println("Hello World!"));
Runable栗子
// 1.1使用匿名內部類 new Thread(new Runnable() { @Override public void run() { System.out.println("Hello world !"); } }).start(); // 1.2使用 lambda expression new Thread(() -> System.out.println("Hello world !")).start(); // 2.1使用匿名內部類 Runnable race1 = new Runnable() { @Override public void run() { System.out.println("Hello world !"); } }; // 2.2使用 lambda expression Runnable race2 = () -> System.out.println("Hello world !"); // 直接調用 run 方法(沒開新線程哦!) race1.run(); race2.run();
使用Lambdas排序集合
String[] players = {"Rafael Nadal", "Novak Djokovic", "Stanislas Wawrinka", "David Ferrer", "Roger Federer", "Andy Murray", "Tomas Berdych", "Juan Martin Del Potro", "Richard Gasquet", "John Isner"}; // 1.1 使用匿名內部類根據 name 排序 players Arrays.sort(players, new Comparator<String>() { @Override public int compare(String s1, String s2) { return (s1.compareTo(s2)); } }); // 1.2 使用 lambda expression 排序 players Comparator<String> sortByName = (String s1, String s2) -> (s1.compareTo(s2)); Arrays.sort(players, sortByName); // 1.3 也可以采用如下形式: Arrays.sort(players, (String s1, String s2) -> (s1.compareTo(s2))); // 1.1 使用匿名內部類根據 surname 排序 players Arrays.sort(players, new Comparator<String>() { @Override public int compare(String s1, String s2) { return (s1.substring(s1.indexOf(" ")).compareTo(s2.substring(s2.indexOf(" ")))); } }); // 1.2 使用 lambda expression 排序,根據 surname Comparator<String> sortBySurname = (String s1, String s2) -> ( s1.substring(s1.indexOf(" ")).compareTo( s2.substring(s2.indexOf(" ")) ) ); Arrays.sort(players, sortBySurname); // 1.3 或者這樣,懷疑原作者是不是想錯了,括號好多... Arrays.sort(players, (String s1, String s2) -> ( s1.substring(s1.indexOf(" ")).compareTo( s2.substring(s2.indexOf(" ")) ) ) ); // 2.1 使用匿名內部類根據 name lenght 排序 players Arrays.sort(players, new Comparator<String>() { @Override public int compare(String s1, String s2) { return (s1.length() - s2.length()); } }); // 2.2 使用 lambda expression 排序,根據 name lenght Comparator<String> sortByNameLenght = (String s1, String s2) -> (s1.length() - s2.length()); Arrays.sort(players, sortByNameLenght); // 2.3 or this Arrays.sort(players, (String s1, String s2) -> (s1.length() - s2.length())); // 3.1 使用匿名內部類排序 players, 根據最后一個字母 Arrays.sort(players, new Comparator<String>() { @Override public int compare(String s1, String s2) { return (s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1)); } }); // 3.2 使用 lambda expression 排序,根據最后一個字母 Comparator<String> sortByLastLetter = (String s1, String s2) -> (s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1)); Arrays.sort(players, sortByLastLetter); // 3.3 or this Arrays.sort(players, (String s1, String s2) -> (s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1)));
使用Lambdas和Streams
public class Person { private String firstName, lastName, job, gender; private int salary, age; public Person(String firstName, String lastName, String job, String gender, int age, int salary) { this.firstName = firstName; this.lastName = lastName; this.gender = gender; this.age = age; this.job = job; this.salary = salary; } // Getter and Setter // . . . . . } List<Person> javaProgrammers = new ArrayList<Person>() { { add(new Person("Elsdon", "Jaycob", "Java programmer", "male", 43, 2000)); add(new Person("Tamsen", "Brittany", "Java programmer", "female", 23, 1500)); add(new Person("Floyd", "Donny", "Java programmer", "male", 33, 1800)); add(new Person("Sindy", "Jonie", "Java programmer", "female", 32, 1600)); add(new Person("Vere", "Hervey", "Java programmer", "male", 22, 1200)); add(new Person("Maude", "Jaimie", "Java programmer", "female", 27, 1900)); add(new Person("Shawn", "Randall", "Java programmer", "male", 30, 2300)); add(new Person("Jayden", "Corrina", "Java programmer", "female", 35, 1700)); add(new Person("Palmer", "Dene", "Java programmer", "male", 33, 2000)); add(new Person("Addison", "Pam", "Java programmer", "female", 34, 1300)); } }; List<Person> phpProgrammers = new ArrayList<Person>() { { add(new Person("Jarrod", "Pace", "PHP programmer", "male", 34, 1550)); add(new Person("Clarette", "Cicely", "PHP programmer", "female", 23, 1200)); add(new Person("Victor", "Channing", "PHP programmer", "male", 32, 1600)); add(new Person("Tori", "Sheryl", "PHP programmer", "female", 21, 1000)); add(new Person("Osborne", "Shad", "PHP programmer", "male", 32, 1100)); add(new Person("Rosalind", "Layla", "PHP programmer", "female", 25, 1300)); add(new Person("Fraser", "Hewie", "PHP programmer", "male", 36, 1100)); add(new Person("Quinn", "Tamara", "PHP programmer", "female", 21, 1000)); add(new Person("Alvin", "Lance", "PHP programmer", "male", 38, 1600)); add(new Person("Evonne", "Shari", "PHP programmer", "female", 40, 1800)); } }; 現在我們使用forEach方法來迭代輸出上述列表: System.out.println("所有程序員的姓名:"); javaProgrammers.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName())); phpProgrammers.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName())); 我們同樣使用forEach方法,增加程序員的工資5%: System.out.println("給程序員加薪 5% :"); Consumer<Person> giveRaise = e -> e.setSalary(e.getSalary() / 100 * 5 + e.getSalary()); javaProgrammers.forEach(giveRaise); phpProgrammers.forEach(giveRaise); 另一個有用的方法是過濾器filter() ,讓我們顯示月薪超過1400美元的PHP程序員: System.out.println("下面是月薪超過 $1,400 的PHP程序員:") phpProgrammers.stream() .filter((p) -> (p.getSalary() > 1400)) .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName())); 我們也可以定義過濾器,然后重用它們來執行其他操作: // 定義 filters Predicate<Person> ageFilter = (p) -> (p.getAge() > 25); Predicate<Person> salaryFilter = (p) -> (p.getSalary() > 1400); Predicate<Person> genderFilter = (p) -> ("female".equals(p.getGender())); System.out.println("下面是年齡大於 24歲且月薪在$1,400以上的女PHP程序員:"); phpProgrammers.stream() .filter(ageFilter) .filter(salaryFilter) .filter(genderFilter) .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName())); // 重用filters System.out.println("年齡大於 24歲的女性 Java programmers:"); javaProgrammers.stream() .filter(ageFilter) .filter(genderFilter) .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName())); 使用limit方法,可以限制結果集的個數: System.out.println("最前面的3個 Java programmers:"); javaProgrammers.stream() .limit(3) .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName())); System.out.println("最前面的3個女性 Java programmers:"); javaProgrammers.stream() .filter(genderFilter) .limit(3) .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName())); 根據名字和薪水排序Java程序員,放到一個list中,然后顯示列表 System.out.println("根據 name 排序,並顯示前5個 Java programmers:"); List<Person> sortedJavaProgrammers = javaProgrammers .stream() .sorted((p, p2) -> (p.getFirstName().compareTo(p2.getFirstName()))) .limit(5) .collect(toList()); sortedJavaProgrammers.forEach((p) -> System.out.printf("%s %s; %n", p.getFirstName(), p.getLastName())); System.out.println("根據 salary 排序 Java programmers:"); sortedJavaProgrammers = javaProgrammers .stream() .sorted( (p, p2) -> (p.getSalary() - p2.getSalary()) ) .collect( toList() ); sortedJavaProgrammers.forEach((p) -> System.out.printf("%s %s; %n", p.getFirstName(), p.getLastName())); 如果我們只對最低和最高的薪水感興趣,比排序后選擇第一個/最后一個 更快的是min和max方法: System.out.println("工資最低的 Java programmer:"); Person pers = javaProgrammers .stream() .min((p1, p2) -> (p1.getSalary() - p2.getSalary())) .get() System.out.printf("Name: %s %s; Salary: $%,d.", pers.getFirstName(), pers.getLastName(), pers.getSalary()) System.out.println("工資最高的 Java programmer:"); Person person = javaProgrammers .stream() .max((p, p2) -> (p.getSalary() - p2.getSalary())) .get() System.out.printf("Name: %s %s; Salary: $%,d.", person.getFirstName(), person.getLastName(), person.getSalary()) 結合 map 方法,我們可以使用 collect 方法來將我們的結果集放到一個字符串,一個 Set 或一個TreeSet中 System.out.println("將 PHP programmers 的 first name 拼接成字符串:"); String phpDevelopers = phpProgrammers .stream() .map(Person::getFirstName) .collect(joining(" ; ")); // 在進一步的操作中可以作為標記(token) System.out.println("將 Java programmers 的 first name 存放到 Set:"); Set<String> javaDevFirstName = javaProgrammers .stream() .map(Person::getFirstName) .collect(toSet()); System.out.println("將 Java programmers 的 first name 存放到 TreeSet:"); TreeSet<String> javaDevLastName = javaProgrammers .stream() .map(Person::getLastName) .collect(toCollection(TreeSet::new)); Streams 還可以是並行的(parallel) System.out.println("計算付給 Java programmers 的所有money:"); int totalSalary = javaProgrammers .parallelStream() .mapToInt(p -> p.getSalary()) .sum(); 以使用summaryStatistics方法獲得stream 中元素的各種匯總數據。 接下來,我們可以訪問這些方法,比如getMax, getMin, getSum或getAverage //計算 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());