目錄
Java8特性學習筆記
Lambda表達式
Lambda語法
Lambda表達式的特徵
Lambda表達式例子
方法引用
方法引用的種類
方法引用的例子
默認方法
默認方法(defalut)
靜態方法(static)
函數接口
java.util.function.Predicate
java.util.Comparator
java.util.function.Supplier
java.util.function.Consumer
Function
說明
主要方法
方法詳解
Stream
中間操做
最終操做
Optional API
Date Time API
github博文傳送門php
Java8特性學習筆記
Java8中新增了許多的新特性,在這裏本人研究學習了幾個較爲經常使用的特性,在這裏與你們進行分享。(這裏推薦深刻理解Java 8用於理解基礎知識)本文分爲如下幾個章節:html
- Lambda 表達式
- 方法引用
- 默認方法
- 函數接口
- Function
- Stream
- Optional API
- Date Time API
Lambda表達式
Lambda 表達式,也可稱爲閉包。Lambda 容許把函數做爲一個方法的參數(函數做爲參數傳遞進方法中)。使用 Lambda 表達式可使代碼變的更加簡潔緊湊。 Lambda表達式能夠替代之前普遍使用的內部匿名類,各類回調,好比事件響應器、傳入Thread類的Runnable等。java
Lambda語法
lambda 表達式的語法格式以下:git
(parameters) -> expression 或 (parameters) ->{ statements; }
Lambda表達式的特徵
- 類型聲明(可選):能夠不須要聲明參數類型,編譯器會識別參數值。
- 參數圓括號(可選):在單個參數時能夠不使用括號,多個參數時必須使用。
- 大括號和return關鍵字(可選):若是隻有一個表達式,則能夠省略大括號和return關鍵字,編譯器會自動的返回值;相對的,在使用大括號的狀況下,則必須指明返回值。
Lambda表達式例子
這裏以經常使用的list排序功能爲例:github
private static List<People> peopleList = new ArrayList<People>(); { peopleList.add(new People("a",17)); peopleList.add(new People("b",16)); peopleList.add(new People("c",19)); peopleList.add(new People("d",15)); } @Test public void testLambda(){ System.out.println("排序前:"+peopleList); //第一種,傳統匿名Compartor接口排序 Collections.sort(peopleList, new Comparator<People>() { @Override public int compare(People o1, People o2) { return o1.getAge().compareTo(o2.getAge()); } }); System.out.println("匿名接口方法——排序後:"+peopleList); //第二種,使用Lambda表達式來代替匿名接口方法 //1.聲明式,不使用大括號,只能夠寫單條語句 Collections.sort(peopleList,(People a,People b)->a.getAge().compareTo(b.getAge())); System.out.println("Lambda表達式一、排序:"+peopleList);; //2.不聲明式,使用大括號,能夠寫多條語句 Collections.sort(peopleList,(a,b)->{ System.out.print("——————————————"); return a.getAge().compareTo(b.getAge()); }); System.out.println(); System.out.println("Lambda表達式二、排序:"+peopleList); //第三種,使用Lambda表達式調用類的靜態方法 Collections.sort(peopleList,(a,b)->People.sortByName(a,b)); System.out.println("Lambda表達式調用靜態方法:"+peopleList); //第四種,使用Lambda表達式調用類的實例方法 Collections.sort(peopleList,(a,b)->new People().sortByAge(a,b)); System.out.println("Lambda表達式調用實例方法:"+peopleList); }
對應的運行結果:
(注意:在Lambda表達式中只能對final的對象進行操做,聲明的對象也爲final)express
有的朋友應該已經觀察到了,Lambda 表達式與C中的函數指針,JavaScript的匿名function均有些類似。其實,Lambda表達式本質上是一個匿名的方法,只不過它的目標類型必須是「函數接口(functional interface)」,這是Java8引入的新概念,在接下來會進行更加詳細的介紹。編程
方法引用
在一些Lambda中可能只是單純的調用方法,好比前例中的3、四,在這種狀況下,就可使用方法引用的方式來提升可讀性。api
方法引用的種類
- 類靜態方法引用
Class::staticMethodName
- 某個對象的方法引用
instance::instanceMethodName
- 特定類的任意對象的方法引用:
Class::method
- 構造方法引用:
Class::new
方法引用的例子
@Test public void testMethodReference() { //第一種,引用類的靜態方法 Collections.sort(peopleList, People::sortByName); System.out.println("引用類的靜態方法:" + peopleList); //第二種,引用類的實例方法 Collections.sort(peopleList, new People()::sortByAge); System.out.println("引用類的實例方法:" + peopleList); //第三種,特定類的方法調用() Integer[] a = new Integer[]{3, 1, 2, 4, 6, 5}; Arrays.sort(a, Integer::compare); System.out.println("特定類的方法引用:" + Arrays.toString(a)); //第四種,引用類的構造器 Car car = Car.create(Car::new); System.out.println("引用類的構造器:" + car); }
public static Car create(Supplier<Car> supplier){ return supplier.get(); }
默認方法
在Java8以前的時代,爲已存在接口增長一個通用的實現是十分困難的,接口一旦發佈以後就等於定型,若是這時在接口內增長一個方法,那麼就會破壞全部實現接口的對象。
默認方法(以前被稱爲 虛擬擴展方法 或 守護方法)的目標便是解決這個問題,使得接口在發佈以後仍能被逐步演化。數組
默認方法(defalut)
public interface vehicle { default void print(){ System.out.println("我是一輛車!"); } }
靜態方法(static)
public interface vehicle { static void blowHorn() { System.out.println("按喇叭!!!"); } }
注:靜態方法與默認方法都可以有多個,默認方法能夠被覆蓋。promise
函數接口
「函數接口(functional interface)」,就是除去默認方法以及繼承的抽象方法,只有顯式聲明一個抽象方法的接口。它使用@FunctionalInterface註解在類上進行標註,也能夠省略,Java會自動識別。接下來介紹一些常見的函數接口:
java.util.function.Predicate
該接口包含方法boolean test(T t),該接口通常用於條件的檢測,內部包含三個默認方法:and、or、negate、,即與或非,用於各式的條件判斷。例:
Predicate<Integer> predicate = x -> x > 3; predicate.test(10);//true predicate.negate().test(10);//false predicate.or(x -> x < 1).and(x -> x > -1).negate().test(-1);//true
注意:在這裏與或非的判斷順序是從左到右的,調用的順序會影響結果。
java.util.Comparator
Comparator是Java中的經典接口,在排序中較爲經常使用。Java8在此之上添加了一些新的默認方法,來豐富該接口的功能。例:
Integer[] a = new Integer[]{3, 1, 2, 4, 6, 5}; Comparator<Integer> comparator = Integer::compare; Arrays.sort(a, comparator); System.out.println("升序:" + Arrays.toString(a)); Arrays.sort(a,comparator.reversed()); System.out.println("降序:"+Arrays.toString(a));
結果
升序:[1, 2, 3, 4, 5, 6] 降序:[6, 5, 4, 3, 2, 1]
java.util.function.Supplier
該類只包含方法:T get();
Supplier接口是在1.8中新出現的函數接口,用於支持函數式編程。它用於返回一個任意泛型的實例對象,與工廠的功能相似。
java.util.function.Consumer
該接口表示一個接受單個輸入參數而且沒有返回值的操做。不像其餘函數式接口,Consumer接口指望執行修改內容的操做。例如 ,咱們須要一個批量修改People的方法,利用Predicate和Consumer就能夠這麼寫
在People內增長updateMany方法:
public static List updateMany(List<People> peopleList, Predicate<People> predicate, Consumer<People> consumer) { for (int i = 0; i < peopleList.size(); i++) { if (predicate.test(peopleList.get(i))) { consumer.accept(peopleList.get(i)); } } return peopleList; }
調用:
//批量修改,將age<18的對象的age改成18 People.updateMany(peopleList, p -> p.getAge() < 18, p -> p.setAge(18)); System.out.println("修改後的結果:" + peopleList);
經過這種方式,能夠將內部的判斷邏輯與修改代碼放至外部調用,而將for、if等語句封裝至內部,提升代碼的可讀性。
其餘的還有一些函數接口,如Runnable,InvocationHandler等,在這裏就不闡述了。有興趣的你們能夠自行查詢資料。Stream、Function、Optional也是函數接口,將在下面進行詳細介紹。
Function
說明
Java8提供的java.util.function包的核心函數接口有4個。
- 函數型T ->R,完成參數類型T向結果類型R的轉換和數據處理。核心函數接口Function
- 判斷型T ->boolean,核心函數接口Predicate
- 消費型T ->void,核心函數接口Consumer
- 供給型void->T,核心函數接口Supplier
Function接口是爲Java8提供了函數式編程的基礎,apply方法與Consumer的accept方法功能相似,可是提供了返回及類型轉換的可能,功能更增強大;再經過andThen與compose方法可使Function組成Function功能鏈,進行多級數據處理及轉換。
主要方法
-
R apply(T t) – 將Function對象應用到輸入的參數上,而後返回計算結果。
-
default Function<T,V> andThen(Function<? super R,? extends V> after) 返回一個先執行當前函數對象apply方法再執行after函數對象apply方法的函數對象。
-
default Function<T,V> compose(Function<? super V,? extends T> before)返回一個先執行before函數對象apply方法再執行當前函數對象apply方法的函數對象。
-
static Function<T,T> identity() 返回一個執行了apply()方法以後只會返回輸入參數的函數對象。
方法詳解
apply:
R apply(T t);
接收類型:T
返回類型:R
類型轉換:T→R
Function接口的核心方法,能夠執行任意的操做,且具備返回值。接收一個T類型的對象,在通過處理後,返回一個R類型的對象。主要功能爲類型轉換及數據處理。
compose:
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) { Objects.requireNonNull(before); return (V v) -> apply(before.apply(v)); }
接收類型:Function<? super V, ? extends T>
返回類型:Function<V, R>
類型轉換:(V+)→(T-)→T→R
apply執行順序:before→this
此處「V+」指代「? super V」,表示包含V在內的V的任意父類;"T-"指代「? extends T」,表示包含T在內的T的任意子類。compose方法返回一個Function<V,R>,這個Function先執行before的apply方法,將V+類型的數據轉換爲T-類型,再將T-做爲參數傳遞給this的apply方法,將T類型轉換爲R類型。
經過compose方法,能夠在某個Function執行以前插入一個Function執行。因爲返回類型依舊爲Function,能夠重複調用compose方法造成方法鏈。
andThen:
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); }
接收類型:Function<? super R, ? extends V>
返回類型:Function<T, V>
類型轉換:T→R→(R+)→(V-)
apply執行順序:this→after
此處「R+」指代「? super R」,表示包含R在內的R的任意父類;"V-"指代「? extends V」,表示包含V在內的V的任意子類。andThen方法返回一個Function<T,V>,這個Function先執行this的apply方法,將T類型的數據轉換爲R類型,再將R做爲參數傳遞給after的apply方法,將R+類型轉換爲V-類型。
經過andThen方法,能夠在某個Function執行以後插入一個Function執行。因爲返回類型依舊爲Function,能夠重複調用andThen方法造成方法鏈。
identity:
static <T> Function<T, T> identity() { return t -> t; }
接收類型:無
返回類型:Function<T, T>
類型轉換:T→T
該方法的說明是:返回一個函數,它老是返回輸入參數。調用該方法能夠獲得一個返回輸入參數的Funtion,這個Function就能夠單純的用來作數據處理,而不用類型轉換。
Stream
Java8中提供了Stream API,即流式處理。能夠經過將List、Set、Array等對象轉換成流進行操做。Stream內的流操做分爲兩種:中間操做和最終操做,中間操做會返回一個全新的Stream對象,意味着你的操做不會影響最初的流;最終操做會將流進行轉換或者操做,返回非Stream的對象。Stream能夠替代傳統的循環操做,從線程上區別,Stream分爲串行(Stream)和並行(parallelStream),關於Stream的性能分析能夠查看這篇文章《Stream性能分析》。下面來看下Strea內的一些方法:
中間操做
-
distinct
java Stream<T> distinct();
去除Stream中重複的對象,並返回一個流。(使用對象的equals方法) -
skip
java Stream<T> skip(long n);
跳過Stream中的前n個對象,將其餘對象返回一個Stream。若是n超過了Stream中對象的個數,則會返回一個空的Stream。 -
limit
java Stream<T> limit(long maxSize);
截取Stream的前maxSize個對象,並造成一個新Stream。 -
filter
java Stream<T> filter(Predicate<? super T> predicate);
根據給定的predicate來過濾對象,返回知足條件的對象構成的Stream。 -
map
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
經過給定的mapper,將T類型的流轉換爲R類型的Stream。
-
flatMap
java <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
flatMap也是將Stream進行轉換,flatMap與map的區別在於 flatMap是將一個Stream中的每一個值都轉成一個個Stream,而後再將這些流扁平化成爲一個Stream。
例(轉自:Java8 新特性之流式數據處理):
假設咱們有一個字符串數組String[] strs = {"java8", "is", "easy", "to", "use"};,咱們但願輸出構成這一數組的全部非重複字符,那麼咱們可能首先會想到以下實現:java List<String[]> distinctStrs = Arrays.stream(strs) .map(str -> str.split("")) // 映射成爲Stream<String[]> .distinct() .collect(Collectors.toList());
在執行map操做之後,咱們獲得是一個包含多個字符串(構成一個字符串的字符數組)的流,此時執行distinct操做是基於在這些字符串數組之間的對比,因此達不到咱們但願的目的,此時的輸出爲:[j, a, v, a, 8] [i, s] [e, a, s, y] [t, o] [u, s, e]
distinct只有對於一個包含多個字符的流進行操做才能達到咱們的目的,即對Stream進行操做。此時flatMap就能夠達到咱們的目的:java List<String> distinctStrs = Arrays.stream(strs) .map(str -> str.split("")) // 映射成爲Stream<String[]> .flatMap(Arrays::stream) // 扁平化爲Stream<String> .distinct() .collect(Collectors.toList());
flatMap將由map映射獲得的Stream<String[]>,轉換成由各個字符串數組映射成的流Stream,再將這些小的流扁平化成爲一個由全部字符串構成的大流Steam,從而可以達到咱們的目的。 -
sorted
java Stream<T> sorted(); Stream<T> sorted(Comparator<? super T> comparator);
sorted方法能夠對Stream進行排序。排序的對象必須實現Comparable,若是沒實現會拋出ClassCastException;不提供comparator時,則會調用compareTo方法。 -
peek
java Stream<T> peek(Consumer<? super T> action);
對流中的每一個對象執行提供的action操做。
在Stack中,peek用於查看一個對象。在流中也是同樣,用於在流循環時,根據給定的action進行查看對象。雖然能夠進行元素修改操做,但不建議。 -
綜合例:
java Integer[] a = new Integer[]{3, 1, 2, 5, 11, 4, 6, 5, 3, 1}; List<Integer> aList = Arrays.stream(a) .distinct() .skip(1) .filter((e) -> e < 6) .peek(e -> System.out.println("循環1次")) .limit(4) .sorted() .collect(Collectors.toList()); System.out.println(aList);
輸出:循環1次 循環1次 循環1次 循環1次 [1, 2, 4, 5]
最終操做
- 聚合
- max & min
Optional<T> min(Comparator<? super T> comparator); Optional<T> max(Comparator<? super T> comparator);
根據給定的comparator返回Stream中的max或min。
- count
long count();
返回Stream中對象的個數。
- 匹配
- anyMatch & allMatch & noneMatch
boolean anyMatch(Predicate<? super T> predicate); boolean allMatch(Predicate<? super T> predicate); boolean noneMatch(Predicate<? super T> predicate);
根據給定的predicate判斷Stream是否匹配條件。
- collect
<R, A> R collect(Collector<? super T, A, R> collector);
根據給定的collector對Stream中的元素進行操做,返回複雜數據結構的對象。用於將Stream中的對象轉換成咱們想要的結構,如list、map、set等。
前例中就使用collect(Collectors.toList())將Stream中的對象轉換成List。- reduce
Optional<T> reduce(BinaryOperator<T> accumulator); T reduce(T identity, BinaryOperator<T> accumulator);
若是咱們不知但願單純的返回List這樣的類型,而是但願將整個Stream通過一些操做後,規約成一個對象返回,就能夠用到規約操做。reduce方法有兩個參數,其中accumulator表明着規約的操做,即用何種的方式進行參數化處理;identity則是accumulator的標識值(具體用處暫不明)。
例:求和java Integer[] a = new Integer[]{3, 1, 2, 5, 11, 4, 6, 5, 3, 1}; int sum = Arrays.stream(a) .distinct() .filter((e) -> e < 6) .reduce(0, (x, y) -> x + y);//或.reduce(0, Integer::sum); System.out.println(sum);//15
- toArray
Object[] toArray();
將Stream中的對象返回成一個Object數組。
- forEach
void forEach(Consumer<? super T> action);
顧名思義,對Stream中每一個元素進行action操做,與peek相似,但forEach是一個最終操做,通常在結束時查看對象使用。
- findFirst & findAny
Optional<T> findFirst(); Optional<T> findAny();
findFirst能夠返回Stream中第一個對象,並將它封裝在Optional中。
findAny則不是返回第一個對象,而是任意一個對象。在順序Stream中findFirst和findAny的結果是一致的,但在並行Stream中,findFirst存在着限制,故在並行Stream中須要使用findAny(findAny源碼註釋中寫的是some element?)。一樣將對象封裝在Optional中。
Optional API
在java8以前的編程中,咱們老是須要進行if(obj=null)來防止NullPointException,而在java8後,提供了Optional類,它一方面用於防止NullPotinException的判斷,另外一方面則爲流式編程與函數式變成提供了更好的支持;Optional是一個包含對象的容器,它能夠包含null值。在Optional類中封裝了許多的方法,來讓咱們更好的處理咱們的代碼。接下來看看Optional中幾個經常使用的方法:
-
empty & of & ofNullable
java public static <T> Optional<T> empty(){...} public static <T> Optional<T> of(T value) {return new Optional<T>(value);} public static <T> Optional<T> ofNullable(T value){return value == null ? empty() : of(value);}
首先,Optioanl的構造方法是私有的,只能經過以上三個靜態方法來獲取Optional的實例。empty方法會返回Optional中的常量EMPTY對象,通常在compare時使用,注意這裏的EMPTY是單例的並且爲常量;通常咱們須要構造一個Optional,使用of或ofNullable方法,of方法會將咱們的傳值構造一個新的Optional返回,而ofNullable則在接收null時返回EMPTY實例。 -
isPresent
java public boolean isPresent() {return value != null;} public void ifPresent(Consumer<? super T> consumer) {if (value != null)consumer.accept(value);}
isPresent方法用於判斷Optional包含的value是否爲null,第一種方法返回一個boolean;第二種方法則根據判斷,爲null則什麼都不執行,不爲null則執行一個consumer操做。 -
map & flatMap
java public<U> Optional<U> map(Function<? super T, ? extends U> mapper){...} public<U> Optional<U> flatMap(Function<? super T, Optional<U> mapper){...}
map與flatMap與Stream中用法與功能大體相同,都是轉換及合併轉換,再也不贅述。 -
get
java public T get() {...}
get方法用於獲取value。須要注意的是,若是value爲null,則會拋出NoSuchElementException。 -
filter
java public Optional<T> filter(Predicate<? super T> predicate) {...}
filter方法也是獲取value,它能夠傳入一個predicate,用於判斷value是否知足條件。若是value爲null,則會返回this;若是predicate.test爲true,則返回this,不然會返回EMPTY。 -
orElse & orElseGet & orElseGet
java public T orElse(T other) {return value != null ? value : other;} public T orElseGet(Supplier<? extends T> other) {return value != null ? value : other.get();} public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X{...}
這三個方法都用於獲取value,同時能夠在value==null的狀況下作出不一樣的操做。orElse能夠傳入一個other,當value==null時則返回null;orElseGet則是使用Supplier,爲null時調用get方法;orElseThrow則是接收一個Supplier包含某種異常的exceptionSupplier,爲null時則會調用get方法拋出一個異常。
Date Time API
Java8使用新的日期時間API覆蓋舊的日期時間API的,處理了如下缺點。
- 線程安全 - java.util.Date不是線程安全的,所以開發者必須在使用日期處理併發性問題。新的日期時間API是不可變的,而且沒有setter方法。
- 設計問題 - 默認的開始日期爲1900年,月的開始月份爲0而不是1,沒有統一。不直接使用方法操做日期。新的API提供了這樣操做實用方法。
- 時區處理困難 - 開發人員必須編寫大量的代碼來處理時區的問題。新的API設計開發爲這些特定領域提供了幫助。
JAVA8引入了java.time包,一個新的日期時間API。限於篇幅與精力問題,這裏不對java.time進行過多的介紹,這裏推薦幾篇我的以爲不錯的博文以供研究:
Java 類庫的新特性之日期時間API (Date/Time API )
Java 8 之 java.time 包