java8新特性學習:stream與lambda
Streams api
對 Stream 的使用就是實現一個 filter-map-reduce 過程,產生一個最終結果,或者導致一個副作用(side effect)。
流的操作類型分為兩種:
- Intermediate:一個流可以後面跟隨零個或多個 intermediate 操作。其目的主要是打開流,做出某種程度的數據映射/過濾,然後返回一個新的流,交給下一個操作使用。這類操作都是惰性化的(lazy),就是說,僅僅調用到這類方法,並沒有真正開始流的遍歷。
- Terminal:==一個流只能有一個 terminal 操作==,當這個操作執行後,流就被使用“光”了,無法再被操作。所以這必定是流的最後一個操作。Terminal 操作的執行,才會真正開始流的遍歷,並且會生成一個結果,或者一個 side effect。
- short-circuiting。用以指:
對於一個 intermediate 操作,如果它接受的是一個無限大(infinite/unbounded)的 Stream,但返回一個有限的新 Stream。
對於一個 terminal 操作,如果它接受的是一個無限大的 Stream,但能在有限的時間計算出結果。
當操作一個無限大的 Stream,而又希望在有限時間內完成操作,則在管道內擁有一個 short-circuiting 操作是必要非充分條件。
map/flatMap
map 生成的是個 1:1 映射,每個輸入元素,都按照規則轉換成為另外一個元素。還有一些場景,是一對多映射關系的,這時需要 flatMap。
reduce
主要作用是把 Stream 元素組合起來。它提供一個起始值(種子),然後依照運算規則(BinaryOperator),和前面 Stream 的第一個、第二個、第 n 個元素組合。從這個意義上說,字符串拼接、數值的 sum、min、max、average 都是特殊的 reduce。
Optional
Optional 中我們真正可依賴的應該是==除了 isPresent() 和 get() 的其他方法:==
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) public T orElse(T other) public T orElseGet(Supplier<? extends T> other) public void ifPresent(Consumer<? super T> consumer) public Optional<T> filter(Predicate<? super T> predicate) public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X
構造方法
Optional 的三種構造方式:
- Optional.of(obj),
- Optional.ofNullable(obj) 和
- 明確的 Optional.empty()
- 存在即返回, 無則提供默認值
return user.orElse(null); //而不是 return user.isPresent() ? user.get() : null;
return user.orElse(UNKNOWN_USER);
- 存在即返回, 無則由函數來產生
return user.orElseGet(() -> fetchAUserFromDatabase()); //而不要 return user.isPresent() ? user: fetchAUserFromDatabase();
存在才對它做點什麽
user.isPresent(System.out::println);
map 函數隆重登場
當 user.isPresent() 為真, 獲得它關聯的 orders, 為假則返回一個空集合時, 我們用上面的 orElse, orElseGet 方法都乏力時, 那原本就是 map 函數的責任
return user.map(u -> u.getOrders()).orElse(Collections.emptyList())
return user.map(u -> u.getUsername())
.map(name -> name.toUpperCase())
.orElse(null);
- flatMap
flatMap方法與map方法類似,區別在於mapping函數的返回值不同。map方法的mapping函數返回值可以是任何類型T,而flatMap方法的mapping函數必須是Optional。
upperName = name.flatMap((value) -> Optional.of(value.toUpperCase()));
System.out.println(upperName.orElse("No value found"));//輸出SANAULLA
- filter
如果有值並且滿足斷言條件返回包含該值的Optional,否則返回空Optional。
Optional<String> longName = name.filter((value) -> value.length() > 6);
System.out.println(longName.orElse("The name is less than 6 characters"));//輸出Sanaulla
//另一個例子是Optional值不滿足filter指定的條件。
Optional<String> anotherName = Optional.of("Sana");
Optional<String> shortName = anotherName.filter((value) -> value.length() > 6);
//輸出:name長度不足6字符
System.out.println(shortName.orElse("The name is less than 6 characters"));
lambda
Java中Lambda表達式的使用
雖然看著很先進,其實Lambda表達式的本質只是一個"語法糖",由編譯器推斷並幫你轉換包裝為常規的代碼,因此你可以使用更少的代碼來實現同樣的功能。
建議不要亂用,因為這就和某些很高級的黑客寫的代碼一樣,簡潔,難懂,難以調試,維護人員想罵娘.
當開發者在編寫Lambda表達式時,也會隨之被編譯成一個函數式接口。
lambda表達式有個限制,那就是只能引用 final 或 final 局部變量,這就是說不能在lambda內部修改定義在域外的變量。
Lambda表達式的語法
基本語法:
(params) -> expression
(params) -> statement
(params) -> { statements }
// 1. 不需要參數,返回值為 5
() -> 5
// 2. 接收一個參數(數字類型),返回其2倍的值
x -> 2 * x
// 3. 接受2個參數(數字),並返回他們的差值
(x, y) -> x – y
// 4. 接收2個int型整數,返回他們的和
(int x, int y) -> x + y
// 5. 接受一個 string 對象,並在控制臺打印,不返回任何值(看起來像是返回void)
(String s) -> System.out.print(s)
players.forEach((player) -> System.out.print(player + "; "));
// 在 Java 8 中使用雙冒號操作符(double colon operator)
players.forEach(System.out::println);
// 使用匿名內部類
btn.setOnAction(event -> System.out.println("Hello World!"));
show.addActionListener((e) -> {
System.out.println("Light, Camera, Action !! Lambda expressions Rocks");
});
new Thread(() -> System.out.println("Hello world !")).start();
Runnable race2 = () -> System.out.println("Hello world !");
//排序
Arrays.sort(players, (String s1, String s2) -> (s1.compareTo(s2)));
如何在lambda表達式中加入Predicate
Predicate<String> startsWithJ = (n) -> n.startsWith("J"); Predicate<String> fourLetterLong = (n) -> n.length() == 4; names.stream() .filter(startsWithJ.and(fourLetterLong)) .forEach((n) -> System.out.print("nName, which starts with ‘J‘ and four letter long is : " + n));
Java 8中使用lambda表達式的Map和Reduce示例
List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500); double bill = costBeforeTax.stream().map((cost) -> cost + .12*cost).reduce((sum, cost) -> sum + cost).get();
通過過濾創建一個String列表
// 創建一個字符串列表,每個字符串長度大於2 List<String> filtered = strList.stream().filter(x -> x.length()> 2).collect(Collectors.toList()); System.out.printf("Original List : %s, filtered list : %s %n", strList, filtered);
對列表的每個元素應用函數
// 將字符串換成大寫並用逗號鏈接起來 List<String> G7 = Arrays.asList("USA", "Japan", "France", "Germany", "Italy", "U.K.","Canada"); String G7Countries = G7.stream().map(x -> x.toUpperCase()).collect(Collectors.joining(", ")); System.out.println(G7Countries);
計算集合元素的最大值、最小值、總和以及平均值
//獲取數字的個數、最小值、最大值、總和以及平均值 List<Integer> primes = Arrays.asList(2, 3, 5, 7, 11, 13, 17, 19, 23, 29); IntSummaryStatistics stats = primes.stream().mapToInt((x) -> x).summaryStatistics(); System.out.println("Highest prime number in List : " + stats.getMax()); System.out.println("Lowest prime number in List : " + stats.getMin()); System.out.println("Sum of all prime numbers : " + stats.getSum()); System.out.println("Average of all prime numbers : " + stats.getAverage());
方法引用
- 第一種方法引用是構造器引用,它的語法是Class::new,或者更一般的Class< T >::new。
- 第二種方法引用是靜態方法引用,它的語法是Class::static_method。
- 第三種方法引用是特定類的任意對象的方法引用,它的語法是Class::method。請註意,這個方法沒有參數。
String[] stringArray = { "Barbara", "James", "Mary", "John",
"Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, String::compareToIgnoreCase);
The equivalent lambda expression for the method reference String::compareToIgnoreCase would have the formal parameter list (String a, String b), where a and b are arbitrary names used to better describe this example. The method reference would invoke the method a.compareToIgnoreCase(b).
public void repair() {
System.out.println( "Repaired " + this.toString() );
}
cars.forEach( Car::repair );
- 第四種方法引用是特定對象的方法引用,它的語法是instance::method。請註意,這個方法接受一個Car類型的參數
class ComparisonProvider {
public int compareByName(Person a, Person b) {
return a.getName().compareTo(b.getName());
}
public int compareByAge(Person a, Person b) {
return a.getBirthday().compareTo(b.getBirthday());
}
}
ComparisonProvider myComparisonProvider = new ComparisonProvider();
Arrays.sort(rosterAsArray, myComparisonProvider::compareByName);
final Car police = Car.create( Car::new );
cars.forEach( police::follow );
public void follow( final Car another ) {
System.out.println( "Following the " + another.toString() );
}
Java虛擬機(JVM)的新特性
PermGen空間被移除了,取而代之的是Metaspace(JEP 122)。JVM選項-XX:PermSize與-XX:MaxPermSize分別被-XX:MetaSpaceSize與-XX:MaxMetaspaceSize所代替。
參考
- 使用 Java8 Optional 的正確姿勢
- Java 8 Optional類深度解析
- Java8 lambda表達式10個示例
- Java 8 中的 Streams API 詳解
- Java 8新特性終極指南
tips:本文屬於自己學習和實踐過程的記錄,很多圖和文字都粘貼自網上文章,沒有註明引用請包涵!如有任何問題請留言或郵件通知,我會及時回復。
java8新特性學習:stream與lambda