java.util.Function包。定義了四個最基礎的函數接口:php
其中, compose, andThen, and, or, negate 用來組合函數接口而獲得更強大的函數接口。java
其它的函數接口都是經過這四個擴展而來。程序員
這些函數接口能夠接收哪些值呢?算法
每個流式計算的末尾總有一個相似 collect(Collectors.toList()) 的方法調用。collect 是 Stream 的方法,而參數則是聚合器Collector。已有的聚合器定義在Collectors 的靜態方法裏。express
那麼這個聚合器是怎麼實現的呢?大部分聚合器都是基於 Reduce 操做實現的。 Reduce ,名曰推導,含有三個要素: 初始值 init, 二元操做符 BinaryOperator, 以及一個用於聚合結果的數據源S。數組
Reduce 的算法以下:多線程
一個聚合器的實現,一般須要提供四要素:併發
Collectors.CollectorImpl 的實現展現了這一點:app
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; } }
自定義聚合器dom
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 主要有四類接口:
除了 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
是一個函數式接口T
的方法參數在數量和類型上一一對應T
的方法返回值相兼容(Compatible)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<>();
捕獲的概念在於解決在λ表達式中咱們可使用哪些外部變量(即除了它本身的參數和內部定義的本地變量)的問題。
與內部類很是類似,但有不一樣點。不一樣點在於內部類老是持有一個其外部類對象的引用。λ表達式呢,除非在它內部用到了其外部類對象的方法或者成員,不然它就不持有這個對象的引用。
這個特性對內存管理是一件好事:內部類實例會一直保留一個對其外部類實例的強引用,而那些沒有捕獲外部類成員的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] + ...
任何一個λ表達式均可以表明某個函數接口的惟一方法的匿名描述符。咱們也可使用某個類的某個具體方法來表明這個描述符,叫作方法引用。
方法引用和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!"; } }
有時候一個流的數據源不必定是一個已存在的集合對象,也多是個「生成器函數」。一個生成器函數會產生一系列元素,供給一個流。
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();
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)));
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());