為什么要用Java8
- elasticsearch: Elasticsearch requires Java 8 or later. Use the official Oracle distribution or an open-source distribution such as OpenJDK.
- dubbo: Requires JDK1.8+, if you use lower version, see 1.6+, use 2.5.5
-
spring: JDK 8+ for Spring Framework 5.x
......
很多主流框架已經(jīng)使用java8進行升級開發(fā),java8是趨勢,是時候一起擁抱java8了.
java8語言新特性
- 函數(shù)式編程
- Lambda表達式
- 接口的默認方法和靜態(tài)方法
- stream API
- 新的類庫:Optional,Streams,Date/Time API (JSR 310)...
- JVM新特性
1.Lambda表達式
Lambda允許把函數(shù)作為一個方法的參數(shù)(函數(shù)作為參數(shù)傳遞進方法中),或者把代碼看成數(shù)據(jù)。
1.1 lambda的基本語法是:
(parameters) -> expression
或者
(parameters) -> { statements; }
eg:java8中有效的lambda表達式:
- 一個String類型的參數(shù)并返回一個int。Lambda中沒有return語句,因為已經(jīng)隱含了return。
(String s) -> s.length()
- 兩個int類型的參數(shù)而沒有返回值。
(int x, int y) -> {
System.out.println("Result:");
System.out.println(x+y);
}
- 沒有參數(shù),返回一個int
() -> 1
1.2 函數(shù)式接口
- 函數(shù)式接口(函數(shù)式接口就是只定義一個抽象方法的接口)
- 函數(shù)描述符(函數(shù)式接口的抽象方法的簽名 就是 lambda表達式的簽名)
java8中接口的變化:
Java 8用默認方法與靜態(tài)方法這兩個新概念來擴展接口的聲明。
此功能是為了向后兼容性增加,使舊接口可用于利用JAVA8,lambda表達式的能力
默認方法與抽象方法不同之處在于抽象方法必須要求實現(xiàn),但是默認方法則沒有這個要求。
在實際使用過程中,函數(shù)式接口是容易出錯的:如有某個人在接口定義中增加了另一個方法,這時,這個接口就不再是函數(shù)式的了,并且編譯過程也會失敗。為了克服函數(shù)式接口的這種脆弱性并且能夠明確聲明接口作為函數(shù)式接口的意圖,Java 8增加了一種特殊的注解@FunctionalInterface(Java 8中所有類庫的已有接口都添加了@FunctionalInterface注解)。
目前已存在的函數(shù)式接口有:Comparable、Runnable和Callable等。
java8在java.util.function中有引入了很多個新的函數(shù)式接口:
- Predicate<T> 輸入?yún)?shù)為類型T, 輸出為類型boolean, 記作 T -> boolean
- Consumer<T> 輸入?yún)?shù)為類型T, 輸出為void, 記作 T -> void
- Function<T,R> 輸入?yún)?shù)為類型T, 輸出為類型R, 記作 T -> R
- Supplier<T> 沒有輸入?yún)?shù), 輸出為類型T, 記作 void -> T
...
1.3方法引用
方法引用讓你可以重復使用現(xiàn)有的方法定義,并像Lambda一樣傳遞它們。
當你需要使用方法引用時,目標引用放在分隔符::前,方法的名稱放在后面。
三種方法引用:
- 指向靜態(tài)方法的方法引用。
lambda表達式: (args) -> ClassName.staticMethod(args)
方法引用: ClassName::staticMethod
比如
s -> Integer.valueOf(s) 等價于 Integer::valueOf
- 指向任意類型實例方法的方法引用。
lambda表達式: (arg0, rest) -> arg0.instanceMethod(rest)
方法引用: ClassName::instanceMethod
(上面args0是ClassName類型的)
比如
(str, integer) -> str.substring(integer) 等價于 String::substring
- 指向現(xiàn)有對象的實例方法的方法引用。
lambda表達式: (args) -> expr.instanceMethod(args)
方法引用: expr::instanceMethod
比如
(Apple arg) -> appleExpr.compareAppleWight(arg) 等價于 appleExpr::compareAppleWight
- 構造函數(shù)的方法引用。
對于一個現(xiàn)有構造函數(shù),你可以利用它的名稱和關鍵字new來創(chuàng)建它的一個引用:
ClassName::new。它的功能與指向靜態(tài)方法的引用類似。
比如創(chuàng)建一個Apple對象。
空參構造器Apple():
Supplier<Apple> c1 = Apple::new;
Apple a1 = c1.get();
等價于
Supplier<Apple> c1 = () -> new Apple();
Apple a1 = c1.get();
兩個參數(shù)的構造函數(shù)Apple(String color, Integer weight):
BiFunction<Integer, String, Apple> biFunction = Apple::new;
Apple apple2 = biFunction.apply(155, "green");
等價于
BiFunction<Integer, String, Apple> biFunction = (weight, color) -> new Apple(weight, color);
Apple apple2 = biFunction.apply(155, "green");
1.4 復合lambda表達式
- 比較器(Comparator<T>)復合(reversed(),thenComparing())
- 謂詞(Predicate<T>)復合(negate(),and(),or())
- 函數(shù)(Function<T, V>)復合(compose(),andThen(),identity())
2.Stream API
Java 8 中的 Stream 是對集合(Collection)對象功能的增強,它專注于對集合對象進行各種非常便利、高效的聚合操作(aggregate operation),或者大批量數(shù)據(jù)操作 (bulk data operation)。
Stream API(java.util.stream.*)可以讓你的代碼具備:
- 聲明性:更簡潔,更易讀
- 可復合:更靈活
- 可并行:性能更好
2.1 什么是流
流不是一種數(shù)據(jù)結構,而是處理集合元素的相關計算,更像一個高級的 Iterator。單向,不可往復,數(shù)據(jù)只能遍歷一次。
如何使用流?
- 一個數(shù)據(jù)源(如集合)來執(zhí)行一個查詢;
- 一個中間操作鏈,形成一條流的流水線;
- 一個終端操作,執(zhí)行流水線,并能生成結果。
2.2 使用流
- 篩選、切片和匹配
- 查找、匹配和歸約
- 使用數(shù)值范圍等數(shù)值流
- 從多個源創(chuàng)建流
- 無限流
2.2.1 篩選,切片
- filter (篩選過濾)
- distinct (去重)
- limit (截取)
- skip (跳過)
2.2.2 映射
- map (映射:接受一個函數(shù)作為參數(shù)。這個函數(shù)會被應用到每個元素上,并將其映射成一個新的元素)
- flatMap (flatmap方法讓你把一個流中的每個值都換成另一個流,然后把所有的流連接起來成為一個流。)
2.2.3 查找和匹配
- anyMatch (檢查謂詞是否至少匹配一個元素)
- allMatch (檢查謂詞是否匹配所有元素)
- noneMatch (檢查是否沒有任何元素與給定的謂詞匹配)
- findAny (返回當前流中的任意元素Optional<T>。它可以與其他流操作結合使用。)
- findFirst (返回流中第一個元素Optional<T>)
2.2.4 reduce
這個方法的主要作用是把 Stream 元素組合起來。它提供一個起始值(種子),然后依照運算規(guī)則(BinaryOperator),和前面 Stream 的第一個、第二個、第 n 個元素組合。從這個意義上說,字符串拼接、數(shù)值的 sum、min、max、average 都是特殊的 reduce。
- Optional<T> reduce(BinaryOperator<T> accumulator);
- T reduce(T identity, BinaryOperator<T> accumulator);
- U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator< U > combiner);
2.2.5 數(shù)值流
- IntStream
- DoubleStream
- LongStream
2.2.6 創(chuàng)建流
- 由值創(chuàng)建流(Stream.of)
- 由數(shù)組創(chuàng)建流(Arrays.stream(array))
- 由文件生成流(java.nio.file.Files)
- 由函數(shù)生成流:創(chuàng)建無限流(Stream.iterate和Stream.generate)
2.3 收集器
- 用Collectors類創(chuàng)建和使用收集器
- 將數(shù)據(jù)流歸約為一個值
- 匯總:歸約的特殊情況
- 數(shù)據(jù)分組和分區(qū)
- 自定義收集器
2.3.1 歸約和匯總
Stream.reduce 與 Stream.collect的區(qū)別:
Stream.reduce,常用的方法有average, sum, min, max, and count,返回單個的結果值,并且reduce操作每處理一個元素總是創(chuàng)建一個新值。
Stream.collect修改現(xiàn)存的值,而不是每處理一個元素,創(chuàng)建一個新值。
2.3.2 數(shù)據(jù)分組
- 一級分組
Map<Dish.Type, List<Dish>> groupByType = menuList.stream().collect(groupingBy(Dish::getType));
- 多級分組
Map<Dish.Type, Map<CaloricLevel, List<Dish>>> groupByTypeAndCalories = menuList.stream().collect(
groupingBy(Dish::getType,
groupingBy(dish -> {
if (dish.getCalories() <= 400) return CaloricLevel.DIET;
else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
else return CaloricLevel.FAT;
}))
);
- 按子組收集數(shù)據(jù)
Map<Dish.Type, Long> groupByTypeToCount = menuList.stream().collect(groupingBy(Dish::getType, counting()));
- 分區(qū)(分區(qū)是一種特殊的分組,結果map至少包含兩個不同的分組——一個true,一個false。)
Map<Boolean, List<Dish>> partitionByVegeterian =
menuList.stream().collect(partitioningBy(Dish::isVegetarian));
2.3.3 Collector接口
public interface Collector<T, A, R> {
Supplier<A> supplier()
BiConsumer<A, T> accumulator()
Function<A, R> finisher()
BinaryOperator<A> combiner()
Set<Characteristics> characteristics()
}
Collector接口的三個泛型:
- T:stream在調用collect方法收集前的數(shù)據(jù)類型
- A:A是T的累加器,遍歷T的時候,會把T按照一定的方式添加到A中,換句話說就是把一些T通過一種方式變成A
- R:R可以看成是A的累加器,是最終的結果,是把A匯聚之后的數(shù)據(jù)類型,換句話說就是把一些A通過一種方式變成R
通過自定義ToList收集器理解接口方法:
Supplier<A> supplier()
怎么創(chuàng)建一個累加器(這里對應的是如何創(chuàng)建一個List)BiConsumer<A, T> accumulator()
怎么把一個對象添加到累加器中(這里對應的是如何在List里添加一個對象,當然是調用add方法)Function<A, R> finisher()
其實就是怎么把A轉化為R,由于是toList,所以A和R是一樣的類型,這里其實用就是Function.identityBinaryOperator<A> combiner()
它定義了對流的各個子部分進行并行處理時,各個子部分歸約所得的累加器要如何合并(這里對應的是如何把List和List合并起來,當然是調用addAll,這里由于最終要返回List,所以A和R是一個類型,都是List所以才調用addAll)Set<Characteristics> characteristics()
會返回一個不可變的Characteristics集合,它定義
了收集器的行為——尤其是關于流是否可以并行歸約,以及可以使用哪些優(yōu)化的提示,toList這里只用了Characteristics.IDENTITY_FINISH
2.4 并行數(shù)據(jù)處理
并行流:
可以通過對收集源調用parallelStream方法來把集合轉換為并行流。并行流就是一個把內容分成多個數(shù)據(jù)塊,并用不同的線程分別處理每個數(shù)據(jù)塊的流。
問題:并行流用的線程是從哪兒來的?有多少個?怎么自定義這個過程呢?
并行流內部使用了默認的ForkJoinPool,它默認的線程數(shù)量就是你的處理器數(shù)量,這個值是由Runtime.getRuntime().available-Processors()得到的。
但是你可以通過系統(tǒng)屬性java.util.concurrent.ForkJoinPool.common.parallelism 來改變線程池大小,如下所示:
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","12");
這是一個全局設置,因此它將影響代碼中所有的并行流。反過來說,目前還無法專為某個并行流指定這個值。一般而言,讓ForkJoinPool的大小等于處理器數(shù)量是個不錯的默認值,除非你有很好的理由,否則我們強烈建議你不要修改它。
使用并行流:
本地測試的過程中,并行流比順序流效果差。原因可能與機器,處理的數(shù)據(jù)量,使用并行流的方式等有關系。
是否使用并行流需考慮如下幾種情況:
- 留意裝箱。(使用(IntStream、LongStream、DoubleStream來避免裝箱拆箱)
- 有些操作本身在并行流上的性能就比順序流差。(limit,findFirst等依賴于元素順序的操作)
- 考慮流的操作流水線的總計算成本。設N是要處理的元素的總數(shù),Q是一個元素通過流水線的大致處理成本,則N*Q就是這個對成本的一個粗略的定性估計。Q值較高就意味著使用并行流時性能好的可能性比較大。
- 數(shù)據(jù)量較小的情況不適合并行流。
- 考慮流背后的數(shù)據(jù)結構是否易于分解。
- 流自身的特點,以及流水線中的中間操作修改流的方式,都可能會改變分解過程的性能。
- 考慮終端操作中合并步驟的代價是大是?。ɡ鏑ollector中的combiner方法)。
| 源 | 可分解性 |
|---|---|
| ArrayList | 極佳 |
| LinkedList | 差 |
| IntStream.range | 極佳 |
| Stream.iterate | 差 |
| HashSet | 好 |
| TreeSet | 好 |
流的數(shù)據(jù)源
| 源 | 可分解性 |
|---|---|
| ArrayList | 極佳 |
| LinkedList | 差 |
| IntStream.range | 極佳 |
| Stream.iterate | 差 |
| HashSet | 好 |
| TreeSet | 好 |
3.Optional使用
- 使用Optional避免null引用
- 整潔代碼中對null的檢查
- Optional的使用
java中的null帶來了種種問題:典型常見,使代碼膨脹,自身無意義等等等。
| 方法 | 描述 |
|---|---|
| empty | 返回一個空的Optional 實例 |
| filter | 如果值存在并且滿足提供的謂詞,就返回包含該值的Optional 對象;否則返回一個空的Optional 對象 |
| flatMap | 如果值存在,就對該值執(zhí)行提供的mapping函數(shù)調用,返回一個Optional 類型的值,否則就返回一個空的Optional 對象 |
| get | 如果該值存在,將該值用Optional封裝返回,否則拋出一個NoSuchElementException 異常 |
| ifPresent | 如果值存在,就執(zhí)行使用該值的方法調用,否則什么也不做 |
| isPresent | 如果值存在就返回true,否則返回false |
| map | 如果值存在,就對該值執(zhí)行提供的mapping 函數(shù)調用 |
| of | 將指定值用Optional封裝之后返回,如果該值為null,則拋出一個NullPointerException異常 |
| ofNullable | 將指定值用Optional封裝之后返回,如果該值為null,則返回一個空的Optional 對象 |
| orElse | 如果有值則將其返回,否則返回一個默認值 |
| orElseGet | 如果有值則將其返回,否則返回一個由指定的Supplier接口生成的值 |
| orElseThrow | 如果有值則將其返回,否則拋出一個由指定的Supplier接口生成的異常 |
注意:Optional 無法序列化
4.新的日期和時間API
新的 java.time 中包含了所有關于:
時鐘(Clock)、本地日期(LocalDate)、本地時間(LocalTime)、本地日期時間(LocalDateTime)、時區(qū)(ZonedDateTime)和持續(xù)時間(Duration)的類。
歷史悠久的 Date 類新增了 toInstant() 方法,用于把 Date 轉換成新的表示形式。這些新增的本地化時間日期 API 大大簡化了了日期時間和本地化的管理。
目前Java8新增了java.time包定義的類表示日期-時間概念的規(guī)則,很方便使用;最重要的一點是值不可變,且線程安全。
本地日期時間API:
- LocalDate(年月日)
- LocalTime(時分秒)
- localDateTime(年月日時分秒)
時區(qū)API:
- ZonedDateTime
時鐘API:
- Clock
計算日期時間差API:
- Period(處理有關基于時間的日期數(shù)量。)
- Duration(處理有關基于時間的時間量。)
時間格式化API
- DateTimeFormatter(DateTimeFormatter實例都是線程安全
的)
5. 其他
- java類庫標準base64編碼使用方式:
final String text = "測試Abc123!!¥¥";
final String encoded = Base64.getEncoder().encodeToString(text.getBytes(StandardCharsets.UTF_8));
System.out.println(encoded); // 5rWL6K+VQWJjMTIzISHvv6Xvv6U=
final String decoded = new String(Base64.getDecoder().decode(encoded), StandardCharsets.UTF_8);
System.out.println(decoded); // 測試Abc123!!¥¥
// url encode
final String url = "http://www.jinhui365.com/abc?foo=中文&¥%&bar=hello123&baz=http://abc/def123";
final String encoded2 = Base64.getUrlEncoder().encodeToString(url.getBytes(StandardCharsets.UTF_8));
System.out.println(encoded2); // aHR0cDovL3d3dy5qaW5odWkzNjUuY29tL2FiYz9mb2895Lit5paHJu-_pSUmYmFyPWhlbGxvMTIzJmJhej1odHRwOi8vYWJjL2RlZjEyMw==
final String decoded2 = new String(Base64.getUrlDecoder().decode(encoded2), StandardCharsets.UTF_8);
System.out.println(decoded2); // http://www.jinhui365.com/abc?foo=中文&¥%&bar=hello123&baz=http://abc/def123
- jvm的變化:
PermGen空間被移除了,取而代之的是Metaspace(JEP 122)。 JVM選項 -XX:PermSize與-XX:MaxPermSize分別被
-XX:MetaSpaceSize與-XX:MaxMetaspaceSize所代替。
最后
Java8 作為 Java 語言的一次重大發(fā)布,包含語法上的更改、新的方法與數(shù)據(jù)類型,以及一些能默默提升應用性能的隱性改善。而且java8有利于提高開發(fā)生產力,對于開發(fā)者來說是好事,也是趨勢。但是生產中使用java8可能存在風險,在正式使用Java8之前,不妨先體驗一下java8的神奇。
附《java8 in action》源碼:https://github.com/java8/Java8InAction.git