連接:https://www.jianshu.com/p/936d97ba0362javascript
連接:https://www.jianshu.com/p/41de7b5ac7b9php
本文主要總結了《Java8實戰》,適用於學習 Java8 的同窗,也能夠做爲一個 API 手冊文檔適用,平時使用時可能因爲不熟練,忘記 API 或者語法。css
Lambda 表達式,也可稱爲閉包,它是推進 Java 8 發佈的最重要新特性。
Lambda 容許把函數做爲一個方法的參數(函數做爲參數傳遞進方中)。
使用 Lambda 表達式可使代碼變的更加簡潔緊湊。
如下是lambda表達式的重要特徵:java
// jdk1.8接口 @FunctionalInterface public interface Supplier <T> { T get(); } public class Car { //Supplier是jdk1.8的接口,這裏和lamda一塊兒使用了 public static Car create(final Supplier <Car> supplier) { return supplier.get(); } }
構造器引用
它的語法是Class::new,或者更通常的Class< T >::new實例以下:python
final Car car = Car.create(Car::new); final List <Car> cars = Arrays.asList(car);
無參構造程序員
Supplier <Apple> c1 = Apple::new; Apple a1 = c1.get();
一個參數構造算法
Function <Integer, Apple> c2 = Apple::new; Apple a2 = c2.apply(110);
兩個參數構造數據庫
BiFunction <String, Integer, Apple> c3 = Apple::new; Apple c3 = c3.apply("green", 110);
多個參數構造
可自定義Function實現,如編程
public interface TriFunction <T, U, V, R> { R apply(T t, U u, V v); }
靜態方法引用
它的語法是Class::static_method,實例以下:數組
cars.forEach(Car::collide);
特定類的任意對象的方法引用
它的語法是Class::method實例以下:
cars.forEach(Car::repair);
特定對象的方法引用
它的語法是instance::method實例以下:
final Car police = Car.create(Car::new); cars.forEach(police::follow);
函數式接口(Functional Interface)就是一個有且僅有一個抽象方法,可是能夠有多個非抽象方法的接口。
函數式接口能夠被隱式轉換爲lambda表達式。
函數式接口能夠現有的函數友好地支持 lambda。
JDK 1.8以前已有的函數式接口:
java.util.function 它包含了不少類,用來支持 Java的 函數式編程,該包中的函數式接口有:
接口名 | 參數 | 返回值 | 用途 |
---|---|---|---|
Predicate | T | boolean | 斷言 |
Consumer | T | void | 消費 |
Function<T,R> | T | R | 函數 |
Supplier | None | T | 工廠方法 |
UnaryOperator | T | T | 邏輯非 |
BinaryOperator | (T,T) | T | 二元操做 |
函數式接口各種介紹:
接口 | 描述 |
---|---|
BiConsumer<T,U> | 表明了一個接受兩個輸入參數的操做,而且不返回任何結果 |
BiFunction<T,U,R> | 表明了一個接受兩個輸入參數的方法,而且返回一個結果 |
BinaryOperator<T> | 表明了一個做用於於兩個同類型操做符的操做,而且返回了操做符同類型的結果 |
BiPredicate<T,U> | 表明了一個兩個參數的boolean值方法 |
BooleanSupplier | 表明了boolean值結果的提供方 |
Consumer<T> | 表明了接受一個輸入參數而且無返回的操做 |
DoubleBinaryOperator | 表明了做用於兩個double值操做符的操做,而且返回了一個double值的結果 |
DoubleConsumer | 表明一個接受double值參數的操做,而且不返回結果 |
DoubleFunction<R> | 表明接受一個double值參數的方法,而且返回結果 |
DoublePredicate | 表明一個擁有double值參數的boolean值方法 |
DoubleSupplier | 表明一個double值結構的提供方 |
DoubleToIntFunction | 接受一個double類型輸入,返回一個int類型結果 |
DoubleToLongFunction | 接受一個double類型輸入,返回一個long類型結果 |
DoubleUnaryOperator | 接受一個參數同爲類型double,返回值類型也爲double |
Function<T,R> | 接受一個輸入參數,返回一個結果 |
IntBinaryOperator | 接受兩個參數同爲類型int,返回值類型也爲int |
IntConsumer | 接受一個int類型的輸入參數,無返回值 |
IntFunction<R> | 接受一個int類型輸入參數,返回一個結果 |
IntPredicate | 接受一個int輸入參數,返回一個布爾值的結果 |
IntSupplier | 無參數,返回一個int類型結果 |
IntToDoubleFunction | 接受一個int類型輸入,返回一個double類型結果 |
IntToLongFunction | 接受一個int類型輸入,返回一個long類型結果 |
IntUnaryOperator | 接受一個參數同爲類型int,返回值類型也爲int |
LongBinaryOperator | 接受兩個參數同爲類型long,返回值類型也爲long |
LongConsumer | 接受一個long類型的輸入參數,無返回值 |
LongFunction<R> | 接受一個long類型輸入參數,返回一個結果 |
LongPredicate | R接受一個long輸入參數,返回一個布爾值類型結果 |
LongSupplier | 無參數,返回一個結果long類型的值 |
LongToDoubleFunction | 接受一個long類型輸入,返回一個double類型結果 |
LongToIntFunction | 接受一個long類型輸入,返回一個int類型結果 |
LongUnaryOperator | 接受一個參數同爲類型long,返回值類型也爲long |
ObjDoubleConsumer<T> | 接受一個object類型和一個double類型的輸入參數,無返回值 |
ObjIntConsumer<T> | 接受一個object類型和一個int類型的輸入參數,無返回值 |
ObjLongConsumer<T> | 接受一個object類型和一個long類型的輸入參數,無返回值。 |
Predicate<T> | 接受一個輸入參數,返回一個布爾值結果 |
Supplier<T> | 無參數,返回一個結果 |
ToDoubleBiFunction<T,U> | 接受兩個輸入參數,返回一個double類型結果 |
ToDoubleFunction<T> | 接受一個輸入參數,返回一個double類型結果 |
ToIntBiFunction<T,U> | 接受兩個輸入參數,返回一個int類型結果 |
ToIntFunction<T> | 接受一個輸入參數,返回一個int類型結果 |
ToLongBiFunction<T,U> | 接受兩個輸入參數,返回一個long類型結果 |
ToLongFunction<T> | 接受一個輸入參數,返回一個long類型結果 |
UnaryOperator<T> | 接受一個參數爲類型T,返回值類型也爲T |
@FunctionalInterface
這個標註用於表示該接口會設計成一個函數式接口。若是你用 @FunctionalInterface 定義了一個接口,而它卻不是函數式接口的話,編譯器將返回一個提示緣由的錯誤。例如,錯誤消息多是「Multiple non-overriding abstract methods found in interface Foo」 , 代表存在多個抽象方法。 請注意, @FunctionalInterface 不是必需的, 但對於爲此設計的接口而言, 使用它是比較好的作法。 它就像是 @Override標註表示方法被重寫了。
// 定義函數式接口 @FunctionalInterface public interface BufferedReaderProcessor { String process(BufferedReader b) throws IOException; } // 定義方法 public static String processFile(BufferedReaderProcessor p) throws IOException { try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) { return p.process(br); } } // 調用 String result = processFile(br -> br.readLine() + br.readLine());
Predicate
Predicate <T> 接口是一個函數式接口,它接受一個輸入參數 T,返回一個布爾值結果。
該接口包含多種默認方法來將Predicate組合成其餘複雜的邏輯(好比:與,或,非)。
該接口用於測試對象是 true 或 false。
與:predicate.and()
或:predicate.or()
非:predicate.negate()
a.or(b).and(c) 能夠看做 (a || b) && c
咱們能夠經過如下實例(Java8Tester.java)來了解函數式接口 Predicate <T> 的使用:
public class Java8Tester { public static void main(String args[]) { List < Integer > list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9); // Predicate<Integer> predicate = n -> true // n 是一個參數傳遞到 Predicate 接口的 test 方法 // n 若是存在則 test 方法返回 true System.out.println("輸出全部數據:"); // 傳遞參數 n eval(list, n -> true); // Predicate<Integer> predicate1 = n -> n%2 == 0 // n 是一個參數傳遞到 Predicate 接口的 test 方法 // 若是 n%2 爲 0 test 方法返回 true System.out.println("輸出全部偶數:"); eval(list, n -> n % 2 == 0); // Predicate<Integer> predicate2 = n -> n > 3 // n 是一個參數傳遞到 Predicate 接口的 test 方法 // 若是 n 大於 3 test 方法返回 true System.out.println("輸出大於 3 的全部數字:"); eval(list, n -> n > 3); } public static void eval(List < Integer > list, Predicate < Integer > predicate) { for (Integer n: list) { if (predicate.test(n)) { System.out.println(n + " "); } } } }
Consumer
java.util.function.Consumer<T> 定義了一個名叫 accept 的抽象方法,它接受泛型 T 的對象,沒有返回( void ) 。你若是須要訪問類型 T 的對象,並對其執行某些操做,就可使用這個接口。
public static <T> void forEach(List <T> list, Consumer <T> c) { for (T i: list) { c.accept(i); } } forEach(Arrays.asList(1, 2, 3, 4, 5), System.out::println);
Function
java.util.function.Function<T, R> 接口定義了一個叫做 apply 的方法,它接受一個泛型 T 的對象,並返回一個泛型 R 的對象。
public static <T, R> List <R> map(List <T> list, Function <T, R> f) { List <R> result = new ArrayList < > (); for (T s: list) { result.add(f.apply(s)); } return result; } // [7, 2, 6] List <Integer> l = map(Arrays.asList("lambdas", "in", "action"), String::length);
Function 接口所表明的Lambda表達式複合起來。 Function 接口爲此配了 andThen 和 compose 兩個默認方法,它們都會返回 Function 的一個實例。
andThen 方法會返回一個函數,它先對輸入應用一個給定函數,再對輸出應用另外一個函數。
好比,假設有一個函數 f 給數字加1 (x -> x + 1) ,另外一個函數 g 給數字乘2,你能夠將它們組合成一個函數 h ,先給數字加1,再給結果乘2:
Function <Integer, Integer> f = x -> x + 1; Function <Integer, Integer> g = x -> x * 2; Function <Integer, Integer> h = f.andThen(g); int result = h.apply(1); // 4
數學上會寫做 g(f(x)) 或(g o f)(x)
compose 方法
Function < Integer, Integer > f = x -> x + 1; Function < Integer, Integer > g = x -> x * 2; Function < Integer, Integer > h = f.compose(g); int result = h.apply(1); // 3
數學上會寫做 f(g(x)) 或 (f o g)(x)
Stream主要用於操做集合,更方便的去處理數據。
java.util.stream.Stream 中的 Stream 接口定義了許多操做。它們能夠分爲兩大類:能夠鏈接起來的流操做稱爲中間操做,關閉流的操做稱爲終端操做。能夠理解爲有返回值是Stream的方法是中間操做,返回值非Stream的方法是終端操做。
中間操做會返回另外一個流,可是若是沒有終端操做,中間操做不會執行任何處理。
Stream調用了終端操做以後,若是再調用,拋出如下異常:
java.lang.IllegalStateException: stream has already been operated upon or closed
Stream只能被消費一次!!!
外部迭代與內部迭代
使用 Collection 接口須要用戶去作迭代(好比用 for-each ) ,這稱爲外部迭代。 相反,Streams庫使用內部迭代。
外部迭代:外部迭代實際是使用Iterator對象。
開發中如何選擇兩種迭代方式:
流的使用通常包括三件事
篩選(filter)
該方法會接受一個謂詞(Predicate)(一個返回boolean 的函數)做爲參數,並返回一個包括全部符合謂詞(Predicate)的元素的流。
List <Dish> vegetarianMenu = menu.stream().filter(Dish::isVegetarian).collect(toList());
切片(limit)
該方法會返回一個不超過給定長度的流。所需的長度做爲參數傳遞給 limit 。若是流是有序的,則最多會返回前 n 個元素。若是流是無序的,limit的結果不會以任務順序排列。
List <Dish> dishes = menu.stream().filter(d -> d.getCalories() > 300).limit(3).collect(toList());
去重(distinct )
該方法會返回一個元素各異(根據流所生成元素的hashCode 和equals 方法實現)的流。
List <Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4); numbers.stream().filter(i -> i % 2 == 0).distinct() .forEach(System.out::println);
跳過元素(skip)
返回一個扔掉了前 n 個元素的流。若是流中元素不足 n 個,則返回一個空流。請注意, limit(n) 和 skip(n) 是互補的!
List <Dish> dishes = menu.stream().filter(d -> d.getCalories() > 300).skip(2).collect(toList());
映射(map和flatMap)
map:
一個很是常見的數據處理套路就是從某些對象中選擇信息。好比在SQL裏,你能夠從表中選擇一列。
能夠改變原有流的類型,從新返回一個新的類型集合。
List <String> dishNames = menu.stream().map(Dish::getName).collect(toList());
flatMap:
一個用於把 Stream<String[]> 轉換成Stream<String> 的操做方法,將流扁平化。
Arrays.stream() 的方法能夠接受一個數組併產生一個流。
若是須要把一個String[] arrayOfWords = {"Hello", "World"};轉換成[G, o, o, d, b, y, e, W, o, r, l, d]
String[] arrayOfWords = {"Hello", "World"}; Stream <String> streamOfwords = Arrays.stream(arrayOfWords); streamOfwords.map(word -> word.split("")).flatMap(Arrays::stream).collect(Collectors.toList());
匹配( allMatch和anyMatch和noneMatch )
檢查是否至少匹配一個元素(anyMatch ):
boolean isVegetarian = menu.stream().anyMatch(Dish::isVegetarian);
檢查是否匹配全部元素(allMatch):
boolean isHealthy = menu.stream().allMatch(d -> d.getCalories() < 1000);
檢查是否全部元素不匹配(noneMatch ):
boolean isHealthy = menu.stream().noneMatch(d -> d.getCalories() >= 1000);
查找(findFirst和findAny)
返回當前流中的任意元素(findAny):
Optional <Dish> dish = menu.stream().filter(Dish::isVegetarian).findAny();
獲取集合中第一個元素(findFirst):
Optional <Dish> dish = menu.stream().filter(Dish::isVegetarian).findFirst();
什麼時候使用 findFirst 和 findAny
你可能會想,爲何會同時有 findFirst 和 findAny 呢?答案是並行。找到第一個元素在並行上限制更多。若是你不關心返回的元素是哪一個,請使用 findAny ,由於它在使用並行流時限制較少。
歸約(reduce)
reduce 接受兩個參數:
求和
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
等同於
int sum = numbers.stream().mapToInt(n -> n).sum();
最大值
Optional <Integer> max = numbers.stream().reduce(Integer::max);
等同於
int max = numbers.stream().mapToInt(n -> n).max();
最小值
Optional <Integer> min = numbers.stream().reduce(Integer::min);
等同於
int min = numbers.stream().mapToInt(n -> n).min();
執行原理:
0 做爲Lambda( a )的第一個參數,從流中得到 4 做爲第二個參數( b ) 。 0 + 4 獲得 4 ,它成了新的累積值。而後再用累積值和流中下一個元素 5 調用Lambda,產生新的累積值 9 。接下來,再用累積值和下一個元素 3調用Lambda,獲得 12 。最後,用 12 和流中最後一個元素 9 調Lambda,獲得最終結果 21 。
ps:reduce若是不設置初始值,會返回一個 Optional 對象。
流操做:無狀態和有狀態
無狀態:操做集合數據時,每個元素之間數據不相互影響,如map或者filter等操做。
有狀態:操做集合數據時,元素之間數據有影響,如sort或者distinct等操做,須要知道每一個元素值才能執行處理。
操 做 | 類 型 | 返回類型 | 使用的類型/函數式接口 | 函數描述符 |
---|---|---|---|---|
filter | 中間 | Stream<T> | Predicate<T> | T -> boolean |
distinct | 中間 (有狀態-無界) | Stream<T> | ||
skip | 中間 (有狀態-有界) | Stream<T> | long | |
limit | 中間 (有狀態-有界) | Stream<T> | long | |
map | 中間 | Stream<T> | Function<T, R> | T -> R |
flatMap | 中間 | Stream<T> | Function<T, Stream<R>> | T -> Stream<R> |
sorted | 中間 (有狀態-無界) | Stream<T> | Comparator<T> | (T, T) -> int |
anyMatch | 終端 | boolean | Predicate<T> | T -> boolean |
noneMatch | 終端 | boolean | Predicate<T> | T -> boolean |
allMatch | 終端 | boolean | Predicate<T> | T -> boolean |
findAny | 終端 | Optional<T> | ||
findFirst | 終端 | Optional<T> | ||
forEach | 終端 | void | Consumer<T> | T -> void |
collect | 終端 | R | Collector<T, A, R> | |
reduce | 終端 (有狀態-有界) | Optional<T> | BinaryOperator<T> | (T, T) -> T |
count | 終端 | long |
原始類型流轉換
IntStream 、 DoubleStream 和 LongStream ,分別將流中的元素特化爲 int 、 long 和 double,從而避免了暗含的裝箱成本。轉換的緣由並不在於流的複雜性,而是裝箱形成的複雜性——即相似 int 和 Integer 之間的效率差別。將流轉換爲轉換的經常使用方法是 mapToInt 、 mapToDouble 和 mapToLong 。
轉換回對象流:
要把原始流轉換成通常流(每一個 int 都會裝箱成一個Integer ) ,可使用 boxed 方法。
IntStream intStream = menu.stream().mapToInt(Dish::getCalories); Stream <Integer> stream = intStream.boxed();
默認值 OptionalInt:
Optional 能夠用Integer 、 String 等參考類型來參數化。對於三種原始流特化,也分別有一個 Optional 原始類型特化版本: OptionalInt 、 OptionalDouble 和 OptionalLong 。
OptionalInt maxCalories = menu.stream().mapToInt(Dish::getCalories).max();
int max = maxCalories.orElse(1);
數值範圍(range 和 rangeClosed):
range 和 rangeClosed這兩個方法都是第一個參數接受起始值,第二個參數接受結束值。但
range 是不包含結束值的,而 rangeClosed 則包含結束值。
由值建立流(Stream.of):
Stream.of 經過顯式值建立一個流,它能夠接受任意數量的參數。
Stream <String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
也能夠建立一個空流:
Stream <String> emptyStream = Stream.empty();
由數組建立流(Arrays.stream):
Arrays.stream 從數組建立一個流,它接受一個數組做爲參數。
int[] numbers = {2, 3, 5, 7, 11, 13}; int sum = Arrays.stream(numbers).sum();
由文件生成流:
Java中用於處理文件等I/O操做的NIO API(非阻塞 I/O)已更新,以便利用Stream API。
java.nio.file.Files 中的不少靜態方法都會返回一個流。例如,一個頗有用的方法是Files.lines ,它會返回一個由指定文件中的各行構成的字符串流。
long uniqueWords = 0; try (Stream <String> lines =Files.lines(Paths.get("data.txt"), Charset.defaultCharset())) { uniqueWords = lines.flatMap(line - > Arrays.stream(line.split(" "))) .distinct().count(); } catch (IOException e) {}
由函數生成流:建立無限流:
Stream API提供了兩個靜態方法來從函數生成流: Stream.iterate 和 Stream.generate 。這兩個操做能夠建立所謂的無限流:不像從固定集合建立的流那樣有固定大小的流。由 iterate和 generate 產生的流會用給定的函數按需建立值,所以能夠無窮無盡地計算下去!通常來講,應該使用 limit(n) 來對這種流加以限制,以免打印無窮多個值。
Stream.iterate:
Stream.iterate(0, n - > n + 2).limit(10).forEach(System.out::println);
Stream.generate:
Stream.generate(Math::random).limit(5).forEach(System.out::println);
收集器
彙總(Collectors.counting 、 Collectors.summingInt、 Collectors.summingLong 、Collectors.summingDouble、 Collectors.averagingInt、 Collectors.averagingLong 、Collectors.averagingDouble、Collectors.summarizingInt、Collectors.summarizingLong、Collectors.summarizingDouble):
總數:
Collectors.counting:
long howManyDishes = menu.stream().collect(Collectors.counting());
等同於
long howManyDishes = menu.stream().count();
求和:
Collectors.summingInt:
int totalCalories = menu.stream().collect(Collectors.summingInt(Dish::getCalories));
等同於
int sum = menu.stream().mapToInt(Dish::getCalories).sum();
Collectors.summingLong:
long totalCalories = menu.stream().collect(Collectors.summingLong(Dish::getCalories));
等同於
long sum = menu.stream().mapToLong(Dish::getCalories).sum();
Collectors.summingDouble:
double totalCalories = menu.stream().collect(Collectors.summingDouble(Dish::getCalories));
等同於
double sum = menu.stream().mapToDouble(Dish::getCalories).sum();
平均數:
Collectors.averagingInt:
int avgCalories = menu.stream().collect(Collectors.averagingInt(Dish::getCalories));
Collectors.averagingLong:
long avgCalories = menu.stream().collect(Collectors.averagingLong(Dish::getCalories));
Collectors.averagingDouble:
double avgCalories = menu.stream().collect(Collectors.averagingDouble(Dish::getCalories));
彙總(總和、平均值、最大值和最小值):
Collectors.summarizingInt:
IntSummaryStatistics menuStatistics = menu.stream().collect(Collectors.summarizingInt(Dish::getCalories));
Collectors.summarizingLong:
LongSummaryStatistics menuStatistics = menu.stream().collect(Collectors.summarizingLong(Dish::getCalories));
Collectors.summarizingDouble:
DoubleSummaryStatistics menuStatistics = menu.stream().collect(Collectors.summarizingDouble(Dish::getCalories));
查找流中的最大值和最小值(Collectors.maxBy 和 Collectors.minBy):
Collectors.maxBy :
Comparator <Dish> dishCaloriesComparator = Comparator.comparingInt(Dish::getCalories); Optional <Dish> mostCalorieDish = menu.stream().collect(Collectors.maxBy(dishCaloriesComparator));
鏈接字符串(Collectors.joining):
joining 工廠方法返回的收集器會把對流中每個對象應用 toString 方法獲得的全部字符串鏈接成一個字符串。 joining 在內部使用了 StringBuilder 來把生成的字符串逐個追加起來。 joining 工廠方法有一個重載版本能夠接受元素之間的分界符。
String shortMenu = menu.stream().map(Dish::getName).collect(Collectors.joining());
分隔符:
String shortMenu = menu.stream().map(Dish::getName).collect(Collectors.joining(」, 「));
分組(Collectors.groupingBy):
普通的單參數 groupingBy(f) (其中 f 是分類函數)其實是 groupingBy(f, toList()) 的簡便寫法。
第一個參數是指定以什麼分組
第二個參數是指定使用的Map
第三個參數是指定Collector
Map <Dish.Type, List <Dish>> dishesByType = menu.stream().collect(Collectors.groupingBy(Dish::getType));
多級分組:
Map<Dish.Type, Map<Long, List<Dish>>> dishesByTypeCaloricLevel = menu.stream().collect(Collectors.groupingBy(Dish::getType, Collectors.groupingBy(Dish::getCalories)));
分組彙總:
Map <Dish.Type, Long> typesCount = menu.stream().collect(Collectors.groupingBy(Dish::getType, counting()));
分組彙總最大值:
Map Dish.Type, Optional <Dish>> mostCaloricByType = menu.stream().collect(Collectors.groupingBy(Dish::getType, Collectors.maxBy(Comparator.comparingInt(Dish::getCalories))));
分組結果包裝Optional轉換具體值(Collectors.collectingAndThen)
Map <Dish.Type, Dish> mostCaloricByType = menu.stream().collect(Collectors.groupingBy(Dish::getType,Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparingInt(Dish::getCalories)), Optional::get)));
分組類型轉換(Collectors.mapping):
這個方法接受兩個參數:一個函數對流中的元素作變換,另外一個則將變換的結果對象收集起來。
Map <Dish.Type, Set <CaloricLevel>> caloricLevelsByType = menu.stream().collect(Collectors.groupingBy(Dish::getType, Collectors.mapping( dish - > { if (dish.getCalories() <= 400) return CaloricLevel.DIET; else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL; else return CaloricLevel.FAT; }, Collectors.toSet())));
分區(Collectors.partitioningBy)
分區是分組的特殊狀況:由一個謂詞(返回一個布爾值的函數)做爲分類函數,它稱分區函數。分區函數返回一個布爾值,這意味着獲得的分組 Map 的鍵類型是 Boolean ,因而它最多能夠分爲兩組—— true 是一組, false 是一組。
Map <Boolean, List <Dish>> partitionedMenu = menu.stream().collect(Collectors.partitioningBy(Dish::isVegetarian));
排序(Comparator )
Comparator 接口如今同時包含了默認方法和靜態方法。可使用靜態方法 Comparator.comparing 返回一個 Comparator 對象,該對象提供了一個函數能夠提取排序關鍵字。
並行流
使用並行流能夠經過parallelStream或者parallel方法。對順序流調用 parallel 方法並不意味着流自己有任何實際的變化。它在內部實際上就是設了一個 boolean 標誌,表示你想讓調用 parallel 以後進行的全部操做都並行執行。並行流轉換成順序流使用sequential方法。
並行化並非沒有代價的。並行化過程自己須要對流作遞歸劃分,把每一個子流的概括操做分配到不一樣的線程,而後把這些操做的結果合併成一個值。但在多個內核之間移動數據的代價也可能比你想的要大, 因此很重要的一點是要保證在內核中並行執行工做的時間比在內核之間傳輸數據的時間長。總而言之,不少狀況下不可能或不方便並行化。然而,在使用並行 Stream 加速代碼以前,你必須確保用得對;若是結果錯了,算得快就毫無心義了。讓咱們來看一個常見的陷阱。
分支/合併框架
分支/合併框架的目的是以遞歸方式將能夠並行的任務拆分紅更小的任務,而後將每一個子任務的結果合併起來生成總體結果。它是 ExecutorService 接口的一個實現,它把子任務分配給線程池(稱爲 ForkJoinPool )中的工做線程。首先來看看如何定義任務和子任務。
RecursiveTask:
要把任務提交到這個池, 必須建立 RecursiveTask<R> 的一個子類, 其中 R 是並行化任務 (以及全部子任務)產生的結果類型,或者若是任務不返回結果,則是 RecursiveAction 類型(固然它可能會更新其餘非局部機構) 。要定義 RecursiveTask, 只需實現它惟一的抽象方法compute :
protected abstract R compute();
這個方法同時定義了將任務拆分紅子任務的邏輯,以及沒法再拆分或不方便再拆分時,生成單個子任務結果的邏輯。正因爲此,這個方法的實現相似於下面的僞代碼:
if (任務足夠小或不可分) { 順序計算該任務 } else { 將任務分紅兩個子任務 遞歸調用本方法,拆分每一個子任務,等待全部子任務完成 合併每一個子任務的結果 }
使用分支/合併框架的最佳作法:
雖然分支/合併框架還算簡單易用,不幸的是它也很容易被誤用。如下是幾個有效使用它的最佳作法。
Spliterator:
Spliterator 是Java 8中加入的另外一個新接口;這個名字表明「可分迭代器」 (splitable iterator) 。和 Iterator 同樣, Spliterator 也用於遍歷數據源中的元素,但它是爲了並行執行而設計的。雖然在實踐中可能用不着本身開發 Spliterator ,但瞭解一下它的實現方式會讓你對並行流的工做原理有更深刻的瞭解。Java 8已經爲集合框架中包含的全部數據結構提供了一個默認的 Spliterator 實現。 集合實現了 Spliterator 接口, 接口提供了一個 spliterator 方法。
public interface Spliterator<T> { boolean tryAdvance(Consumer<? super T> action); Spliterator<T> trySplit(); long estimateSize(); int characteristics(); }
與往常同樣, T 是 Spliterator 遍歷的元素的類型。 tryAdvance 方法的行爲相似於普通的Iterator ,由於它會按順序一個一個使用 Spliterator 中的元素,而且若是還有其餘元素要遍歷就返回 true 。 但 trySplit 是專爲 Spliterator 接口設計的, 由於它能夠把一些元素劃出去分給第二個 Spliterator (由該方法返回) ,讓它們兩個並行處理。 Spliterator 還可經過estimateSize 方法估計還剩下多少元素要遍歷,由於即便不那麼確切,能快速算出來是一個值也有助於讓拆分均勻一點。
流拆分過程:
將 Stream 拆分紅多個部分的算法是一個遞歸過程,第一步是對第一個Spliterator 調用 trySplit ,生成第二個 Spliterator 。第二步對這兩個 Spliterator 調用trySplit ,這樣總共就有了四個 Spliterator 。這個框架不斷對 Spliterator 調用 trySplit直到它返回 null ,代表它處理的數據結構不能再分割,最後,這個遞歸拆分過程到第四步就終止了,這時全部的 Spliterator 在調用 trySplit 時都返回了 null 。
這個拆分過程也受 Spliterator 自己的特性影響,而特性是經過 characteristics 方法聲明的,它將返回一個 int ,表明 Spliterator 自己特性集的編碼。使用 Spliterator 的客戶能夠用這些特性來更好地控制和優化它的使用。
Future
Future 接口在Java 5中被引入,設計初衷是對未來某個時刻會發生的結果進行建模。它建模了一種異步計算,返回一個執行運算結果的引用,當運算結束後,這個引用被返回給調用方。在Future 中觸發那些潛在耗時的操做把調用線程解放出來,讓它能繼續執行其餘有價值的工做,再也不須要呆呆等待耗時的操做完成。
ExecutorService executor = Executors.newCachedThreadPool();
Future < Double > future = executor.submit(new Callable <Double> () { public Double call() { return doSomeLongComputation(); } }); doSomethingElse(); try { Double result = future.get(1, TimeUnit.SECONDS); } catch (ExecutionException ee) { // 計算拋出一個異常 } catch (InterruptedException ie) { // 當前線程在等待過程當中被中斷 } catch (TimeoutException te) { // 在Future對象完成以前超過已過時 }
這種編程方式讓你的線程能夠在 ExecutorService 以併發方式調用另外一個線程執行耗時操做的同時,去執行一些其餘的任務。
侷限性:
Future 接口提供了方法來檢測異步計算是否已經結束(使用isDone 方法) ,等待異步操做結束,以及獲取計算的結果。
CompletableFuture
CompletableFuture 的 completeExceptionally 方法將致使 CompletableFuture 內發生問題的異常拋出。客戶端如今會收到一個 ExecutionException 異常,該異常接收了一個包含失敗緣由的Exception 參數。
使用工廠方法 supplyAsync 建立 CompletableFuture:
public Future <Double> getPriceAsync(String product) { return CompletableFuture.supplyAsync(() -> calculatePrice(product)); }
supplyAsync 方法接受一個生產者( Supplier )做爲參數,返回一個 CompletableFuture對象, 該對象完成異步執行後會讀取調用生產者方法的返回值。 生產者方法會交由 ForkJoinPool池中的某個執行線程( Executor )運行,可是你也可使用 supplyAsync 方法的重載版本,傳遞第二個參數指定不一樣的執行線程執行生產者方法。通常而言,向 CompletableFuture 的工廠方法傳遞可選參數,指定生產者方法的執行線程是可行的。
CompletableFuture和stream組合使用:
public List <String> findPrices(String product) { List < CompletableFuture <String>> priceFutures = shops.stream() .map(shop -> CompletableFuture.supplyAsync(() - > shop.getName() + " price is " + shop.getPrice(product))) .collect(Collectors.toList()); return priceFutures.stream() .map(CompletableFuture::join) .collect(Collectors.toList()); }
利用 CompletableFutures 向其提交任務執行是個不錯的主意。處理需大量使用異步操做的狀況時,這幾乎是最有效的策略。
構造同步和異步操做:
public List <String> findPrices(String product) { List <CompletableFuture <String>> priceFutures = shops.stream() .map(shop -> CompletableFuture.supplyAsync( () -> shop.getPrice(product), executor)) .map(future -> future.thenApply(Quote::parse)) .map(future -> future.thenCompose(quote -> CompletableFuture.supplyAsync( () -> Discount.applyDiscount(quote), executor))) .collect(Collectors.toList()); return priceFutures.stream() .map(CompletableFuture::join) .collect(Collectors.toList()); }
Java 8的 CompletableFuture API提供了名爲 thenCompose 的方法,它就是專門爲這一目的而設計的, thenCompose 方法容許你對兩個異步操做進行流水線,第一個操做完成時,將其結果做爲參數傳遞給第二個操做。建立兩個 CompletableFutures 對象,對第一個 CompletableFuture 對象調用 thenCompose ,並向其傳遞一個函數。當第一個CompletableFuture 執行完畢後,它的結果將做爲該函數的參數,這個函數的返回值是以第一個 CompletableFuture 的返回作輸入計算出的第二個 CompletableFuture 對象。thenCompose 方法像 CompletableFuture 類中的其餘方法同樣,也提供了一個以 Async 後綴結尾的版本 thenComposeAsync 。一般而言,名稱中不帶 Async的方法和它的前一個任務同樣,在同一個線程中運行;而名稱以 Async 結尾的方法會將後續的任務提交到一個線程池,因此每一個任務是由不一樣的線程處理的。
方法名 | 描述 |
---|---|
allOf(CompletableFuture<?>... cfs) | 等待全部任務完成,構造後CompletableFuture完成 |
anyOf(CompletableFuture<?>... cfs) | 只要有一個任務完成,構造後CompletableFuture就完成 |
runAsync(Runnable runnable) | 使用ForkJoinPool.commonPool()做爲它的線程池執行異步代碼 |
runAsync(Runnable runnable, Executor executor) | 使用指定的thread pool執行異步代碼 |
supplyAsync(Supplier<U> supplier) | 使用ForkJoinPool.commonPool()做爲它的線程池執行異步代碼,異步操做有返回值 |
supplyAsync(Supplier<U> supplier,Executor executor) | 使用指定的thread pool執行異步代碼,異步操做有返回值 |
complete(T t) | 完成異步執行,並返回future的結果 |
completeExceptionlly(Throwable ex) | 異步執行不正常的結束 |
cancel(boolean mayInterruptIfRunning) | 取消任務的執行。參數指定是否當即中斷任務執行,或者等等任務結束 |
isCancelled() | 任務是否已經取消,任務正常完成前將其取消,則返回 true |
isDone() | 任務是否已經完成。須要注意的是若是任務正常終止、異常或取消,都將返回true |
get() | throws InterruptedException, ExecutionException 等待任務執行結束,而後得到V類型的結果。InterruptedException 線程被中斷異常, ExecutionException任務執行異常,若是任務被取消,還會拋出CancellationException |
get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException | 同上面的get功能同樣,多了設置超時時間。參數timeout指定超時時間,uint指定時間的單位,在枚舉類TimeUnit中有相關的定義。若是計 算超時,將拋出TimeoutException |
thenApply(Function<? super T,? extends U> fn) | 轉換一個新的CompletableFuture對象 |
thenApplyAsync(Function<? super T,? extends U> fn) | 異步轉換一個新的CompletableFuture對象 |
thenApplyAsync(Function<? super T,? extends U> fn, Executor executor) | 使用指定的thread pool執行異步代碼,異步轉換一個新的CompletableFuture對象 |
thenCompose(Function<? super T, ? extends CompletionStage<U>> fn) | 在異步操做完成的時候對異步操做的結果進行一些操做,而且仍然返回CompletableFuture類型 |
thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) | 在異步操做完成的時候對異步操做的結果進行一些操做,而且仍然返回CompletableFuture類型。使用ForkJoinPool |
thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn, Executor executor) | 在異步操做完成的時候對異步操做的結果進行一些操做,而且仍然返回CompletableFuture類型。使用指定的線程池 |
thenAccept(Consumer<? super T> action) | 當CompletableFuture完成計算結果,只對結果執行Action,而不返回新的計算值 |
thenAcceptAsync(Consumer<? super T> action) | 當CompletableFuture完成計算結果,只對結果執行Action,而不返回新的計算值,使用ForkJoinPool |
thenAcceptAsync(Consumer<? super T> action,Executor executor) | 當CompletableFuture完成計算結果,只對結果執行Action,而不返回新的計算值 |
thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn) | 當兩個CompletableFuture都正常完成後,執行提供的fn,用它來組合另一個CompletableFuture的結果 |
thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn) | 當兩個CompletableFuture都正常完成後,執行提供的fn,用它來組合另一個CompletableFuture的結果,使用ForkJoinPool |
thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn, Executor executor) | 當兩個CompletableFuture都正常完成後,執行提供的fn,用它來組合另一個CompletableFuture的結果,使用指定的線程池 |
thenAcceptBoth(CompletionStage<? extends U> other, BiConsumer<? super T,? super U> action) | 當兩個CompletableFuture都正常完成後,執行提供的action,用它來組合另一個CompletableFuture的結果 |
thenAcceptBothAsync(CompletionStage<? extends U> other, BiConsumer<? super T,? super U> action) | 當兩個CompletableFuture都正常完成後,執行提供的action,用它來組合另一個CompletableFuture的結果,使用ForkJoinPool |
thenAcceptBothAsync(CompletionStage<? extends U> other, BiConsumer<? super T,? super U> action, Executor executor) | 當兩個CompletableFuture都正常完成後,執行提供的action,用它來組合另一個CompletableFuture的結果,使用指定的線程池 |
whenComplete(BiConsumer<? super T,? super Throwable> action) | 當CompletableFuture完成計算結果時對結果進行處理,或者當CompletableFuture產生異常的時候對異常進行處理 |
whenCompleteAsync(BiConsumer<? super T,? super Throwable> action) | 當CompletableFuture完成計算結果時對結果進行處理,或者當CompletableFuture產生異常的時候對異常進行處理,使用ForkJoinPool |
whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor) | 當CompletableFuture完成計算結果時對結果進行處理,或者當CompletableFuture產生異常的時候對異常進行處理,使用指定的線程池。 |
handle(BiFunction<? super T, Throwable, ? extends U> fn) | 當CompletableFuture完成計算結果或者拋出異常的時候,執行提供的fn |
handleAsync(BiFunction<? super T, Throwable, ? extends U> fn) | 當CompletableFuture完成計算結果或者拋出異常的時候,執行提供的fn,使用ForkJoinPool |
handleAsync(BiFunction<? super T, Throwable, ? extends U> fn, Executor executor) | 當CompletableFuture完成計算結果或者拋出異常的時候,執行提供的fn,使用指定的線程池 |
Clock
Clock類提供了訪問當前日期和時間的方法,Clock是時區敏感的,能夠用來取代 System.currentTimeMillis() 來獲取當前的微秒數。某一個特定的時間點也可使用Instant類來表示,Instant類也能夠用來建立老的java.util.Date對象。
Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();
Instant instant = clock.instant();
Date legacyDate = Date.from(instant);
LocalDate
該類的實例是一個不可變對象,它只提供了簡單的日期,並不含當天的時間信息。另外,它也不附帶任何與時區相關的信息。經過靜態工廠方法 of 建立一個 LocalDate 實例。 LocalDate 實例提供了多種方法來讀取經常使用的值,好比年份、月份、星期幾等。
LocalDate date = LocalDate.of(2018, 10, 1); int year = date.getYear(); Month month = date.getMonth(); int day = date.getDayOfMonth(); DayOfWeek dow = date.getDayOfWeek(); int len = date.lengthOfMonth(); boolean leap = date.isLeapYear();
等同於
int year = date.get(ChronoField.YEAR); int month = date.get(ChronoField.MONTH_OF_YEAR); int day = date.get(ChronoField.DAY_OF_MONTH);
獲取當前時間:
LocalDate today = LocalDate.now();
LocalTime
一天中的時間,好比13:45:20,可使用 LocalTime 類表示。你可使用 of 重載的兩個工廠方法建立 LocalTime 的實例。 第一個重載函數接收小時和分鐘, 第二個重載函數同時還接收秒。同 LocalDate 同樣, LocalTime 類也提供了一些 getter 方法訪問這些變量的值。
LocalTime time = LocalTime.of(13, 45, 20); int hour = time.getHour(); int minute = time.getMinute(); int second = time.getSecond();
LocalDate 和 LocalTime 均可以經過解析表明它們的字符串建立。使用靜態方法 parse:
LocalDate date = LocalDate.parse("2018-03-18"); LocalTime time = LocalTime.parse("13:45:20");
能夠向 parse 方法傳遞一個 DateTimeFormatter 。該類的實例定義瞭如何格式化一個日
期或者時間對象。它是替換老版 java.util.DateFormat 的推薦替代品。一旦傳遞的字符串參數沒法被解析爲合法的 LocalDate 或 LocalTime 對象, 這兩個 parse 方法都會拋出一個繼承自 RuntimeException 的 DateTimeParseException 異常。
LocalDateTime
這個複合類名叫 LocalDateTime ,是 LocalDate 和 LocalTime 的合體。它同時表示了日期和時間, 但不帶有時區信息, 你能夠直接建立, 也能夠經過合併日期和時間對象構造。
LocalDateTime dt1 = LocalDateTime.of(2018, Month.MARCH, 18, 13, 45, 20); LocalDateTime dt2 = LocalDateTime.of(date, time); LocalDateTime dt3 = date.atTime(13, 45, 20); LocalDateTime dt4 = date.atTime(time); LocalDateTime dt5 = time.atDate(date);
經過它們各自的 atTime 或者 atDate 方法,向 LocalDate 傳遞一個時間對象,或者向LocalTime 傳遞一個日期對象的方式,你能夠建立一個 LocalDateTime 對象。你也可使用toLocalDate 或者 toLocalTime 方法,從 LocalDateTime 中提取 LocalDate 或者 LocalTime組件:
LocalDate date1 = dt1.toLocalDate(); LocalTime time1 = dt1.toLocalTime();
Instant
能夠經過向靜態工廠方法 ofEpochSecond 傳遞一個表明秒數的值建立一個該類的實例。 靜態工廠方法 ofEpochSecond 還有一個加強的重載版本,它接收第二個以納秒爲單位的參數值,對傳入做爲秒數的參數進行調整。重載的版本會調整納秒參數,確保保存的納秒分片在0到999 999999之間。
Instant.ofEpochSecond(3); Instant.ofEpochSecond(3, 0); Instant.ofEpochSecond(2, 1_000_000_000); // 2秒 之 後 再 加上100萬納秒(1秒) Instant.ofEpochSecond(4, -1_000_000_000); // 4秒以前的100萬納秒(1秒)
修改操做:
若是你已經有一個 LocalDate 對象, 想要建立它的一個修改版, 最直接也最簡單的方法是使用 withAttribute 方法。 withAttribute 方法會建立對象的一個副本,並按照須要修改它的屬性。
LocalDate date1 = LocalDate.of(2014, 3, 18); // 2014-03-18 LocalDate date2 = date1.withYear(2011); // 2011-03-18 LocalDate date3 = date2.withDayOfMonth(25); // 2011-03-25 LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 9); // 2011-09-25 LocalDate date1 = LocalDate.of(2014, 3, 18); // 2014-03-18 LocalDate date2 = date1.plusWeeks(1); // 2014-03-25 LocalDate date3 = date2.minusYears(3); // 2011-03-25 LocalDate date4 = date3.plus(6, ChronoUnit.MONTHS); // 2011-09-25
LocalDate 、 LocalTime 、 LocalDateTime 以及 Instant通用方法
方法名 | 是不是靜態方法 | 描述 |
---|---|---|
from | 是 | 依據傳入的 Temporal 對象建立對象實例 |
now | 是 | 依據系統時鐘建立 Temporal 對象 |
of | 是 | 由 Temporal 對象的某個部分建立該對象的實例 |
parse | 是 | 由字符串建立 Temporal 對象的實例 |
atOffset | 否 | 將 Temporal 對象和某個時區偏移相結合 |
atZone | 否 | 將 Temporal 對象和某個時區相結合 |
format | 否 | 使用某個指定的格式器將 Temporal 對象轉換爲字符串 ( Instant 類不提供該方法) |
get | 否 | 讀取 Temporal 對象的某一部分的值 |
minus | 否 | 建立 Temporal 對象的一個副本, 經過將當前 Temporal 對象的值減去必定的時長建立該副本 |
plus | 否 | 建立 Temporal 對象的一個副本, 經過將當前 Temporal 對象的值加上必定的時長建立該副本 |
with | 否 | 以該 Temporal 對象爲模板,對某些狀態進行修改建立該對象的副本 |
LocalDate date = LocalDate.of(2014, 3, 18); date = date.with(ChronoField.MONTH_OF_YEAR, 9); date = date.plusYears(2).minusDays(10); date.withYear(2011);
答案: 2016-09-08 。
每一個動做都會建立一個新的 LocalDate 對象,後續的方法調用能夠操縱前一方法建立的對象。這段代碼的最後一句不會產生任何咱們能看到的效果,由於它像前面的那些操做同樣,會建立一個新的 LocalDate 實例,不過咱們並無將這個新建立的值賦給任何的變量。
Duration
用於比較LocalTime之間的時間差, Duration 類主要用於以秒和納秒衡量時間的長短。
LocalTime time1 = LocalTime.now(); LocalTime time2 = LocalTime.of(11, 0, 0); Duration d1 = Duration.between(time1, time2);
Period
用於比較LocalDate之間的時間差, Period類主要用於以年月日衡量時間的長短。
Period tenDays = Period.between(LocalDate.of(2014, 3, 8), LocalDate.of(2014, 3, 18));
Duration和Period通用方法
方法名 | 是不是靜態方法 | 方法描述 |
---|---|---|
between | 是 | 建立兩個時間點之間的 interval |
from | 是 | 由一個臨時時間點建立 interval |
of | 是 | 由它的組成部分建立 interval的實例 |
parse | 是 | 由字符串建立 interval 的實例 |
addTo | 否 | 建立該 interval 的副本,並將其疊加到某個指定的 temporal 對象 |
get | 否 | 讀取該 interval 的狀態 |
isNegative | 否 | 檢查該 interval 是否爲負值,不包含零 |
isZero | 否 | 檢查該 interval 的時長是否爲零 |
minus | 否 | 經過減去必定的時間建立該 interval 的副本 |
multipliedBy | 否 | 將 interval 的值乘以某個標量建立該 interval 的副本 |
negated | 否 | 以忽略某個時長的方式建立該 interval 的副本 |
plus | 否 | 以增長某個指定的時長的方式建立該 interval 的副本 |
subtractFrom | 否 | 從指定的 temporal 對象中減去該 interval |
TemporalAdjuster
將日期調整到下個週日、下個工做日,或者是本月的最後一天。這時,你可使用重載版本的 with 方法, 向其傳遞一個提供了更多定製化選擇的 TemporalAdjuster 對象,更加靈活地處理日期。
LocalDate date1 = LocalDate.of(2014, 3, 18); LocalDate date2 = date1.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY)); LocalDate date3 = date2.with(TemporalAdjusters.lastDayOfMonth());
方法名 | 描述 |
---|---|
dayOfWeekInMonth | 建立一個新的日期,它的值爲同一個月中每一週的第幾天 |
firstDayOfMonth | 建立一個新的日期,它的值爲當月的第一天 |
firstDayOfNextMonth | 建立一個新的日期,它的值爲下月的第一天 |
firstDayOfNextYear | 建立一個新的日期,它的值爲明年的第一天 |
firstDayOfYear | 建立一個新的日期,它的值爲當年的第一天 |
firstInMonth | 建立一個新的日期,它的值爲同一個月中,第一個符合星期幾要求的值 |
lastDayOfMonth | 建立一個新的日期,它的值爲當月的最後一天 |
lastDayOfNextMonth | 建立一個新的日期,它的值爲下月的最後一天 |
lastDayOfNextYear | 建立一個新的日期,它的值爲明年的最後一天 |
lastDayOfYear | 建立一個新的日期,它的值爲今年的最後一天 |
lastInMonth | 建立一個新的日期,它的值爲同一個月中,最後一個符合星期幾要求的值 |
next/previous | 建立一個新的日期,並將其值設定爲日期調整後或者調整前,第一個符合指定星期幾要求的日期 |
nextOrSame/previousOrSame | 建立一個新的日期,並將其值設定爲日期調整後或者調整前,第一個符合指定星期幾要求的日期,若是該日期已經符合要求,直接返回該對象 |
DateTimeFormatter
處理日期和時間對象時,格式化以及解析日期時間對象是另外一個很是重要的功能。新的java.time.format 包就是特別爲這個目的而設計的。這個包中,最重要的類是 DateTime-Formatter 。 建立格式器最簡單的方法是經過它的靜態工廠方法以及常量。 像 BASIC_ISO_DATE和 ISO_LOCAL_DATE 這 樣 的 常 量 是 DateTimeFormatter 類 的 預 定 義 實 例 。 所 有 的DateTimeFormatter 實例都能用於以必定的格式建立表明特定日期或時間的字符串。
LocalDate date = LocalDate.of(2014, 3, 18); String s1 = date.format(DateTimeFormatter.BASIC_ISO_DATE); // 20140318 String s2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE); // 2014-03-18
等同於
LocalDate date1 = LocalDate.parse("20140318", DateTimeFormatter.BASIC_ISO_DATE); LocalDate date2 = LocalDate.parse("2014-03-18", DateTimeFormatter.ISO_LOCAL_DATE);
和老的 java.util.DateFormat 相比較,全部的 DateTimeFormatter 實例都是線程安全的。因此,你可以以單例模式建立格式器實例,就像 DateTimeFormatter 所定義的那些常量,並能在多個線程間共享這些實例。 DateTimeFormatter 類還支持一個靜態工廠方法,它能夠按照某個特定的模式建立格式器。
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy") LocalDate date1 = LocalDate.of(2014, 3, 18); String formattedDate = date1.format(formatter); LocalDate date2 = LocalDate.parse(formattedDate, formatter);
若是還須要更加細粒度的控制, DateTimeFormatterBuilder 類還提供了更復雜的格式器,你能夠選擇恰當的方法,一步一步地構造本身的格式器。另外,它還提供了很是強大的解析功能,好比區分大小寫的解析、柔性解析(容許解析器使用啓發式的機制去解析輸入,不精確地匹配指定的模式) 、填充,以及在格式器中指定可選節。
DateTimeFormatter italianFormatter = new DateTimeFormatterBuilder() .appendText(ChronoField.DAY_OF_MONTH) .appendLiteral(". ") .appendText(ChronoField.MONTH_OF_YEAR) .appendLiteral(" ") .appendText(ChronoField.YEAR) .parseCaseInsensitive() .toFormatter(Locale.ITALIAN);
ZoneId
以前看到的日期和時間的種類都不包含時區信息。時區的處理是新版日期和時間API新增長的重要功能,使用新版日期和時間API時區的處理被極大地簡化了。新的 java.time.ZoneId類是老版 java.util.TimeZone 的替代品。它的設計目標就是要讓你無需爲時區處理的複雜和繁瑣而操心,好比處理日光時(Daylight Saving Time,DST)這種問題。跟其餘日期和時間類同樣, ZoneId 類也是沒法修改的。
ZoneId romeZone = ZoneId.of("Europe/Rome");
地區ID都爲 「{區域}/{城市}」 的格式, 這些地區集合的設定都由英特網編號分配機構 (IANA)的時區數據庫提供。你能夠經過Java 8的新方法 toZoneId 將一個老的時區對象轉換爲 ZoneId :
ZoneId zoneId = TimeZone.getDefault().toZoneId();
一旦獲得一個 ZoneId 對象,你就能夠將它與 LocalDate 、 LocalDateTime 或者是 Instant對象整合起來,構造爲一個 ZonedDateTime 實例,它表明了相對於指定時區的時間點。
LocalDate date = LocalDate.of(2014, Month.MARCH, 18); ZonedDateTime zdt1 = date.atStartOfDay(romeZone); LocalDateTime dateTime = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45); ZonedDateTime zdt2 = dateTime.atZone(romeZone); Instant instant = Instant.now(); ZonedDateTime zdt3 = instant.atZone(romeZone);
ZonedDateTime
將 LocalDateTime 轉換爲 Instant :
LocalDateTime dateTime = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45); Instant instantFromDateTime = dateTime.toInstant(romeZone);
將 Instant 轉換爲 LocalDateTime :
Instant instant = Instant.now(); LocalDateTime timeFromInstant = LocalDateTime.ofInstant(instant, romeZone);
計算時區
ZoneOffset newYorkOffset = ZoneOffset.of("-05:00");
日曆系統
Java 8中另外還提供了4種其餘的日曆系統。這些日曆系統中的每個都有一個對應的日誌類,分別是 ThaiBuddhistDate 、MinguoDate 、 JapaneseDate 以及 HijrahDate 。全部這些類以及 LocalDate 都實現了ChronoLocalDate 接口,可以對公曆的日期進行建模。利用 LocalDate 對象,你能夠建立這些類的實例。
LocalDate date = LocalDate.of(2014, Month.MARCH, 18); JapaneseDate japaneseDate = JapaneseDate.from(date);
等同於
Chronology japaneseChronology = Chronology.ofLocale(Locale.JAPAN); ChronoLocalDate now = japaneseChronology.dateNow();
@Repeatable
若是一個註解在設計之初就是可重複的,你能夠直接使用它。可是,若是你提供的註解是爲用戶提供的,那麼就須要作一些工做,說明該註解能夠重複。
新增方法
類/接口 | 新方法 |
---|---|
Map | getOrDefault , forEach , compute , computeIfAbsent , computeIfPresent , merge ,putIfAbsent , remove(key,value) , replace , replaceAll |
Iterable | forEach , spliterator |
Iterator | forEachRemaining |
Collection | removeIf , stream , parallelStream |
List | replaceAll , sort |
BitSet | stream |
Map
forEach 該方法簽名爲void forEach(BiConsumer<? super K,? super V> action),做用是對Map中的每一個映射執行action指定的操做,其中BiConsumer是一個函數接口,裏面有一個待實現方法void accept(T t, U u)。
java8以前寫法:
HashMap<Integer, String> map = new HashMap<>(); map.put(1, "one"); map.put(2, "two"); map.put(3, "three"); for (Map.Entry<Integer, String> entry : map.entrySet()) { System.out.println(entry.getKey() + "=" + entry.getValue()); }
java8:
HashMap<Integer, String> map = new HashMap<>(); map.put(1, "one"); map.put(2, "two"); map.put(3, "three"); map.forEach((k, v) -> System.out.println(k + "=" + v));
getOrDefault 方法就能夠替換如今檢測 Map 中是否包含給定鍵映射的慣用方法。若是 Map 中不存在這樣的鍵映射,你能夠提供一個默認值,方法會返回該默認值。
java8以前寫法:
Map<String, Integer> carInventory = new HashMap<>(); Integer count = 0; if (map.containsKey("Aston Martin")) { count = map.get("Aston Martin"); }
java8:
Integer count = map.getOrDefault("Aston Martin", 0);
putIfAbsent 方法簽名爲V putIfAbsent(K key, V value),做用是隻有在不存在key值的映射或映射值爲null時,纔將value指定的值放入到Map中,不然不對Map作更改.該方法將條件判斷和賦值合二爲一,使用起來更加方便。
remove(Object key, Object value)方法,只有在當前Map中key正好映射到value時才刪除該映射,不然什麼也不作。
replace 在Java7及之前,要想替換Map中的映射關係可經過put(K key, V value)方法實現,該方法老是會用新值替換原來的值.爲了更精確的控制替換行爲,Java8在Map中加入了兩個replace()方法,分別以下:
replaceAll 該方法簽名爲replaceAll(BiFunction<? super K,? super V,? extends V> function),做用是對Map中的每一個映射執行function指定的操做,並用function的執行結果替換原來的value,其中BiFunction是一個函數接口,裏面有一個待實現方法R apply(T t, U u)。
java8以前寫法:
HashMap<Integer, String> map = new HashMap<>(); map.put(1, "one"); map.put(2, "two"); map.put(3, "three"); for (Map.Entry<Integer, String> entry : map.entrySet()) { entry.setValue(entry.getValue().toUpperCase()); }
java8:
HashMap<Integer, String> map = new HashMap<>(); map.put(1, "one"); map.put(2, "two"); map.put(3, "three"); map.replaceAll((k, v) -> v.toUpperCase());
merge 該方法簽名爲merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction),做用是:
若是Map中key對應的映射不存在或者爲null,則將value(不能是null)關聯到key上;
不然執行remappingFunction,若是執行結果非null則用該結果跟key關聯,不然在Map中刪除key的映射。
Map<String, String> myMap = new HashMap<>(); myMap.put("A", "str01A"); myMap.merge("A", "merge01", String::concat); // str01A merge01
compute 該方法簽名爲compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction) ,若是map裏有這個key,那麼remappingFunction輸入的v就是如今的值,返回的是對應value,若是沒有這個key,那麼輸入的v是null。
map.compute(key, (k, v) -> v == null ? newMsg : v.concat(newMsg));
computeIfAbsent 該方法簽名爲V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction),做用是:只有在當前Map中不存在key值的映射或映射值爲null時,才調用mappingFunction,並在mappingFunction執行結果非null時,將結果跟key關聯。
java8以前寫法:
Map<Integer, Set<String>> map = new HashMap<>(); if (map.containsKey(1)) { map.get(1).add("one"); } else { Set<String> valueSet = new HashSet<String>(); valueSet.add("one"); map.put(1, valueSet); }
java8:
map.computeIfAbsent(1, v -> new HashSet<String>()).add("yi");
computeIfPresent 該方法簽名爲V computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction),做用跟computeIfAbsent()相反,即,只有在當前Map中存在key值的映射且非null時,才調用remappingFunction,若是remappingFunction執行結果爲null,則刪除key的映射,不然使用該結果替換key原來的映射。
Collection
removeIf 該方法簽名爲boolean removeIf(Predicate<? super E> filter),做用是刪除容器中全部知足filter指定條件的元素,其中Predicate是一個函數接口,裏面只有一個待實現方法boolean test(T t),一樣的這個方法的名字根本不重要,由於用的時候不須要書寫這個名字。
java8以前寫法:
// 使用迭代器刪除列表元素 ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too")); Iterator<String> it = list.iterator(); while (it.hasNext()) { if (it.next().length()>3) { // 刪除長度大於3的元素 it.remove(); } }
java8:
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too")); // 刪除長度大於3的元素 list.removeIf(str -> str.length() > 3);
replaceAll 該方法簽名爲void replaceAll(UnaryOperator<E> operator),做用是對每一個元素執行operator指定的操做,並用操做結果來替換原來的元素。其中UnaryOperator是一個函數接口,裏面只有一個待實現函數T apply(T t)。
java8以前寫法:
// 使用下標實現元素替換 ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too")); for (int i=0; i<list.size(); i++) { String str = list.get(i); if (str.length()>3) { list.set(i, str.toUpperCase()); } }
java8:
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too")); list.replaceAll(str -> { if (str.length() > 3) { return str.toUpperCase(); } return str; });
sort 該方法定義在List接口中,方法簽名爲void sort(Comparator<? super E> c),該方法根據c指定的比較規則對容器元素進行排序。Comparator接口咱們並不陌生,其中有一個方法int compare(T o1, T o2)須要實現,顯然該接口是個函數接口。
java8以前寫法:
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too")); Collections.sort(list, new Comparator<String>() { @Override public int compare(String str1, String str2) { return str1.length() - str2.length(); } });
java8:
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too")); list.sort((str1, str2) -> str1.length() - str2.length());
spliterator 方法簽名爲Spliterator<E> spliterator(),該方法返回容器的可拆分迭代器。從名字來看該方法跟iterator()方法有點像,咱們知道Iterator是用來迭代容器的,Spliterator也有相似做用,但兩者有以下不一樣:
stream和parallelStream 分別返回該容器的Stream視圖表示,不一樣之處在於parallelStream()返回並行的Stream。Stream是Java函數式編程的核心類。
原子操做
java.util.concurrent.atomic 包提供了多個對數字類型進行操做的類,好比 AtomicInteger 和 AtomicLong ,它們支持對單一變量的原子操做。這些類在Java 8中新增了更多的方法支持。
Adder 和 Accumulator:
多線程的環境中,若是多個線程須要頻繁地進行更新操做,且不多有讀取的動做(好比,在統計計算的上下文中) ,Java API文檔中推薦你們使用新的類 LongAdder 、 LongAccumulator 、DoubleAdder 以及 DoubleAccumulator ,儘可能避免使用它們對應的原子類型。這些新的類在設計之初就考慮了動態增加的需求,能夠有效地減小線程間的競爭。
LongAddr 和 DoubleAdder 類都支持加法操做,而 LongAccumulator 和 DoubleAccumulator 可使用給定的方法整合多個值。
LongAdder adder = new LongAdder(); adder.add(10); long sum = adder.sum();
等同於
LongAccumulator acc = new LongAccumulator(Long::sum, 0); acc.accumulate(10); long result = acc.get();
ConcurrentHashMap
ConcurrentHashMap 類的引入極大地提高了 HashMap 現代化的程度,新引入的ConcurrentHashMap 對併發的支持很是友好。 ConcurrentHashMap 容許併發地進行新增和更新操做,由於它僅對內部數據結構的某些部分上鎖。所以,和另外一種選擇,即同步式的 Hashtable 比較起來,它具備更高的讀寫性能。
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(); Optional<Integer> maxValue = Optional.of(map.reduceValues(1, Integer::max));
注意,對 int 、 long 和 double ,它們的 reduce 操做各有不一樣(好比 reduceValuesToInt 、reduceKeysToLong 等) 。
Arrays
使用 parallelSort
parallelSort 方法會以併發的方式對指定的數組進行排序,你可使用天然順序,也能夠爲數組對象定義特別的 Comparator 。
使用 setAll 和 parallelSetAll
setAll 和 parallelSetAll 方法能夠以順序的方式也能夠用併發的方式,使用提供的函數計算每個元素的值,對指定數組中的全部元素進行設置。該函數接受元素的索引,返回該索引元素對應的值。因爲 parallelSetAll 須要併發執行,因此提供的函數必須沒有任何反作用。
int[] evenNumbers = new int[10]; Arrays.setAll(evenNumbers, i -> i * 2); // 0, 2, 4, 6...
使用 parallelPrefix
parallelPrefix 方法以併發的方式, 用用戶提供的二進制操做符對給定數組中的每一個元素進行累積計算。
int[] ones = new int[10];
Arrays.fill(ones, 1);
Arrays.parallelPrefix(ones, (a, b) -> a + b);
Number
Number 類中新增的方法以下。
Math
若是 Math 中的方法在操做中出現溢出, Math 類提供了新的方法能夠拋出算術異常。支持這一異常的方法包括使用 int 和 long 參數的 addExact 、 subtractExact 、 multipleExact 、incrementExact 、 decrementExact 和 negateExact 。此外, Math 類還新增了一個靜態方法toIntExact , 能夠將 long 值轉換爲 int 值。 其餘的新增內容包括靜態方法 floorMod 、 floorDiv和 nextDown 。
Files
Files 類最引人注目的改變是,你如今能夠用文件直接產生流。經過 Files.lines 方法你能夠以延遲方式讀取文件的內容,並將其做爲一個流。此外,還有一些很是有用的靜態方法能夠返回流。
Reflection
Relection 接口的另外一個變化是新增了能夠查詢方法參數信息的API,好比,你如今可使用新增的 java.lang.reflect.Parameter 類查詢方法參數的名稱和修飾符,這個類被新的java.lang.reflect.Executable 類所引用, 而 java.lang.reflect.Executable 通用函數和構造函數共享的父類。
String
String 類也新增了一個靜態方法,名叫 join 。你大概已經猜出它的功能了,它能夠用一個分隔符將多個字符串鏈接起來。
String authors = String.join(", ", "Raoul", "Mario", "Alan");
泛型
Java類型要麼是引用類型(好比 Byte 、 Integer 、 Object 、 List ) ,要麼是原始類型(好比 int 、 double 、 byte 、 char ) 。可是泛型(好比 Consumer<T> 中的 T )只能綁定到引用類型。這是由泛型內部的實現方式形成的。所以,在Java裏有一個將原始類型轉換爲對應的引用類型的機制。這個機制叫做裝箱(boxing)。相反的操做,也就是將引用類型轉換爲對應accept 方法的實現Lambda是 Function接口的 apply 方法的實現的原始類型,叫做拆箱(unboxing) 。Java還有一個自動裝箱機制來幫助程序員執行這一任務:裝箱和拆箱操做是自動完成的。
工具類庫
Guava、Apache和lambdaj
廣義歸約( Collectors.reducing)
它須要三個參數。
第一個參數是歸約操做的起始值,也是流中沒有元素時的返回值,因此很顯然對於數值和而言 0 是一個合適的值。
第二個參數就是轉換成一個表示其所含熱量的 int 。
第三個參數是一個 BinaryOperator ,將兩個項目累積成一個同類型的值。
求和:
int totalCalories = menu.stream().collect(Collectors.reducing(0, Dish::getCalories, (i, j) -> i + j));
找出集合中最大值:
Optional <Dish> mostCalorieDish = menu.stream().collect(reducing((d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2));
Collectors 類的靜態工廠方法
工廠方法 | 返回類型 | 描 述 | 使用示例 |
---|---|---|---|
toList | List<T> | 把流中全部項目收集到一個 List | List<Dish> dishes = menuStream.collect(Collectors.toList()); |
toSet | Set<T> | 把流中全部項目收集到一個 Set ,刪除重複項 | Set<Dish> dishes = menuStream.collect(Collectors.toSet()); |
toMap | Map<T, K> | 把流中全部項目收集到一個 Map ,刪除重複項,默認狀況下,出現重複數據會報錯 | Map<Long, Dish> dishesMap = menuStream.collect(Collectors.toMap(Dish::getCalories));若有重複數據,能夠設置使用哪個數據 Map<Long, Dish> dishesMap = menuStream.collect(Collectors.toMap(Dish::getCalories, d -> d, (d1, d2) -> d1, LinkedHashMap::new)); |
toCollection | Collection<T> | 把流中全部項目收集到給定的供應源建立的集合 | Collection<Dish> dishes = menuStream.collect(Collectors.toCollection(), ArrayList::new); |
counting | Long | 計算流中元素的個數 | long howManyDishes = menuStream.collect(Collectors.counting()); |
summingInt | Integer | 對流中項目的一個整數屬性求和 | int totalCalories = menuStream.collect(Collectors.summingInt(Dish::getCalories)); |
averagingInt | Integer | 計算流中項目 Integer 屬性的平均值 | int avgCalories = menuStream.collect(Collectors.averagingInt(Dish::getCalories)); |
summarizingInt | IntSummaryStatistics | 收集關於流中項目 Integer 屬性的統計值,例如最大、最小、總和與平均值 | IntSummaryStatistics menuStatistics = menuStream.collect(Collectors.summarizingInt(Dish::getCalories)); |
joining | String | 鏈接對流中每一個項目調用 toString 方法所生成的字符串 | String shortMenu = menuStream.map(Dish::getName).collect(Collectors.joining(", ")); |
maxBy | Optional<T> | 一個包裹了流中按照給定比較器選出的最大元素的 Optional ,或若是流爲空則爲 Optional.empty() | Optional<Dish> fattest = menuStream.collect(Collectors.maxBy(Comparator.comparingInt(Dish::getCalories))); |
minBy | Optional<T> | 一個包裹了流中按照給定比較器選出的最小元素的 Optional ,或若是流爲空則爲 Optional.empty() | Optional<Dish> lightest = menuStream.collect(Collectors.minBy(Comparator.comparingInt(Dish::getCalories))); |
reducing | 歸約操做產生的類型 | 從一個做爲累加器的初始值開始,利用 BinaryOperator 與流中的元素逐個結合,從而將流歸約爲單個值 | int totalCalories = menuStream.collect(Collectors.reducing(0, Dish::getCalories, Integer::sum)); |
collectingAndThen | 轉換函數返回的類型 | 包裹另外一個收集器,對其結果應用轉換函數 | int howManyDishes = menuStream.collect(Collectors.collectingAndThen(toList(), List::size)); |
groupingBy | Map<K, List<T>> | 根據項目的一個屬性的值對流中的項目分組組,並將屬性值分組結果 Map 的鍵 | Map<Dish.Type,List<Dish>> dishesByType = menuStream.collect(Collectors.groupingBy(Dish::getType)); |
partitioningBy | Map<Boolean,List<T>> | 根據對流中每一個項目應用謂詞的結果來對項目進行分區 | Map<Boolean,List<Dish>> vegetarianDishes = menuStream.collect(Collectors.partitioningBy(Dish::isVegetarian)); |
Optional介紹
Optional<T> 類( java.util.Optional )是一個容器類,表明一個值存在或不存在。
線程個數計算方式
若是線程池中線程的數量過多,最終它們會競爭稀缺的處理器和內存資源,浪費大量的時間在上下文切換上。反之,若是線程的數目過少,正如你的應用所面臨的狀況,處理器的一些核可能就沒法充分利用。Brian Goetz建議,線程池大小與處理器的利用率之比可使用下面的公式進行估算:
N threads = N CPU * U CPU * (1 + W/C)
其中:
並行——使用parallelStream仍是 CompletableFutures ?
目前爲止, 你已經知道對集合進行並行計算有兩種方式: 要麼將其轉化爲parallelStream, 利用map這樣的操做開展工做,要麼枚舉出集合中的每個元素,建立新的線程,在 Completable-Future 內對其進行操做。後者提供了更多的靈活性,你能夠調整線程池的大小,而這能幫助你確保總體的計算不會由於線程都在等待I/O而發生阻塞。
咱們對使用這些API的建議以下。
若是你進行的是計算密集型的操做,而且沒有I/O,那麼推薦使用 Stream 接口,由於實現簡單,同時效率也多是最高的(若是全部的線程都是計算密集型的,那就沒有必要建立比處理器核數更多的線程) 。
反之,若是你並行的工做單元還涉及等待I/O的操做(包括網絡鏈接等待) ,那麼使用CompletableFuture 靈活性更好,你能夠像前文討論的那樣,依據等待/計算,或者W/C的比率設定須要使用的線程數。這種狀況不使用並行流的另外一個緣由是,處理流的流水線中若是發生I/O等待, 流的延遲特性會讓咱們很難判斷到底何時觸發了等待。
配置並行流使用的線程池
並行流內部使用了默認的 ForkJoinPool,它默認的線程數量就是你的處理器數量 , 這個值是由Runtime.getRuntime().available-Processors() 獲得的。
可是能夠通 過系統屬性java.util.concurrent.ForkJoinPool.common.parallelism 來改變線程池大小,以下所示:
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","12");
這是一個全局設置,所以它將影響代碼中全部的並行流。反過來講,目前還沒法專爲某個
並行流指定這個值。通常而言,讓 ForkJoinPool 的大小等於處理器數量是個不錯的默認值,
除非你有很好的理由,不然咱們強烈建議你不要修改它。
測量流性能
咱們聲稱並行求和方法應該比順序和迭代方法性能好。然而在軟件工程上,靠猜絕對不是什麼好辦法!特別是在優化性能時,你應該始終遵循三個黃金規則:測量,測量,再測量。
可分解性總結了一些流數據源適不適於並行:
源 | 可分解性 |
---|---|
ArrayList | 極佳 |
LinkedList | 差 |
IntStream.range | 極佳 |
Stream.iterate | 差 |
HashSet | 好 |
TreeSet | 好 |