github博文傳送門php
Java8中新增了許多的新特性,在這裏本人研究學習了幾個較爲經常使用的特性,在這裏與你們進行分享。(這裏推薦深刻理解Java 8用於理解基礎知識)本文分爲如下幾個章節:html
Lambda 表達式,也可稱爲閉包。Lambda 容許把函數做爲一個方法的參數(函數做爲參數傳遞進方法中)。使用 Lambda 表達式可使代碼變的更加簡潔緊湊。 Lambda表達式能夠替代之前普遍使用的內部匿名類,各類回調,好比事件響應器、傳入Thread類的Runnable等。java
lambda 表達式的語法格式以下:git
(parameters) -> expression 或 (parameters) ->{ statements; }
這裏以經常使用的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以前的時代,爲已存在接口增長一個通用的實現是十分困難的,接口一旦發佈以後就等於定型,若是這時在接口內增長一個方法,那麼就會破壞全部實現接口的對象。
默認方法(以前被稱爲 虛擬擴展方法 或 守護方法)的目標便是解決這個問題,使得接口在發佈以後仍能被逐步演化。數組
public interface vehicle { default void print(){ System.out.println("我是一輛車!"); } }
public interface vehicle { static void blowHorn() { System.out.println("按喇叭!!!"); } }
注:靜態方法與默認方法都可以有多個,默認方法能夠被覆蓋。promise
「函數接口(functional interface)」,就是除去默認方法以及繼承的抽象方法,只有顯式聲明一個抽象方法的接口。它使用@FunctionalInterface註解在類上進行標註,也能夠省略,Java會自動識別。接下來介紹一些常見的函數接口:
該接口包含方法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
注意:在這裏與或非的判斷順序是從左到右的,調用的順序會影響結果。
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]
該類只包含方法:
T get();
Supplier接口是在1.8中新出現的函數接口,用於支持函數式編程。它用於返回一個任意泛型的實例對象,與工廠的功能相似。
該接口表示一個接受單個輸入參數而且沒有返回值的操做。不像其餘函數式接口,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也是函數接口,將在下面進行詳細介紹。
Java8提供的java.util.function包的核心函數接口有4個。
Function接口是爲Java8提供了函數式編程的基礎,apply方法與Consumer的accept方法功能相似,可是提供了返回及類型轉換的可能,功能更增強大;再經過andThen與compose方法可使Function組成Function功能鏈,進行多級數據處理及轉換。
R apply(T t) – 將Function對象應用到輸入的參數上,而後返回計算結果。
default
default
static
R apply(T t);
接收類型:T
返回類型:R
類型轉換:T→R
Function接口的核心方法,能夠執行任意的操做,且具備返回值。接收一個T類型的對象,在通過處理後,返回一個R類型的對象。主要功能爲類型轉換及數據處理。
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方法造成方法鏈。
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方法造成方法鏈。
static <T> Function<T, T> identity() { return t -> t; }
接收類型:無
返回類型:Function<T, T>
類型轉換:T→T
該方法的說明是:返回一個函數,它老是返回輸入參數。調用該方法能夠獲得一個返回輸入參數的Funtion,這個Function就能夠單純的用來作數據處理,而不用類型轉換。
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
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
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]
Optional<T> min(Comparator<? super T> comparator); Optional<T> max(Comparator<? super T> comparator);
根據給定的comparator返回Stream中的max或min。
long count();
返回Stream中對象的個數。
boolean anyMatch(Predicate<? super T> predicate); boolean allMatch(Predicate<? super T> predicate); boolean noneMatch(Predicate<? super T> predicate);
根據給定的predicate判斷Stream是否匹配條件。
<R, A> R collect(Collector<? super T, A, R> collector);
根據給定的collector對Stream中的元素進行操做,返回複雜數據結構的對象。用於將Stream中的對象轉換成咱們想要的結構,如list、map、set等。
前例中就使用collect(Collectors.toList())將Stream中的對象轉換成List。
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
Object[] toArray();
將Stream中的對象返回成一個Object數組。
void forEach(Consumer<? super T> action);
顧名思義,對Stream中每一個元素進行action操做,與peek相似,但forEach是一個最終操做,通常在結束時查看對象使用。
Optional<T> findFirst(); Optional<T> findAny();
findFirst能夠返回Stream中第一個對象,並將它封裝在Optional中。
findAny則不是返回第一個對象,而是任意一個對象。在順序Stream中findFirst和findAny的結果是一致的,但在並行Stream中,findFirst存在着限制,故在並行Stream中須要使用findAny(findAny源碼註釋中寫的是some element?)。一樣將對象封裝在Optional中。
在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方法拋出一個異常。
Java8使用新的日期時間API覆蓋舊的日期時間API的,處理了如下缺點。
JAVA8引入了java.time包,一個新的日期時間API。限於篇幅與精力問題,這裏不對java.time進行過多的介紹,這裏推薦幾篇我的以爲不錯的博文以供研究:
Java 類庫的新特性之日期時間API (Date/Time API )
Java 8 之 java.time 包