JAVA8你只需要知道這些(2)

weixin_34320159發表於2017-06-16

前言

上篇文章我們講了JAVA8中介面和匿名內部類的一些新特性。這篇文章我們將繼續深入的研究JAVA8。

1.Function包

在JAVA8中引入了java.util.function包,這個包裡面全是介面,其中有四個介面值得我們注意:
1.Function 功能型介面

@FunctionalInterface
public interface Function<T, R> {
 R apply(T t);
}

需要注意的是@FunctionalInterface註解說明這是一個函式式介面,函式式介面中只能包含一個抽象方法,如果多於一個會報錯,但是可以有預設方法,靜態方法。
從Function介面來看,該介面接收一個引數返回一個引數。

2.Consumer 消費型介面

@FunctionalInterface
public interface Consumer<T> {
  void accept(T t);
}

Consumer介面接收一個引數,但是沒有返回值。

3.Supplier 供給型介面

@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}

Supplier介面不接收引數,但是有返回值。

4.Predicate 斷言型介面

@FunctionalInterface
public interface Predicate<T> {
   boolean test(T t);
}

Predicate介面接收一個引數,返回一個boolean型別。
  知道這4個介面有什麼用呢?這4個介面是我們接下來要講的stream的基礎,在stream中,有大量的方法用到了以上的介面。

2.Stream

Stream API真正的把函數語言程式設計風格引入了JAVA。Stream主要用來對資料進行處理,尤其是對集合資料的處理。Stream Api對應的是java.util.stream包,在這個包下面有一個Stream介面,是我們主要研究的物件。
我們先來看看建立Stream的幾種方式:

//1.使用Stream介面中的方法
Stream stream = Stream.of("1", "2", "3");
// 2. 使用Arrays.stream方法
String [] strs = new String[] {"1", "2", "3"};
stream = Arrays.stream(strs );
// 3. 使用Collection介面中的stream方法
List<String> list = Arrays.asList(strs );
stream = list.stream();

但是不論是用哪一種方式建立Stream,其內部都是通過以下程式碼實現:

 public static <T> Stream<T> stream(Spliterator<T> spliterator, boolean parallel) {
        Objects.requireNonNull(spliterator);
        return new ReferencePipeline.Head<>(spliterator,
                                            StreamOpFlag.fromCharacteristics(spliterator),
                                            parallel);
    }

知道如何建立Stream以後,我們就要開始使用它:

String [] strs = new String[] {"java", "android", "ios"};
Arrays.stream(strs).filter((x)-> x.length()>3).forEach(System.out::println);

以上程式碼很簡單,首先建立了一個String陣列,然後通過Arrays.stream建立一個Stream,再通過filter方法把長度大於3的字串過濾出來,最後通過System.out::println輸出到控制檯。
到這裡就不得不提到Stream的操作了:


1870458-18acfd97299014db.png
Stream操作.png

  Stream操作分為中間操作和終端操作,中間操作就比如上面的filter方法,終端操作就比如上面的forEach方法。Stream的中間操作會惰性執行,只有執行到終端操作了才會立即去處理。這樣的好處就是可以提升效能。
  中間操作又分為有狀態和無狀態兩種,有狀態的操作必須等到所有元素處理完之後才知道最終結果,比如排序操作,在讀取完所有元素前,就不能確定排序的結果。而無狀態的操作中,元素的處理不受前面元素的影響,比如filter操作就是無狀態的,當前元素的操作不會受到前面元素的影響。
  終端操作也分為非短路操作和短路操作,短路操作即不用處理全部元素就可以返回結果,比如anyMatch方法,找到第一個滿足條件的元素就返回。非短路操作就是要處理完全部元素才可以返回結果。

中間操作包括:map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered

終端操作包括:forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator

3.方法引用

可能細心的朋友已經發現了,在以上我們的程式碼中使用了System.out::println而不是System.out.prinlnt。這種語法我們以前似乎沒有見過?對,它叫方法引用,這也是JAVA8中的新特性,我們來看看它的語法:
1.引用靜態方法: 類名稱::靜態方法名
2.引用普通方法:  例項化物件名::普通方法名
3.引用特定類方法:  特定類::普通方法名
4.引用構造方法:  類名稱::new
  你可能會說,不就是把以前的方法呼叫由.變成了::麼?有什麼可稀奇的?方法引用跟以前方法呼叫最大的區別在於,你可以用另外一個方法來引用該方法,說通俗一點就是可以給方法取一個別名,例如:

範例:把Int型轉換為String

@FunctionalInterface
interface Action<P,R>{
    R convert(P p);
}

public static void main(String[]args){
        Action<Integer,String> a=String::valueOf;
        String str=a.convert(123);
        System.out.println(str);
}

在上面程式碼中,我們首先定義了函式式介面,關於函式式介面的定義我們上篇文章已經說過了,要是忘記了可以再回上篇文章看看。Action介面中有convert方法,接收一個引數,返回一個值。這跟我們String類中的valueOf方法一樣,valueOf方法也是傳入一個引數,返回一個值:

public static String valueOf(int i)

所以我們就可以用convert來引用valueOf方法,可以看見我們的convert方法是沒有被實現的,他的功能完全取決於valueOf方法。並且因為valueOf方法是靜態的,所以我們按照方法引用的語法,可以用類名::靜態方法名直接引用。

範例:把小寫字串轉換為大寫

@FunctionalInterface
interface Action<R>{
    R convert();
}
public static void main(String[]args){
        Action<String> a="abc"::toUpperCase;
        String str=a.convert();
        System.out.println(str);
}

在上面程式碼中,我們通過方法引用把小寫的abc,轉換為大寫的ABC,在使用方法引用的時候,我們用了"abc"這就是String類的一個例項,而toUpperCase的定義是一個普通方法:

public String toUpperCase()

範例:比較兩個字元的大小

@FunctionalInterface
interface Action<P>{
    int  convert(P p1,P p2);
}
public static void main(String[]args){
        Action<String> a=String::compareTo;
        System.out.println(a.convert("a","b"));
}

在以上程式碼中我們引用了compareTo方法來比較兩個字串的大小。但是我們來看該方法的定義:

public int compareTo(String anotherString)

它竟然不是靜態方法,我們卻用類名直接引用,這是為什麼?正常情況下,類名::方法名,這個方法肯定是靜態方法。但是為什麼這裡普通方法也可以呢?這就要分析一下compareTo這個方法了,這個方法是兩個字串進行對比,正常情況下的寫法應該是"a".compareTo("b"),對應的方法引用應該是"a"::compareTo。
如果是這樣,我們定義函式式介面的時候,就應該定義成以下這樣:

@FunctionalInterface
interface Action<P>{
    int  convert(P p1);
}
    
    public static void main(String[]args){
        Action<String> a="a"::compareTo;
        System.out.println(a.convert("b"));
}

如果介面定義成這樣,就可以使用"a"::compareTo這種形式的方法引用。到這裡聰明的你應該明白,為什麼在上面可以通過類名引用普通方法了吧?我們想想在String類裡面除了compareTo還有其他方法沒?很多是不是?equals,endsWith都屬於,所以他們都可以通過把方法引數定義為兩個,然後用類名::方法名。

class ShopCar{
    private String name;
    private double price;
    private int amount;
    public ShopCar(String name,double price,int amount){
        this.name=name;
        this.price=price;
        this.amount=amount;
    }
    
    public int getAmount() {
        return amount;
    }
    
    public String getName() {
        return name;
    }
    
    public double getPrice() {
        return price;
    }
    
    @Override
    public String toString() {
        // TODO Auto-generated method stub
        return "商品名:"+this.name+" 價格:"+this.price+" 數量:"+this.amount;
    }
}

@FunctionalInterface
interface Action<C>{
    C  create(String name,double price,int amount);
}

public static void main(String[]args){
        Action<ShopCar> a=ShopCar :: new;
        ShopCar car=a.create("蘋果",5,2);
        System.out.println(car);
}

以上程式碼演示瞭如何通過類名稱::new 來構造一個ShopCar物件。

結語:
  今天的文章主要講了JAVA8新引入的兩個包和方法引用,其中Function包是基礎,大家一定要多多理解。Stream包是函數語言程式設計風格的主要提現,下一篇文章我們會著重介紹Stream介面中的方法。
如果你覺得本篇文章幫助到了你,希望大爺能夠打賞。
本文為原創文章,轉載請註明出處!

相關文章