java8實戰(zhàn)學習心得

為什么要用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表達式:

  1. 一個String類型的參數(shù)并返回一個int。Lambda中沒有return語句,因為已經(jīng)隱含了return。
(String s) -> s.length()
  1. 兩個int類型的參數(shù)而沒有返回值。
(int x, int y) -> {
    System.out.println("Result:");
    System.out.println(x+y);
}
  1. 沒有參數(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一樣傳遞它們。
當你需要使用方法引用時,目標引用放在分隔符::前,方法的名稱放在后面。

三種方法引用:

  1. 指向靜態(tài)方法的方法引用。
    lambda表達式: (args) -> ClassName.staticMethod(args)
    方法引用: ClassName::staticMethod

比如

s -> Integer.valueOf(s) 等價于 Integer::valueOf
  1. 指向任意類型實例方法的方法引用。
    lambda表達式: (arg0, rest) -> arg0.instanceMethod(rest)
    方法引用: ClassName::instanceMethod
    (上面args0是ClassName類型的)

比如

(str, integer) -> str.substring(integer) 等價于 String::substring
  1. 指向現(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表達式
  1. 比較器(Comparator<T>)復合(reversed(),thenComparing())
  2. 謂詞(Predicate<T>)復合(negate(),and(),or())
  3. 函數(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ù)只能遍歷一次。

如何使用流?

  1. 一個數(shù)據(jù)源(如集合)來執(zhí)行一個查詢;
  2. 一個中間操作鏈,形成一條流的流水線;
  3. 一個終端操作,執(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.identity

  • BinaryOperator<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ù)量,使用并行流的方式等有關系。

是否使用并行流需考慮如下幾種情況:

  1. 留意裝箱。(使用(IntStream、LongStream、DoubleStream來避免裝箱拆箱)
  2. 有些操作本身在并行流上的性能就比順序流差。(limit,findFirst等依賴于元素順序的操作)
  3. 考慮流的操作流水線的總計算成本。設N是要處理的元素的總數(shù),Q是一個元素通過流水線的大致處理成本,則N*Q就是這個對成本的一個粗略的定性估計。Q值較高就意味著使用并行流時性能好的可能性比較大。
  4. 數(shù)據(jù)量較小的情況不適合并行流。
  5. 考慮流背后的數(shù)據(jù)結構是否易于分解。
  6. 流自身的特點,以及流水線中的中間操作修改流的方式,都可能會改變分解過程的性能。
  7. 考慮終端操作中合并步驟的代價是大是?。ɡ鏑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. 其他

  1. 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

  1. 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

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • Java8 in action 沒有共享的可變數(shù)據(jù),將方法和函數(shù)即代碼傳遞給其他方法的能力就是我們平常所說的函數(shù)式...
    鐵牛很鐵閱讀 1,359評論 1 2
  • 第一章 為什么要關心Java 8 使用Stream庫來選擇最佳低級執(zhí)行機制可以避免使用Synchronized(同...
    謝隨安閱讀 1,562評論 0 4
  • 前幾天因為參加一個演出統(tǒng)一服裝,網(wǎng)購的均碼衣服,一個星期后演出。于是向隊伍里若干漂亮愛打扮的妞中請教了一位大三的有...
    記得讀書閱讀 212評論 0 3

友情鏈接更多精彩內容