JavaSE(十六)Java8新特徵

接口的非抽象方法

  接口是對功能的說明而不是對功能的實現,所以接口只能包含抽象方法,但從Java8開始,接口卻可以包含非抽象方法(默認方法和靜態方法)
  接口的默認方法就是用default修飾的普通方法,它包含了函數體,提供默認方法的目的就是爲了減少子類實現接口的工作量。Java的一大特點就是單繼承,單繼承讓編程者從複雜的繼承關係中解脫出來(複雜的繼承關係會讓產生Bug的機率大幅度上升),接口如果能夠有默認實現,那麼又會將編程者扔進複雜的繼承關係中。在Java8的核心包中,很多原有的接口被添加了若干個新的功能(特別是集合框架下的接口),爲了減少子類重新實現這些接口的工作量,添加了默認方法這一語法。可見,默認方法是Java核心包的開發者爲了偷懶而做的妥協,在實際應用中,儘量不要使用默認方法這一語法
  接口的靜態方法和普通類的靜態方法擁有相同的語法結構,用static修飾且包含了函數體,類的靜態方法雖然無法體現出多態,但可以被繼承,而對於接口來說,無論是其子接口還是其實現類,靜態方法都不能被繼承。

Lambda表達式

函數式接口

  有且僅有一個抽象方法的接口(可以有多個非抽象方法)就是函數式接口。可以使用@FunctionalInterface對函數式接口進行標識,該註解會在編譯時檢查當前接口是否滿足函數式接口的條件,函數式接口並不強制要求必須使用該註解進行標識,就像使用@Override來標識重寫函數一樣。可見,除了可有可無的@FunctionalInterface註解以外,函數式接口並沒有添加新的語法,只是對滿足特殊條件的接口進行了概念上的新定義
  函數式接口的用法和普通接口的用法完全一樣,提出函數式接口的定義主要是爲了實現Lambda表達式。在Java中內置了一些比較常用的函數式接口,以便在接收Lambda表達式時,可以方便地利用已存在的,滿足條件的函數式接口(滿足條件主要指參數類型和個數以及返回類型,而與接口名和方法名無關),如果這些函數式接口都無法滿足需求,依然需要自定義函數式接口。Java中部分內置的函數式接口如下:

接口 抽象函數 說明
Consumer<T> void accept(T t) 消費型接口
BiConsumcr<T, U> void accept(T t, U u)
Supplier<T> T get() 供給型接口
Function<T, R> R apply(T t) 函數型接口
BiFunction<T, U, R> R apply(T t, U u)
UnaryOperator<T> T apply(T t)
BinaryOperator<T> T apply(T t1, T t2)
ToXxxFunction<T> xxx applyAsXxx(T t) xxx可取int、long、double
XxxFunction<R> R apply(xxx value) xxx可取int、long、double
Predicate<T> boolean test(T t) 斷言型接口

Lambda表達式

  Lambda表達式雖然在語法上表現爲一個表達式,但實際上會被編譯爲一個實現了某函數式接口的匿名內部類,其語法結構爲:(參數列表)->{代碼},其中,參數列表在類型和個數上必須和函數式接口中唯一抽象方法的參數列表保持一致(參數名可以不同),而這裏的代碼將會作爲該抽象方法的函數體
  Lambda表達式必須要賦值給一個函數式接口類型的變量(成員變量、局部變量或函數參數),其原因有兩點:首先,只有賦值給一個函數式接口,才能確定該Lambda表達式被編譯爲實現了哪個函數式接口的匿名內部類;其次,Lambda表達式不會自動執行函數體中的代碼,必須具有進行方法調用的句柄。大多數時候,Lambda表達式被賦值給一個方法的參數(函數式接口類型),這時在方法中回調函數式接口就可以執行Lambda表達式的代碼,由於接口中可以存在非抽象方法,所以可以通過非抽象方法調用抽象方法(Lambda表達式只能實現抽象方法)來實現功能的封裝(模板方法模式),這時可以通過回調函數式接口的非抽象方法來執行Lambda表達式的代碼。
  Lambda表達式的基本語法結構爲:(參數列表)->{代碼},其中,參數列表的參數類型可以省略,因爲可以根據其代表的抽象方法推導出參數類型(省略參數類型通常是編程人員更喜歡的編程方式),如表達式(int a, String b)->{}可以簡寫爲(a, b)->{}。Java還提供了很多其他的語法糖來實現某些特殊的Lambda表達式:

  1. 省略(),如a->{…},代表(a)->{…},有且僅有一個參數時的語法糖
  2. 省略{},如(…)->表達式,有且僅有一條表達式時的語法糖,當表達式爲函數且無返回值時,代表(…)->{表達式;},當表達式爲其他形式時(如有返回值的函數、普通的運算表達式等),代表(…)->{return 表達式;}

  在Lambda表達式的代碼塊中,不但可以使用代碼塊中的變量、參數列表中的形參,還可以使用Lambda表達式所在類中的成員變量,以及Lambda表達式所在位置可見的局部變量(會自動將局部變量的定義修改爲final修飾)。

方法引用

  方法引用是Lambda表達式的一種特殊表現形式,通過指向一個已實現的方法來與函數式接口進行綁定,它可以使語言的構造更緊湊簡潔,減少冗餘代碼。方法引用的主要格式如下:

  1. 實例::實例方法名,如instance::fun,等效於(X x, Y y…)->instance.fun(x, y…)
  2. 類名::靜態方法名,如MyClass::fun,等效於(X x, Y y…)->MyClass.fun(x, y…)
  3. 類名::實例方法名,如MyClass::fun,等效於(MyClass clazz, X x…)->clazz.fun(x…)
  4. 類名::new,如MyClass::new,等效於(X x, Y y…)->new MyClass(x, y)

  被引用方法的參數列表與其函數式接口方法的參數列表必須具有嚴格的對應關係。

集合新特徵

可迭代對象的遍歷

  對於可迭代的類(實現了Iterable接口的類,如集合),可以通過Iterator來進行元素的遍歷,而在Java8中,又引入了新的遍歷方式,那就是通過Iterable的forEach方法。forEach函數的參數是一個函數式接口,該函數式接口的抽象方法的參數,就是每次被迭代處理的元素,可以用Lambda表達式來對每次迭代的元素進行處理,如iterableObject.forEach(e->{…對本次迭代的元素e進行處理…})。通過Iterator來進行迭代,需要自己手動寫for循環語句,這種迭代方式被叫着外部迭代;提供函數並在函數內部進行循環,只需要傳遞針對各個元素進行處理的處理對象給函數,這種迭代方法叫着內部迭代

public interface Iterable<T> {
    Iterator<T> iterator();
    
    default void forEach(Consumer<? super T> action) {
        for (T t : this) {
            action.accept(t);
        }
    }
}

列表排序

  對於列表的排序,可以使用工具類Collections的靜態方法sort,而在Java8中,又提供了對列表進行排序的新方法,那就是List的sort方法。sort方法只接收一個Comparator參數來對元素進行比較(排序本來就是以比較作爲基礎的),由於Comparator本身是個函數式接口,所以可以用Lambda表達式來對元素進行比較,如listObject.sort((a, b)->{…a與b進行比較,並返回比較結果…})。

public interface List<E> extends Collection<E> {
	default void sort(Comparator<? super E> c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }
    ...
}

Stream

  Stream使用一種類似SQL語句的直觀方式來提供一種對Java集合進行運算和表達的高階抽象,這種風格將要處理的元素集合看作一種流,流在管道中傳輸,並且可以在管道的節點上進行處理,比如篩選,排序,聚合等。
  每個流都綁定着一個數據源(集合、陣列、IO、生成函數等),但它不會改變數據源,只是按照特定的規則對數據進行處理,如同SQL中的select語句。流的處理分爲中間處理和最終處理,中間處理會將流進行處理並返回一個新的流(新的流綁定着一個新的數據源),最終處理纔是爲了得到最後的結果,流的存在只是作爲處理數據源的一種工具,所以如果沒有最終處理,再多的中間處理都是沒有意義的。每一次流操作,應該包含數據源、零次或多次中間處理、一次最終處理,只有在執行最終處理時,流操作纔會真正的執行

public interface Stream<T> extends BaseStream<T, Stream<T>> {
    Stream<T> filter(Predicate<? super T> predicate); // 過濾
    Stream<T> sorted(Comparator<? super T> comparator); // 排序
    Stream<T> sorted(); // 對實現了Comparable的元素進行排序
    Stream<T> limit(long maxSize); // 最多保留maxSize個元素
    Stream<T> distinct(); // 根據元素的equal方法去重
    Stream<T> peek(Consumer<? super T> action); // 新流與原流數據完全相同,但在新流中執行最終操作前,會先對每個元素執行action操作,常用於並行流的同步控制
    Stream<T> skip(long n); // 忽略n個元素,由其他元素組成新流
    
    // 將流中的每一個數據都進行特定的處理,然後組成新的流
    <R> Stream<R> map(Function<? super T, ? extends R> mapper);
    IntStream mapToInt(ToIntFunction<? super T> mapper);
    LongStream mapToLong(ToLongFunction<? super T> mapper);
    DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);
    <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper); // 每一個元素進行處理後都能得到一個流,最後把所有流進行合併得到新流,例如已有一個學校的班級流,mapper就是根據班級流的每一個班級得到該班級的學生流,那麼該方法最後得到的是學校的學生流
    IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper);
    LongStream flatMapToLong(Function<? super T, ? extends LongStream> mapper);
    DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream> mapper);
    
    boolean allMatch(Predicate<? super T> predicate); // 所有元素都滿足條件時返回true,否則false
    boolean anyMatch(Predicate<? super T> predicate); // 只要有元素滿足條件就返回true,否則false
    boolean noneMatch(Predicate<? super T> predicate); // 沒有任何元素滿足條件時返回true,否則false
    
    // 對流中的各個元素進行處理
    void forEach(Consumer<? super T> action); // 在並行流中無法保證消費元素的順序
    void forEachOrdered(Consumer<? super T> action); // 在並行流中也能保證消費元素的順序
    
    // 將流處理爲一個最終數據,reduce適用於以返回值的方式得到處理結果,collect適用於以參數的方式得到處理結果
    T reduce(T identity, BinaryOperator<T> accumulator); // accumulator的apply方法返回處理結果,apply方法參數分別爲上一次處理的結果和新的迭代元素,identity爲上一次處理結果的初始值(在迭代第一個元素時使用)
    Optional<T> reduce(BinaryOperator<T> accumulator); // 第一次迭代的結果就是第一個元素,從第二個元素開始使用accumulator
    <U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner); // 對於並行流和串行流,該方法展現出完全不同的行爲
    				// 對於串行流,第三個參數被忽略,計算方式與兩個參數的reduce方法相似,只是處理的結果類型不一定與原流的元素類型相等
    				// 對於並行流,會先使用accumulator對每一個元素進行一次處理得到新的流(必然與原流個數相等),然後使用combiner消費新的流中的任意兩個元素並將結果加入新流,直到新流中只有一個元素爲止
    <R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner); // 對於並行流和串行流,該方法展現出完全不同的行爲
    				// 對於串行流,第三個參數被忽略,supplier只會產生一個容器,accumulator處理的第一個參數就是supplier產生的唯一容器
    				// 對於並行流,先對每一個元素都調用accumulator處理(方法的第一個參數每次都由supplier新產生,最後將產生的supplier組成一個新的流),然後使用combiner消費新的流中的任意兩個元素(第一個元素會保留在新流中),直到新流中只有一個元素
    <R, A> R collect(Collector<? super T, A, R> collector); // collector是對具有三個參數的collect方法的參數的封裝,Collectors提供了許多常用的collector,比如將Stream轉換爲list
    
    long count(); // 統計流中元素個數
    Optional<T> findFirst(); // 返回第一個元素,如果是無序流,返回的可能是任一元素
    Optional<T> findAny(); // 返回任一元素
    Optional<T> min(Comparator<? super T> comparator); // 返回最小元素
    Optional<T> max(Comparator<? super T> comparator); // 返回最大元素
    
    // 創建Stream並關聯數據源,我們常用Collection的stream方法來創建串行流或parallelStream方法來創建並行流(流關聯的數據源就是當前Collection)
    public static<T> Builder<T> builder(); // 通過builder的方式創建流
    public static<T> Stream<T> empty(); // 綁定的數據源爲空數據
    public static<T> Stream<T> of(T... values); // 綁定的數據源爲values
    public static<T> Stream<T> generate(Supplier<T> s); // 創建一個流,每次獲取元素時通過提供者生成元素
    public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b); // 將兩個流合併爲一個流
}

public interface BaseStream<T, S extends BaseStream<T, S>> extends AutoCloseable {
    Iterator<T> iterator(); // 返回對流的迭代器
    boolean isParallel(); // 是否是並行流
    S sequential(); // 返回等效的串行流,當前流是串行流時返回本身
    S parallel(); // 返回等效的並行流,當前流是並行流時返回本身
    S unordered(); // 返回等效的無序流
    S onClose(Runnable closeHandler); // 關閉流時的額外操作
    void close(); // 關閉流
}

  幾個將集合進行過濾的例子,幫助理解流的各個方法:

// 最常用的方法
public static  List<String> normal(Stream<String> stream, String start) {
    return stream.filter(e->e.startsWith(start)).collect(Collectors.toList());
}

public static List<String> reduce(Stream<String> stream, String start) {
    if (stream.isParallel()) {
        return stream.reduce(new ArrayList(),
                (x, e)->{
                    ArrayList<String> list = new ArrayList<>();
                    if (e.startsWith(start)) list.add(e);
                    return list;
                },
                (x1, x2)-> {
                    x1.addAll(x2);
                    return x1;
                }
        );
    }

    return stream.reduce(
            new ArrayList<String>(),
            (list, e)->{
                if (e.startsWith(start))
                    list.add(e);
                return list;
             },
            (x, y)->null
    );
}

public static List<String> collect(Stream<String> stream, String start) {
    if (stream.isParallel()) {
        return stream.collect(ArrayList::new,
                    (list, e)->{
                        if (e.startsWith(start))
                            list.add(e);
                    },
                    (list1, list2)->{
                        list1.addAll(list2);
                    }
                );
    }

    return stream.collect(ArrayList::new,
                (list, e)->{
                    if (e.startsWith(start))
                        list.add(e);
                },
                (x, y)->{}
            );
}

Optional

  Optional對象是一個可以存放單個對象的容器,它可能存放了一個對象,也可能沒有存放任何對象(存放的null值被當做沒有存放對象)

public final class Optional<T> {
    public static<T> Optional<T> empty(); // 創建一個空的Optional
    public static <T> Optional<T> of(T value); // 創建一個存放了value的Optional,value不能爲null,否則拋出異常
    public static <T> Optional<T> ofNullable(T value); // value爲null時等同於empty(),否則等同於of(value)
    public boolean isPresent(); // 是否存放了對象
    public T get(); // 返回存放的對象,若未存放對象拋出異常
    public T orElse(T other); // 返回存放的對象,若未存放對象就返回other
    public T orElseGet(Supplier<? extends T> other); // 返回存放的對象,若未存放對象就由other生成一個對象返回
    public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X; // 返回存放的對象,若未存放對象則拋出由exceptionSupplier生成的異常
    public Optional<T> filter(Predicate<? super T> predicate); // 如果存放了對象且對象滿足predicate則返回該Optional,否則返回空Optional對象
    public void ifPresent(Consumer<? super T> consumer); // 如果存放了對象就用consumer來處理,否則不做任何操作
    public<U> Optional<U> map(Function<? super T, ? extends U> mapper); // 如果存放了對象則對存放的對象進行處理得到一個新的對象,並將對象存放到一個Optional返回,否則返回空Optional
    public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper);
}

新的日期與時間API

  在Java8之前,通過java.util.Date與Calendar來處理日期和時間,在Java8中,對日期和時間模塊進行了大幅度的重構,主要包含了以下新的類:

  • 時間戳Instant、時鐘Clock,時間戳就是時刻,同一時刻在不同的時區表現爲不同的時間,而時鐘主要用於獲取當前的時間戳
  • 時區時間ZonedDateTime,某一時刻在某一時區表現出來的時間,是時間戳的一種表現形式
  • 日期LocalDate、時間LocalTime、時間與日期LocalDateTime,用於記錄時間,與時區無關,如生日,上班時間等
  • 時區ZoneId、相對於格林尼治時間的時間偏差ZoneOffset
  • 時間間隔Duration、時間段Peroid

其他新功能

  • 添加了類Base64來完成Base64格式的加解密,詳見章節《加密與安全》。
  • 支持重複註解,詳見章節《反射與註解》。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章