Java8——快速入門手冊(學習筆記)

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 包

相關文章
相關標籤/搜索