1. 程式人生 > >Java8 新特性學習

Java8 新特性學習

1、介面中的預設方法

Java8中允許介面中包含具有具體實現的方法,這種方法被稱為“預設方法”,使用default關鍵字修飾。
如:

public interface MyInterface {

    String notDefault();

    default String testDefault() {
        return "Hello Default Method";
    }
}

該介面的實現類中只需要實現抽象方法即可,預設方法可以直接使用。

MyInterface myInterface = new MyInterface() {
    @Override
    public
String notDefault() { return "這不是一個default方法,實現類需要重寫這個方法"; } }; System.out.println(myInterface.notDefault()); System.out.println(myInterface.testDefault());

介面中預設方法的“類優先”原則:

若一個介面中定義了一個預設方法,而另外一個父類或介面中又定義了一個同名的方法時

  • 選擇父類中的方法。如果一個父類中提供了具體的實現,那麼介面中具有相同名稱和引數的預設方法會被忽略。 測試程式碼:
public  class
MySuperClazz {
public String testDefault() { return "This is default method"; } } public interface MyInterface { default String testDefault() { return "Hello Default Method"; } } public class MyClazz extends MySuperClazz implements MyInterface{ } MyClazz myClazz = new
MyClazz(); System.out.println(myClazz.testDefault());

輸出結果:

This is default method
  • 介面衝突,如果一個父介面提供一個預設方法,而另一個介面也提供了一個具有相同名稱和引數列表的方法(不管方法是否是預設方法),那麼實現類必須覆蓋該方法來解決衝突。

測試程式碼:

public class MyClazz /*extends MySuperClazz */implements MyInterface,MyInterface2{

    @Override
    public String testDefault() {
        return "測試兩個介面中同名方法";
    }
}

public interface MyInterface2 {

    default String testDefault() {
        return "Hello Default Method2";
    }
}

public interface MyInterface {

    default String testDefault() {
        return "Hello Default Method";
    }
}

測試結果:

測試兩個介面中同名方法

Java8的介面中也可以宣告靜態方法

public interface MyInterface {

    static void testStatic() {
        System.out.println("靜態方法");
    }
}

2、Lambda表示式

上面提到的程式碼

MyInterface myInterface = new MyInterface() {
    @Override
    public String notDefault() {
        return "這不是一個default方法,實現類需要重寫這個方法";
    }
};

可以通過Lambda表示式改寫成如下方式

MyInterface myInterface = () -> "這不是一個default方法,實現類需要重寫這個方法";

這種形式,便是java8提供的更加簡潔的方式,即Lambda表示式。相比於jdk1.7之前的程式碼,使用Lambda表示式更加的簡短易讀。

Lambda表示式是一種“語法糖”,需要函式式介面的支援。
Lambda表示式的引數列表的資料型別可以省略不寫,因為JVM編譯器可以通過上下文推斷出資料型別,即:型別推斷。Java的編譯器能夠自動識別引數的型別,所以可以省略型別不寫。

Lambda表示式可以分為兩部分:
左側:指定了Lambda表示式需要的所有引數
右側:指定了Lambda體,即lambda表示式要執行的功能

Lambda表示式有以下幾種格式:

  • 語法格式一:無引數,無返回值
格式:
    () -> System.out.println("Hello Lambda");
例:
    Runnable r1 = () -> System.out.println("Hello Lambda");
  • 語法格式二:有一個引數,並且無返回值
格式:
     (x) -> System.out.println(x)
     只有一個引數小括號可以不寫
     x -> System.out.println(x)
例:
    Consumer<String> con = (x) -> System.out.println(x);
    Consumer<String> con2 = x -> System.out.println(x);
    con.accept("zhw");
    con2.accept("nb");
  • 語法格式三:有兩個引數以上,並且lambda體中有多條語句,並且有返回值,如果有多條語句必須使用{}
例:
    Comparator<Integer> com = (x,y) -> {
        System.out.println("語法格式三");
        return Integer.compare(x,y);
    };
  • 語法格式四:lambda體重只有一條語句,並有返回值,大括號可以省略,return可以省略
Comparator<Integer> com = (x,y) -> Integer.compare(x,y);

Lambda表示式的範圍:

對於lambdab表示式外部的變數,其訪問許可權的粒度與匿名物件的方式非常類似。
你能夠訪問區域性對應的外部區域的區域性final變數,以及成員變數和靜態變數。
注意:final關鍵字可以省略,但是編譯的時候依然被隱式的當成final變數來處理。

方法引用:

若Lambda體中的內容有方法已經實現了,我們可以使用“方法引用”,(可以理解為方法引用是Lambda表示式的另一種表現形式)

注意:
1、 Lambda體中呼叫方法的引數列表與返回值型別,要與函式式介面中抽象方法的引數列表和返回值型別保持一致
2、若lambda引數列表中的第一個引數是 例項方法的呼叫者,而第二個引數是例項方法的引數時,可以使用ClassName::method的形式

方法引用的三種語法格式:

  • 物件::例項方法名

  • 類::靜態方法名

  • 類::例項方法名

構造器引用

格式:

  • ClassName::new

注意:
需要呼叫的構造器的引數列表要與函式式介面中抽象方法的引數保持一致

3、函式式介面

上文提到的Lambda表示式需要函式式介面的支援那什麼是函式式介面呢?所謂的函式式介面就是必須有且僅有一個抽象方法宣告的介面,Lambda表示式必須要與抽象方法的宣告相匹配,由於預設方法不是抽象的,所以可以在函式式介面中任意新增預設方法。

定義函式式介面的時候可以使用@FunctionalInterface註解,編譯器會判斷定義的介面是否滿足函式是介面的條件,如果不滿足,編譯器會丟擲異常。

@FunctionalInterface
public interface MyFunction<T,R> {

    R get(T t1,T t2);
}

Java8內建的函式式介面:

Consumers

Consumer<T> : 消費型介面,Consumer代表了在一個輸入引數上需要進行的操作。
    void accept(T t); 

Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));

Suppliers

Supplier<T>  : 供給型介面,口產生一個給定型別的結果。與Function不同的是,Supplier沒有輸入引數。

Supplier<Person> personSupplier = Person::new;
personSupplier.get(); // new Person

Functions

Function<T,R>  : 函式型介面,接受一個引數(T),並返回單一的結果(R)。預設方法可以將多個函式串在一起(compse, andThen)。

Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = toInteger.andThen(String::valueOf);
backToString.apply("123"); // "123"

Predicates

Predicate<T>  : 段言型介面,是一個布林型別的函式,該函式只有一個引數。Predicate介面包含了多種預設方法,用於處理複雜的邏輯動詞(and, or,negate)

Predicate<String> predicate = (s) -> s.length() > 0;
predicate.test("foo"); // true
predicate.negate().test("foo"); // false
Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;
Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();

4、Stream 資料流

資料流表示元素的序列,並支援不同種類的操作來執行元素上的計算。資料流是單體(Monad),並且在Java8函數語言程式設計中起到重要作用。

流(Stream)到底是什麼:是資料渠道,用於操作資料來源(集合,陣列等)所生成的元素序列“集合講的是資料,流講的是計算”。

注意:

  1. Stream自己不會儲存元素
  2. Stream不會改變源物件。相反,他們會返回一個持有結果的新的Stream
  3. Stream操作是延遲執行的。這意味著他們會等到需要結果的時候才會執行。

Stream操作的三個步驟:

  • 建立資料流
  • 銜接操作
  • 終止操作

Stream資料流的操作過程圖:
這裡寫圖片描述

資料流操作要麼是銜接操作(建立過程也可以看成銜接操作),要麼是終止操作,資料流的操作大多數是無狀態的而且是無干擾的,即當一個函式不修改資料流的底層資料來源時,它就是無干擾的;當一個函式的操作不依賴於外部作用域中的任何操作過程中的可變的變數或狀態時,它就是無狀態的。

建立資料流:

建立Stream的五種方式:
1、 可以通過Collection系列集合中的stream()或者parallelStream()方法獲取序列流或者並行流。

List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();

2、 通過Arrays中的靜態方法stream()方法獲取陣列流

 Arrays.asList("a1", "a2", "a3").stream();

3、 通過Stream類中的靜態方法of()方法獲取流,可以從一系列物件引用中建立資料流

Stream<String> stream = Stream.of(“aa”,”bb”,”cc”);

4、 建立無限流

//迭代
Stream<Integer> stream = Stream.iterate(0,(x) -> x+ 2);
Stream.limit(10).forEach(System.out::println)
//生成
Stream.generate(()->Math.random()).forEach(System.out::println);

5、java8提供了一些特殊種類的流,用於處理基本資料型別int,long,double。分別為IntStream,LongStream,DoubleStream

前四種方式建立的資料流稱為“物件資料流”,第五種方式建立的流稱為“基本資料流”,基本資料流和物件資料流用法相似,但有一些不同,基本資料流使用的是特殊的lambda表示式,如:IntFunction而不是Function,IntPredicate而不是Predicate。同時基本資料流還支援一些額外的聚合終止操作sum()和average()等。有些時候需要進行物件資料流和基本資料流的轉換。轉換方式如下:

物件資料流轉換成基本資料流:

mapToInt() 、 mapToLong() 和mapToDouble()

例:
Stream.of("a1", "a2", "a3")
        .map(s -> s.substring(1))
        .mapToInt(Integer::parseInt)
        .max()
        .ifPresent(System.out::println); 

基本資料流轉換成物件資料流:

mapToObj()

例:
IntStream.range(1, 4)
        .mapToObj(i -> "a" + i)
        .forEach(System.out::println);

銜接操作:

多個銜接操作可以連線起來形成 一個流水線,除非流水線上出發終止操作,否則銜接操作不會執行任何處理,而在終止操作是一次性全部處理,稱為“惰性求值”或“延遲執行”,延遲性是銜接操作的一個重要特性。

例:
Stream.of("d2", "a2", "b1", "b3", "c")
        .filter(s -> {
            System.out.println("filter: " + s);
            return true;
        });

上面這段程式碼並不會向控制檯列印任何東西,因為並沒有終止操作,銜接操作只有在終止操作呼叫時才會被執行。

銜接操作主要有以下幾種方式:

篩選與切片

filter—接收Lambda,從六中排除某些元素
limit—截斷流,使其元素不超過給定數量
skip(n)—跳過元素,返回一個扔掉了前n個元素的流。若流中元素不足n個,則返回一個空流。與limit互補
distinct—篩選,通過流所生成元素的hashCode()equals()去除重複元素

內部迭代:迭代操作有Stream API完成
外部迭代:自己編寫迭代操作

對映

map—接收Lambda,將元素轉換成其他形式或提取資訊。接收一個函式操作引數,該函式會應用到每個元素上,並將其對映成一個新的元素。

flatMap—接收一個函式作為引數,將流中的每個值都換成另一個流,然後把所有流連結成一個流

排序

sorted()—自然排序
sorted(Comparator com)—定製排序

終止操作:

在資料流中,只有執行了終止操作時,銜接操作才會一次性執行。終止操作主要有以下幾種方式:

查詢與匹配

allMatch—檢查是否匹配所有元素
anyMatch—檢查是否至少匹配一個元素
noneMatch—檢查是否沒有匹配所有元素
findFirst—返回當前流中的第一個元素
findAny—返回流中元素的任意元素
count—返回流中元素的總個數
max—返回流中的最大值
min—返回流中的最小值

歸約

ruduce(T identity,BinaryOperator) / ruduce(BinaryOperator) –可以將流中元素反覆結合起來,組合為單一結果。

收集

collect—將流轉換為其他形式。接收一個Collector介面的實現,用於給Stream中元素做彙總的方法,將流中的元素存放在不同型別的結果中。Java8通過內建的 Collectors 類支援多種內建的收集器。

List<Person> filtered = persons.stream()
                                .filter(p -> p.name.startsWith("P"))
                                .collect(Collectors.toList());
System.out.println(filtered); // [Peter, Pamela]

5、並行流與序列流

序列流:在一個執行緒上執行一個內容

Arrays.asList("a1", "a2", "a3")
        .stream()
        .findFirst()
        .ifPresent(System.out::println);

並行流:就是把一個內容分成多個數據快,並用不同的執行緒分別處理每個資料塊的流。並行流能夠在多個執行緒上執行操作。主要是為了解決處理大量元素時的效能問題。

Arrays.asList("a1", "a2", "a3")
        .stream()
        .findFirst()
        .parallel()
        .ifPresent(System.out::println);
--------------------------------------------------------------------
Arrays.asList("a1", "a2", "a3")
        .parallelStream()
        .findFirst()
        .ifPresent(System.out::println);

Stream API 可以宣告性的通過parallel()與sequential()在並行流與序列流之間進行切換。
在一個序列流中使用parallel()切換為並行流,在一個並行流中使用sequential()切換為序列流。

Java8對並行流有了更好的支援
並行流使用公共的 ForkJoinPool ,由 ForkJoinPool.commonPool() 方法提供。底層執行緒池的大小最大為五個執行緒 – 取決於CPU的物理核數。

Fork/Join框架:在必要的情況下,講一個大任務,進行拆分(fork)成若干個小任務(拆分到不可拆分時),再將一個個小人物運算的結果進行彙總(join)。
這裡寫圖片描述

採用“工作竊取”演算法(work-stealing),Java8之前這種框架使用的並不多,主要是因為這種方法實現起來過於麻煩,而Java8將實現的方法變得非常簡單。

“工作竊取”演算法:演算法是指某個執行緒從其他佇列裡竊取任務來執行

對於常見的一個大型任務,我們可以把這個大的任務切割成很多個小任務,然後這些小任務會放在不同的佇列中,每一個佇列都有一個相應的的工作執行執行緒來執行,當一個執行緒所需要執行的佇列中,任務執行完之後,這個執行緒就會被閒置,為了提高執行緒的利用率,這些空閒的執行緒可以從其他的任務佇列中竊取一些任務,來避免使自身資源浪費,這種在自己執行緒閒置,同時竊取其他任務佇列中任務執行的演算法,就是工作竊取演算法

6、Optional類

Optional類是一個容器類,代表一個值存在或不存在,可以避免空指標異常。Optional不是一個函式式介面,而是一個精巧的工具介面,用來方式NullPointException異常。Optional是一個簡單的值容器,這個值可以是null,也可以是non-null,為了不直接返回null,我們在Java 8中就返回一個Optional類,使用Optional類可以避免使用if進行null檢查,這種檢查的過程交由底層自動處理。主要有如下一些方法:

of(T t) : 建立一個Optional例項
empty() : 建立一個空的Optional例項
ofNullable(T t) :  若t不為null,建立Optional例項,否則建立空的Optional例項
isPresent() : 判斷是否包含值
orElse(T t) : 如果呼叫物件包含值,返回該值,否則返回t
orElseGet(Suppplier s) : 如果呼叫物件包含值,返回該值,否則返回s獲取的值
map(Function f) : 如果有值對其處理,並返回處理後的Optional,否則返回Optional.empty()
flatMap(Function mapper) : 與map類似,要求返回值必須是Optional

7、JAVA8全新的時間包

Java 8 包含了全新的時間日期API,這些功能都放在了java.time包下。主要有如下一些方法:

LocalDate,LocalTime,LocalDateTime : 時間
Instant : 時間戳(以Unix元年:19701月一日 00:00:00到某個時間時間的毫秒值)
Duration : 計算兩個“時間”之間的間隔
Period : 計算兩個“日期”之間的間隔
TemporalAdjust :時間校正器
DateTimeFormatter :格式化時間/日期
ZonedDate,ZonedTime,ZonedDateTime : 時區