Java8實戰 自學筆記

由於新項目需要用到Java8,在此通過閱讀Java8實戰系統的學習java8的新特性,在此將學習資料整理便於以後複習。
聲明:由於按照書中的章節來把每個章節的重點整理到筆記所以不像總結那樣一目瞭然。
建議想系統的學習Java8又沒有足夠的時間看整本書的讀者閱讀。

通過Streams庫避免synchronized編寫代碼,可以理解爲特別的迭代器。
stream api 通過內部迭代避免不必要的循環,並且不受單核限制。
collection和stream的區別:collection主要是爲了存儲和訪問數據,stream則主要用於描述對數據的計算。
stream提倡並行處理一個stream中元素。(順序處理→Collection.stream()、多核並行處理→→Collection.parallelStream())
java8優化並行難題有2點:
1.將大的流分成幾個小的流,以便並行處理。
2.在沒有可變共享狀態時,函數或方法可以有效、安全地並行執行。

java8可以包含實現類沒有提供實現的方法,接口有了默認實現。(接口聲明聲明中用心的default關鍵字來表示)
例如java8中可以直接調用List的sort方法,默認調用List接口中的default sort方法。

default void sort(Comparator<? super E> c) {
	Collections.sort(this, c);
}

* 一個類可以實現多個接口,當好幾個接口中有多個默認實現,就意味着java中有了某種形式的多重基層。

行爲參數化:就是一個方法接受多個不同的行爲作爲參數,並在內部使用它們,完成不同行爲的能力。
可以把一個行爲(一段代碼,結合匿名類)通過接口封裝起來後在調用時候通過Lambda方式實現,能夠輕鬆的適應不斷變化的需求。這種做法類似於策略設計模式。

Lambda表達式可以理解爲匿名函數,它沒有名稱,但有參數列表、函數主體、返回類型,可能還有一個可以拋出的異常的列表。
函數式接口就是僅僅聲明瞭一個抽象方法的接口。
只有在接受函數式接口的地方纔可以使用Lambda表達式。
Lambda表達式可以作爲參數傳遞給方法或存儲在變量中。
Lambda表達式允許你直接內聯,爲函數式接口的抽象方法提供實現,並且將整個表達式作爲函數式接口的一個實例。
Lambda表達式所需要代表的類型稱爲目標類型
方法引用讓你重複使用現有的方法實現並直接傳遞它們。

Lambda的基本語法:

  1. (parameters) -> expression 例: () -> "Iron Man"
  2. (parameters) -> { statements; } 例:() -> {return "Mario";}
  • 參數列表——這裏它採用了Comparator中compare方法的參數,兩個Apple。
  • 箭頭——箭頭->把參數列表與Lambda主體分隔開。
  • Lambda主體——比較兩個Apple的重量。表達式就是Lambda的返回值了。

函數式接口就是隻定義一個抽象方法的接口。

public interface Comparator<T> {
int compare(T o1, T o2);                   ← java.util.Comparator
}
public interface Runnable{
void run();                                ← java.lang.Runnable
}

函數式接口的抽象方法的簽名基本上就是Lambda表達式的簽名。函數式接口的抽象方法的簽名稱爲函數描述符。(簽名理解爲返回類型)
Lambda表達式允許你直接以內聯的形式爲函數式接口的抽象方法提供實現,並把整個表達式作爲函數式接口的實例(具體說來,是函數式接口一個具體實現的實例)

新的Java API @FunctionalInterface 這個標註用於表示該接口會設計成一個函數式接口。

Lambda環繞執行模式

  1. 記得行爲參數化。(將不同的行爲參數化)
  2. 使用函數式接口來傳遞行爲。(定義函數式接口@FunctionalInterface)
  3. 執行一個行爲。(聲明覆用方法參數接收函數式接口,實現中調用函數式接口的抽象方法)
  4. 傳遞Lambda。(調用複用方法參數傳遞函數式接口關聯的Lambda)

Java 8的庫設計師幫你在java.util.function包中引入了幾個新的函數式接口。

  • Predicate<T>接口定義了一個名叫test的抽象方法,它接受泛型T對象,並返回一個boolean。
    需要表示一個涉及類型T的布爾表達式時,就可以使用這個接口。
  • Consumer<T>定義了一個名叫accept的抽象方法,它接受泛型T的對象,沒有返回(void)。
    需要訪問類型T的對象,並對其執行某些操作,就可以使用這個接口。
  • Function<T, R>接口定義了一個叫作apply的方法,它接受一個泛型T的對象,並返回一個泛型R的對象。
    需要定義一個Lambda,將輸入對象的信息映射到輸出,就可以使用這個接口。

針對專門的輸入參數類型的函數式接口的名稱都要加上對應的原始類型前綴,比如DoublePredicate、IntConsumer、LongBinaryOperator、IntFunction等。Function接口還有針對輸出參數類型的變種:ToIntFunction<T>、IntToDoubleFunction等。
注意,如果Lambda表達式拋出一個異常,那麼抽象方法所聲明的throws語句也必須與之匹配。

函數式接口 函數描述符(即Lambda表達式的簽名) 原始類型特化
Predicate<T> T->boolean

IntPredicate,

LongPredicate,

DoublePredicate

Consumer<T> T->void

IntConsumer,

LongConsumer,

DoubleConsumer

Function<T,R> T->R IntFunction<R>,
IntToDoubleFunction,
IntToLongFunction,
LongFunction<R>,
LongToDoubleFunction,
LongToIntFunction,
DoubleFunction<R>,
ToIntFunction<T>,
ToDoubleFunction<T>,
ToLongFunction<T>
Supplier<T> ()->T

BooleanSupplier,

IntSupplier,

LongSupplier,

DoubleSupplier

UnaryOperator<T> T->T

IntUnaryOperator,

LongUnaryOperator,

DoubleUnaryOperator

BinaryOperator<T> (T,T)->T

IntBinaryOperator,

LongBinaryOperator,

DoubleBinaryOperator

BiPredicate<L,R> (L,R)->boolean  
BiConsumer<T,U> (T,U)->void ObjIntConsumer<T>,
ObjLongConsumer<T>,
ObjDoubleConsumer<T>
BiFunction<T,U,R> (T,U)->R ToIntBiFunction<T,U>,
ToLongBiFunction<T,U>,
ToDoubleBiFunction<T,U>

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

同樣的Lambda,不同的函數式接口:同一個Lambda表達式就可以與不同的函數式接口聯繫起來,只要它
們的抽象方法簽名能夠兼容。
特殊的void兼容規則:如果一個Lambda的主體是一個語句表達式, 它就和一個返回void的函數描述符兼容(當
然需要參數列表也兼容)
類型推斷(可以省略參數類型):Java編譯器會從上下文(目標類型)推斷出用什麼函數式接口來配合Lambda表達式,這意味着它也可以推斷出適合Lambda的簽名,因爲函數描述符可以通過目標類型來得到。這樣做的好處在於,編譯器可以瞭解Lambda表達式的參數類型,這樣就可以在Lambda語法中省去標註參數類型。換句話說,Java編譯器會像下面這樣推斷Lambda的參數類型。

Comparator<Apple> c =(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
Comparator<Apple> c =(a1, a2) -> a1.getWeight().compareTo(a2.getWeight());

Lambda使用局部變量的限制:。Lambda可以沒有限制地捕獲(也就是在其主體中引用)實例變量和靜態變量。但局部變量必須顯式聲明爲final,或事實上是final。換句話說,Lambda表達式只能捕獲指派給它們的局部變量一次,否則無法編譯。
原因:第一,實例變量都存儲在堆中,而局部變量則保存在棧上。如果Lambda可以直接訪問局部變量,而且Lambda是在一個線程中使用的,則使用Lambda的線程,可能會在分配該變量的線程將這個變量收回之後,去訪問該變量。因此,Java在訪問自由局部變量時,實際上是在訪問它的副本,而不是訪問原始變量。如果局部變量僅僅賦值一次那就沒有什麼區別了——因此就有了這個限制。
第二,這一限制不鼓勵你使用改變外部變量的典型命令式編程模式(這種模式會阻礙很容易做到的並行處理)。

 

方法引用就是Lambda表達式的快捷寫法。使用方法引用時,目標引用放在分隔符::前,方法的名稱放在後面。

Lambda 等效的方法引用
(Apple a) -> a.getWeight() Apple::getWeight
() -> Thread.currentThread().dumpStack() Thread.currentThread()::dumpStack
(str, i) -> str.substring(i) String::substring
(String s) -> System.out.println(s) System.out::println

 

 

 

 



方法引用主要有三類:

  1. 指向靜態方法的方法引用: (args) -> ClassName.staticMethod(args)    ClassName::staticMethod。
  2. 指向任意類型實例方法的方法引用: (arg0, rest) -> arg0.instanceMethod(rest)    ClassName::instanceMethod
  3. 指向現有對象的實例方法的方法引用: (args) -> expr.instanceMethod(args)    expr::instanceMethod。

構造函數引用:對於一個現有構造函數,你可以利用它的名稱和關鍵字new來創建它的一個引用:ClassName::new。它的功能與指向靜態方法的引用類似。例如,Lambda表達式() -> new Apple()構造函數引用爲Apple::new

謂詞複合:謂詞接口包括三個方法:negate、and和or(從左向右確定優先級),讓你可以重用已有的Predicate來創建更復雜的謂詞。
函數複合:把Function接口所代表的Lambda表達式復合起來。Function接口爲此配了andThen和compose兩個默認方法,它們都會返回Function的一個實例。
andThen:Function f = x -> x + 1; Function g = x -> x * 2; Function h = f.andThen(g); h.apply(1) = 4;
compose:Function f = x -> x + 1; Function g = x -> x * 2; Function h = f.compose(g); h.apply(1) = 3;

函數式數據處理
引入流:
流是Java API的新成員,它允許你以聲明性方式處理數據集合(通過查詢語句來表達,而不是臨時編寫一個實現)。
元素序列——就像集合一樣,流也提供了一個接口,可以訪問特定元素類型的一組有序值。因爲集合是數據結構,所以它的主要目的是以特定的時間/空間複雜度存儲和訪問元素(如ArrayList 與 LinkedList)。但流的目的在於表達計算,比如你前面見到的filter、sorted和map。集合講的是數據,流講的是計算。
源——流會使用一個提供數據的源,如集合、數組或輸入/輸出資源。 請注意,從有序集合生成流時會保留原有的順序。由列表生成的流,其元素順序與列表一致。
數據處理操作——流的數據處理功能支持類似於數據庫的操作,以及函數式編程語言中的常用操作,如filter、map、reduce、find、match、sort等。流操作可以順序執行,也可並行執行。
特點1:流水線——很多流操作本身會返回一個流,這樣多個操作就可以鏈接起來,形成一個大的流水線。這讓我們下一章中的一些優化成爲可能,如延遲和短路。流水線的操作可以看作對數據源進行數據庫式查詢。
特點2:內部迭代——與使用迭代器顯式迭代的集合不同,流的迭代操作是在背後進行的。
流與集合:集合與流之間的差異就在於什麼時候進行計算。區別在於流只能遍歷一次,並且它們遍歷數據的方式不同。
集合是一個內存中的數據結構,它包含數據結構中目前所有的值——集合中的每個元素都得先算出來才能添加到集合中。
則是在概念上固定的數據結構(你不能添加或刪除元素),其元素則是按需計算的,流就像是一個延遲創建的集合:只有在消費者要求的時候纔會計算值。|
外部迭代與內部迭代:Collection接口需用用戶去迭代,這成爲外部迭代。相反,Streams庫使用內部迭代,不需要用戶去迭代,只需要給出一個函數指定它幹什麼就可以了,並且內部迭代可以透明的並行處理。
流操作:稱爲中間操作,關閉流的操作稱爲終端操作。中間操作返回另一個流,只有觸發一個終端操作纔會從流的流水線生成結果。
循環合併:Streams儘管filter和map是兩個獨立的操作,但它們合併到同一次遍歷中,把這種叫做循環合併。
使用流一般包括三件事:

  1. 一個數據源(如集合)來執行一個查詢。
  2. 一箇中間操作鏈,形成一條流的流水線;
  3. 一個終端操作,執行流水線,並能生成結果。

 

 

 


同步閱讀Java8實戰書更新博客,直到全部書閱讀完,有興趣的讀者可以關注博主。
Number of pages read : 82 / 353

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章