1. 程式人生 > 程式設計 >JDK1.8新特性(超詳細)

JDK1.8新特性(超詳細)

Java函式式設計

實現方法:

  • @FunctionalInterface介面
  • Lambda語法
  • 方法引用
  • 介面default方法實現

一、lambda表示式

lambda表示式為匿名內部類的簡寫,類似於匿名內部類的語法糖;但又區別於匿名內部類(後文會講解)。

匿名內部類特點:

  • 基於多型(多數基於介面程式設計)
  • 實現類無需名稱
  • 允許多個抽象方法

Lambda的語法簡潔,沒有面向物件複雜的束縛。 特點:

  1. 使用Lambda必須有介面,並且介面中有且僅有一個抽象方法。 只有當介面中的抽象方法存在且唯一時,才可以使用Lambda,但排除介面預設方法以及宣告中覆蓋Object的公開方法。
  2. 使用Lambda必須具有上下文推斷
    。 也就是方法的引數或區域性變數型別必須為Lambda對應的介面型別,才能使用Lambda作為該介面的例項。

備註:有且僅有一個抽象方法的介面,稱為“函式式介面”。

標準格式由三部分組成:

  • 一些引數
  • 一個箭頭
  • 一段程式碼 格式:
(引數列表)->{一些重要方法的程式碼};
():介面中抽象方法的引數列表,沒有引數,就空著;有引數就寫出引數,多個引數用逗號分隔。
->:傳遞:把引數傳遞給方法體{}
{}:重寫介面的抽象方法的方法體
複製程式碼

箭頭操作符的左側對應介面中引數列表(lambda表示式的引數列表), 箭頭右側為該抽象方法的實現(lambda表示式所需執行的功能)。

lambda優化寫法: 可以推導的,都可以省略(凡是能根據上下文推匯出來的內容,都可以省略不寫):

(引數列表):括號中引數列表的資料型別,可以省略不寫
(引數列表):括號中的引數只有一個,那麼型別和()都可以省略
 {一些程式碼} :如果{}中的程式碼只有一行,無論是否有返回值,都可以省略({},return,分號)
複製程式碼

注意:要省略{},return,分號 必須一起省略

public class MyLambda {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
public void run() { System.out.println(Thread.currentThread().getName()+"新執行緒建立了"); } }).start(); //使用Lambda new Thread(()->{ System.out.println(Thread.currentThread().getName()+"新執行緒建立了"); }).start(); //優化lambda new Thread(()->System.out.println(Thread.currentThread().getName()+"新執行緒建立了")).start(); } } 複製程式碼

1.1無引數,無返回

()->System.out.println("hello lambda")

Cook:

public interface Cook {
    public abstract void makeFood();
}
複製程式碼

Demo01Cook:

public class Demo01Cook {

    public static void main(String[] args) {
        invokeCook(new Cook() {
            public void makeFood() {
                System.out.println("做飯。。。");
            }
        });
        //使用Lambda
        invokeCook(()->{
            System.out.println("做飯。。。");
        });

        //優化lambda
        invokeCook(()-> System.out.println("做飯。。。"));
    }

    public static void invokeCook(Cook cook){
        cook.makeFood();
    }
}
複製程式碼

1.2 有引數,無返回

x->System.out.println("hello lambda")

import java.util.function.Consumer;

/**
 * Created by hongcaixia on 2019/10/31.
 */
public class Demo2 {

    public static void main(String[] args) {
        Consumer<String> consumer = x-> System.out.println(x);
        consumer.accept("有引數無返回");
    }
}
複製程式碼

1.3 有引數,有返回

(Person p1,Person p2)->{ return p1.getAge()-p2.getAge(); }

package com.hcx.lambda;

import java.util.Arrays;
import java.util.Comparator;

/**
 * Created by hongcaixia on 2019/10/26.
 */
public class MyArrays {

    public static void main(String[] args) {
        Person[] arr = {new Person("陳奕迅",40),new Person("鍾漢良",39),new Person("楊千嬅",38)};

        //對年齡進行排序
        Arrays.sort(arr,new Comparator<Person>() {
            @Override
            public int compare(Person o1,Person o2) {
                return o1.getAge()-o2.getAge();
            }
        });

        //使用lambda
        Arrays.sort(arr,(Person p1,Person p2)->{
            return p1.getAge()-p2.getAge();
        });

        //優化lambda
        Arrays.sort(arr,(p1,p2)->p1.getAge()-p2.getAge());

        Arrays.sort(arr,Comparator.comparing(Person::getAge));

        for (Person p:arr){
            System.out.println(p);
        }
    }
}
複製程式碼

二、函式式介面

2.1概念

函式式介面:有且僅有一個抽象方法的介面

函式式介面,即適用於函式語言程式設計場景的介面。而Java中的函式語言程式設計體現就是Lambda,所以函式式介面就是可以適用於Lambda使用的介面。只有確保介面中有且僅有一個抽象方法,Java中的Lambda才能順利地進行推導。

2.2格式

修飾符 interface 介面名稱 {
    public abstract 返回值型別 方法名稱(可選引數資訊);
    // 其他非抽象方法內容
}
複製程式碼

抽象方法的 public abstract 是可以省略的:

public interface MyFunctionalInterface {   
    void myMethod();    
}
複製程式碼

2.3@FunctionalInterface註解

與 @Override 註解的作用類似,Java 8中專門為函式式介面引入了一個新的註解: @FunctionalInterface 。用於函式式介面型別宣告的資訊註解型別,這些介面的例項被Lambda表示式、方法引用或構造器引用建立。函式式介面只能有一個抽象方法,但排除介面預設方法以及宣告中覆蓋Object的公開方法: @FunctionalInterface註解原始碼註釋:

image.png

object下的public方法.png

原始碼註釋:該註解是定義一個lambda表示式的基礎, 即是否是函式式介面可以標註也可以不標註。 函式式介面必須有一個精確的抽象方法,但排除以下兩種: ①java8的default,他們不屬於抽象方法。 ②如果該介面宣告的一個抽象方法覆蓋了任意一個object的方法,也排除掉。

@FunctionalInterface
public interface MyFunctionInterface {
    //唯一個抽象方法
    void method();
    //排除用default修飾的方法
    default void method1(){
    }
    //排除Ojbect下的方法
    int hashCode();
}
複製程式碼
@FunctionalInterface
public interface MyFunctionalInterface {
	void myMethod();    
}
複製程式碼

注意:@FuncationlInterface不能標註在註解、類以及列舉上。一旦使用該註解來定義介面,編譯器將會強制檢查該介面是否確實有且僅有一個抽象方法,否則將會報錯。需要注意的是,即使不使用該註解,只要滿足函式式介面的定義,這仍然是一個函式式介面,使用起來都一樣。

MyFunctionalInterface:

@FunctionalInterface
public interface MyFunctionalInterface {
    //定義一個抽象方法
    public abstract void method();
}
複製程式碼

MyFunctionalInterfaceImpl:

public class MyFunctionalInterfaceImpl implements MyFunctionalInterface{
    @Override
    public void method() {

    }
}
複製程式碼

Demo:

/*
    函式式介面的使用:一般可以作為方法的引數和返回值型別
 */
public class Demo {
    //定義一個方法,引數使用函式式介面MyFunctionalInterface
    public static void show(MyFunctionalInterface myInter){
        myInter.method();
    }

    public static void main(String[] args) {
        //呼叫show方法,方法的引數是一個介面,所以可以傳遞介面的實現類物件
        show(new MyFunctionalInterfaceImpl());

        //呼叫show方法,所以我們可以傳遞介面的匿名內部類
        show(new MyFunctionalInterface() {
            @Override
            public void method() {
                System.out.println("使用匿名內部類重寫介面中的抽象方法");
            }
        });

        //呼叫show方法,方法的引數是一個函式式介面,所以我們可以Lambda表示式
        show(()->{
            System.out.println("使用Lambda表示式重寫介面中的抽象方法");
        });

        //簡化Lambda表示式
        show(()-> System.out.println("使用Lambda表示式重寫介面中的抽象方法"));
    }
}
複製程式碼

案例: 分析:函式式介面和普通方法的差異

import java.util.function.Supplier;

/**
 * Created by hongcaixia on 2019/11/3.
 */
public class MyTest {
    public static void main(String[] args) {
        print1("hello,world");
        print2(()->"hello world");
    }

    public static void print1(String message){
        System.out.println(message);
    }

    public static void print2(Supplier<String> message){
        System.out.println(message.get());
    }
}
複製程式碼

以上程式碼會得到同樣的結果,但使用了函式式介面相當於把資料進行了延遲載入。使用函式式介面,資料並沒有完全確定,等到真正呼叫的時候才確定,類似推模型。

Demo01Logger:

public class Demo01Logger {
    //定義一個根據日誌的級別,顯示日誌資訊的方法
    public static void showLog(int level,String message){
        //對日誌的等級進行判斷,如果是1級別,那麼輸出日誌資訊
        if(level==1){
            System.out.println(message);
        }
    }

    public static void main(String[] args) {
        //定義三個日誌資訊
        String msg1 = "Hello";
        String msg2 = "World";
        String msg3 = "Java";

        //呼叫showLog方法,傳遞日誌級別和日誌資訊
        showLog(2,msg1+msg2+msg3);

    }
}
複製程式碼

Demo02Lambda:

/*
    使用Lambda優化日誌案例
    Lambda的特點:延遲載入
    Lambda的使用前提,必須存在函式式介面
 */
public class Demo02Lambda {
    //定義一個顯示日誌的方法,方法的引數傳遞日誌的等級和MessageBuilder介面
    public static void showLog(int level,MessageBuilder mb){
        //對日誌的等級進行判斷,如果是1級,則呼叫MessageBuilder介面中的builderMessage方法
        if(level==1){
            System.out.println(mb.builderMessage());
        }
    }

    public static void main(String[] args) {
        //定義三個日誌資訊
        String msg1 = "Hello";
        String msg2 = "World";
        String msg3 = "Java";

        //呼叫showLog方法,引數MessageBuilder是一個函式式介面,所以可以傳遞Lambda表示式
        /*showLog(2,()->{
            //返回一個拼接好的字串
            return  msg1+msg2+msg3;
        });*/

        showLog(1,()->{
            System.out.println("不滿足條件不執行");
            //返回一個拼接好的字串
            return  msg1+msg2+msg3;
        });
    }
}
複製程式碼

MessageBuilder:

@FunctionalInterface
public interface MessageBuilder {
    //定義一個拼接訊息的抽象方法,返回被拼接的訊息
    public abstract String builderMessage();
}
複製程式碼

分析:使用Lambda表示式作為引數傳遞,僅僅是把引數傳遞到showLog方法中,只有滿足條件,日誌的等級是1級才會呼叫介面MessageBuilder中的方法builderMessage,才會進行字串的拼接; 如果條件不滿足,日誌的等級不是1級,那麼MessageBuilder介面中的方法builderMessage也不會執行,所以拼接字串的程式碼也不會執行,所以不會存在效能的浪費

2.4使用函式式介面作為方法的引數

Demo01Runnable:

public class Demo01Runnable {
    //定義一個方法startThread,方法的引數使用函式式介面Runnable
    public static void startThread(Runnable run){
        //開啟多執行緒
        new Thread(run).start();
    }

    public static void main(String[] args) {
        //呼叫startThread方法,那麼我們可以傳遞這個介面的匿名內部類
        startThread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"-->"+"執行緒啟動了");
            }
        });

        //呼叫startThread方法,所以可以傳遞Lambda表示式
        startThread(()->{
            System.out.println(Thread.currentThread().getName()+"-->"+"執行緒啟動了");
        });

        //優化Lambda表示式
        startThread(()->System.out.println(Thread.currentThread().getName()+"-->"+"執行緒啟動了"));
    }
}

複製程式碼

2.5使用函式式介面作為方法的返回值

Demo02Comparator:

import java.util.Arrays;
import java.util.Comparator;

/*
    如果一個方法的返回值型別是一個函式式介面,那麼就可以直接返回一個Lambda表示式。
    當需要通過一個方法來獲取一個java.util.Comparator介面型別的物件作為排序器時,就可以調該方法獲取。
 */
public class Demo02Comparator {
    //定義一個方法,方法的返回值型別使用函式式介面Comparator
    public static Comparator<String> getComparator(){
        //方法的返回值型別是一個介面,那麼我們可以返回這個介面的匿名內部類
        /*return new Comparator<String>() {
            @Override
            public int compare(String o1,String o2) {
                //按照字串的降序排序
                return o2.length()-o1.length();
            }
        };*/

        //方法的返回值型別是一個函式式介面,所以我們可以返回一個Lambda表示式
        /*return (String o1,String o2)->{
            //按照字串的降序排序
            return o2.length()-o1.length();
        };*/

        //繼續優化Lambda表示式
        return (o1,o2)->o2.length()-o1.length();
    }

    public static void main(String[] args) {
        //建立一個字串陣列
        String[] arr = {"a","bb","ccc","dddd"};
        //輸出排序前的陣列
        System.out.println(Arrays.toString(arr));
        //呼叫Arrays中的sort方法,對字串陣列進行排序
        Arrays.sort(arr,getComparator());
        //輸出排序後的陣列
        System.out.println(Arrays.toString(arr));
    }

}
複製程式碼

每次都要宣告一個介面,寫一個抽象方法,然後再用這個介面作為引數去用lambda實現。。。當果不需要!這個新特性就是為了我們使用簡單的呀,所以java已經內建了一堆函式式介面了。

先來個表格整體概覽一下常用的一些:

函式式介面 引數型別 返回型別 用途
Supplier 供給型 T 返回型別為T的物件,方法:T get()
Consumer消費型 T void 對型別為T的物件應用操作,方法:void accept(T t)
Predicate 斷定型 T boolean 確定型別為T的物件是否滿足某種約束,返回布林值,方法:boolean test(T t)
Function<T,R>函式型 T R 對型別為T的物件應用操作,並返回R型別的物件,方法:R apply(T,t)

2.6常用函式式介面

①提供型別:Supplier介面

特點:只出不進,作為方法/構造引數、方法返回值 java.util.function.Supplier介面僅包含一個無參的方法:T get()。 用來獲取一個泛型引數指定型別的物件資料。

Supplier介面被稱之為生產型介面,指定介面的泛型是什麼型別,那麼介面中的get方法就會生產什麼型別的資料

import java.util.function.Supplier;

/**
 * Created by hongcaixia on 2019/10/29.
 */
public class MySupplier {

    public static String getString(Supplier<String> supplier){
        return supplier.get();
    }

    public static void main(String[] args) {
        getString(new Supplier<String>() {
            @Override
            public String get() {
                return null;
            }
        });

        String s = getString(()->"Eason");
        System.out.println(s);
    }
}
複製程式碼

GetMax:

import java.util.function.Supplier;

/**
 * Created by hongcaixia on 2019/10/29.
 */
public class GetMax {

    public static int getMaxNum(Supplier<Integer> supplier){
        return supplier.get();
    }

    public static void main(String[] args) {
        int[] arr = {-1,0,1,2,3};
        int maxValue = getMaxNum(()->{
            int max = arr[0];
            for(int i=0;i<arr.length;i++){
                if(arr[i]>max){
                    max = arr[i];
                }
            }
            return max;
        });
        System.out.println("陣列元素的最大值是:"+maxValue);
    }

}
複製程式碼
②消費型別:Consumer介面

特點:只進不出,作為方法/構造引數 java.util.function.Consumer介面則正好與Supplier介面相反, 它不是生產一個資料,而是消費一個資料,其資料型別由泛型決定。 Consumer介面中包含抽象方法void accept(T t),意為消費一個指定泛型的資料。

Consumer介面是一個消費型介面,泛型執行什麼型別,就可以使用accept方法消費什麼型別的資料 至於具體怎麼消費(使用),需要自定義

import java.util.function.Consumer;

/**
 * Created by hongcaixia on 2019/10/29.
 */
public class MyConsumer {

    public static void method(String name,Consumer<String> consumer){
        consumer.accept(name);
    }

    public static void main(String[] args) {
        method("小哇",new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println("s是:"+s);
            }
        });

        method("小哇",(name)->{
            String s = new StringBuffer(name).reverse().toString();
            System.out.println("s是:"+s);
        });
    }
}
複製程式碼

andThen: Consumer介面的預設方法andThen 作用:需要兩個Consumer介面,可以把兩個Consumer介面組合到一起,在對資料進行消費

import java.util.function.Consumer;

/**
 * Created by hongcaixia on 2019/10/30.
 */
public class AndThen {

    public static void method(String s,Consumer<String> consumer1,Consumer<String> consumer2){
//        consumer1.accept(s);
//        consumer2.accept(s);
        //使用andThen方法,把兩個Consumer介面連線到一起,在消費資料
        //con1連線con2,先執行con1消費資料,在執行con2消費資料
        consumer1.andThen(consumer2).accept(s);
    }

    public static void main(String[] args) {
        method("Hello",(t)-> System.out.println(t.toUpperCase()),//消費方式:把字串轉換為大寫輸出
                (t)-> System.out.println(t.toLowerCase()));//消費方式:把字串轉換為小寫輸出


        method("Hello",new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s.toUpperCase());
            }},new Consumer<String>() {
                @Override
                public void accept(String s1) {
                    System.out.println(s1.toUpperCase());
                }
        });
    }
}

複製程式碼

按照格式“姓名:XX。性別:XX。”的格式將資訊列印 要求將列印姓名的動作作為第一個Consumer介面的Lambda例項, 將列印性別的動作作為第二個Consumer介面的Lambda例項, 將兩個Consumer介面按照順序“拼接”到一起。

public class DemoTest {
    //定義一個方法,引數傳遞String型別的陣列和兩個Consumer介面,泛型使用String
    public static void printInfo(String[] arr,Consumer<String> con1,Consumer<String> con2){
        //遍歷字串陣列
        for (String message : arr) {
            //使用andThen方法連線兩個Consumer介面,消費字串
            con1.andThen(con2).accept(message);
        }
    }

    public static void main(String[] args) {
        //定義一個字串型別的陣列
        String[] arr = { "陳奕迅,男","鍾漢良,"胡歌,男" };

        //呼叫printInfo方法,傳遞一個字串陣列,和兩個Lambda表示式
        printInfo(arr,(message)->{
            //消費方式:對message進行切割,獲取姓名,按照指定的格式輸出
            String name = message.split(",")[0];
            System.out.print("姓名: "+name);
        },獲取年齡,按照指定的格式輸出
            String age = message.split(",")[1];
            System.out.println(";年齡: "+age+"。");
        });
    }
}
複製程式碼
③斷定型別:Predicate介面

特點:boolean型別判斷,作為方法/構造引數 java.util.function.Predicate介面 作用:對某種資料型別的資料進行判斷,結果返回一個boolean值

Predicate介面中包含一個抽象方法: boolean test(T t):用來對指定資料型別資料進行判斷的方法 結果: 符合條件,返回true 不符合條件,返回false

import java.util.function.Predicate;

/**
 * Created by hongcaixia on 2019/10/30.
 */
public class MyPredicate1 {

    public static boolean validateStr(String str,Predicate<String> predicate){
        return predicate.test(str);
    }

    public static void main(String[] args) {
        String str = "abcdef";
        boolean b = validateStr(str,string->str.length()>5);
        System.out.println(b);


        boolean b1 = validateStr(str,new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s.length()>5;
            }
        });

        System.out.println(b1);
    }

}
複製程式碼

and方法: Predicate介面中有一個方法and,表示並且關係,也可以用於連線兩個判斷條件

default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> this.test(t) && other.test(t);
}
複製程式碼

MyPredicateAnd :

import java.util.function.Predicate;

/**
 * Created by hongcaixia on 2019/10/30.
 */
public class MyPredicateAnd {

    public static boolean validateStr(String str,Predicate<String> pre1,Predicate<String> pre2){
//        return pre1.test(str) && pre2.test(str);
        return pre1.and(pre2).test(str);
    }

    public static void main(String[] args) {
        String s = "abcdef";
        boolean b = validateStr(s,str->str.length()>5,str->str.contains("a"));
        System.out.println(b);
    }
}
複製程式碼

or方法: Predicate介面中有一個方法or,表示或者關係,也可以用於連線兩個判斷條件

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }
複製程式碼

MyPredicateOr:

package com.hcx.lambda;

import java.util.function.Predicate;

/**
 * Created by hongcaixia on 2019/10/30.
 */
public class MyPredicateOr {
    public static boolean validateStr(String s,Predicate<String> pre2){
//        return pre1.test(s) || pre2.test(s);
        return pre1.or(pre2).test(s);
    }

    public static void main(String[] args) {
        String s = "acdef";
        boolean b = validateStr(s,str->str.contains("a"));

        validateStr(s,new Predicate<String>() {
            @Override
            public boolean test(String str) {
                return s.length()>5;
            }
        },new Predicate<String>() {
            @Override
            public boolean test(String str) {
                return s.contains("a");
            }
        });

        System.out.println(b);
    }
}

複製程式碼

negate方法: Predicate介面中有一個方法negate,也表示取反的意思

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }
複製程式碼

MyPredicateNegate:

import java.util.function.Predicate;

/**
 * Created by hongcaixia on 2019/10/30.
 */
public class MyPredicateNegate {

    public static boolean validateStr(String s,Predicate<String> pre){
//        return !pre.test(s);
        return pre.negate().test(s);
    }

    public static void main(String[] args) {
        String s = "acde";
        boolean b = validateStr(s,str->str.length()>5);

        System.out.println(b);
    }
}
複製程式碼

MyTest:

package com.hcx.lambda;

import java.util.ArrayList;
import java.util.function.Predicate;

/**
 * Created by hongcaixia on 2019/10/30.
 */
public class MyTest {

    public static ArrayList<String> filter(String[] arr,Predicate<String> pre2) {
        ArrayList<String> list = new ArrayList<>();
        for (String s : arr) {
            boolean b = pre1.and(pre2).test(s);
            if (b) {
                list.add(s);
            }
        }
        return list;
    }

    public static void main(String[] args) {
        String[] array = {"迪麗熱巴,女","古力娜扎,"佟麗婭,"趙麗穎,女"};
        ArrayList<String> list = filter(array,s -> s.split(",")[1].equals("女"),")[0].length() == 4);
        for(String s : list){
            System.out.println(s);
        }
    }
}

複製程式碼
④轉換型別:Function介面

特點:有輸入,有輸出 java.util.function.Function<T,R>介面用來根據一個型別的資料得到另一個型別的資料, 前者稱為前置條件,後者稱為後置條件。

Function介面中最主要的抽象方法為:R apply(T t),根據型別T的引數獲取型別R的結果。 使用的場景例如:將String型別轉換為Integer型別。

package com.hcx.lambda;

import java.util.function.Function;

/**
 * Created by hongcaixia on 2019/10/30.
 */
public class MyFunction {

    public static void change(String str,Function<String,Integer> function){
//        Integer i = function.apply(str);
        //自動拆箱 Integer自動轉為int
        int i = function.apply(str);
        System.out.println(i);
    }

    public static void main(String[] args) {
        String s = "1234";
        change(s,str->Integer.parseInt(str));

        int i = Integer.parseInt(s);
        System.out.println(i);
    }
}
複製程式碼

andThen方法:

package com.hcx.lambda;

import java.util.function.Function;

/**
 * 1.String轉Integer,加10
 *      Function<String,Integer> fun1 :Integer i = fun1.apply("123")+10;
 * 2.Interger轉String
 *      Function<Integer,String> fun2 :String s = fun2.apply(i);
 * Created by hongcaixia on 2019/10/31.
 */
public class MyFunctionTest {

    public static void change(String str,Integer> fun1,Function<Integer,String> fun2){
        String string = fun1.andThen(fun2).apply(str);
        System.out.println(string);
    }

    public static void main(String[] args) {
        change("123",str->Integer.parseInt(str)+10,i->i+"");
    }
}
複製程式碼

自定義函式模型拼接Demo:

package com.hcx.lambda;

import java.util.function.Function;

/**
 * String str = "趙麗穎,20";
 * 1.將字串擷取數字年齡部分,得到字串;
 * 2.將上一步的字串轉換成為int型別的數字;
 * 3.將上一步的int數字累加100,得到結果int數字。
 * Created by hongcaixia on 2019/10/31.
 */
public class MyFunctionTest2 {

    public static int change(String str,String> fun1,Integer> fun2,Integer> fun3){
        return fun1.andThen(fun2).andThen(fun3).apply(str);
    }

    public static void main(String[] args) {
        int num = change("趙麗穎,32",str->str.split(",")[1],str->Integer.parseInt(str),i->i+100);
        System.out.println(num);
    }
}
複製程式碼

注意:使用匿名內部類會編譯後會多產生一個類,而使用lambda,底層是invokedynamic指令,不會有多餘的類

三、方法引用

若lambda體中的內容,有方法已經實現了,則可以使用方法引用。方法引用是對lambda的簡化

MyPrintable:

public interface MyPrintable {
    void print(String str);
}
複製程式碼

DemoPrint:

public class DemoPrint {

    private static void printString(MyPrintable data){
        data.print("Hello,World");
    }

    public static void main(String[] args) {
        printString(s->System.out.println(s));

        printString(new MyPrintable() {
            @Override
            public void print(String str) {
                System.out.println(str);
            }
        });
    }
}
複製程式碼

改進:

public class DemoPrint {

    private static void printString(MyPrintable data){
        data.print("Hello,World");
    }

    public static void main(String[] args) {
        //printString(s->System.out.println(s));
        printString(System.out::println);
    }
}
複製程式碼

方法引用

雙冒號::為引用運運算元,而它所在的表示式被稱為方法引用。 如果Lambda要表達的函式方案已經存在於某個方法的實現中,那麼則可以通過雙冒號來引用該方法作為Lambda的替代者。

三種格式:

  • 物件::例項方法名
  • 類::靜態方法名
  • 類::例項方法名

分析

  • Lambda表示式寫法: s -> System.out.println(s);
  • 方法引用寫法: System.out::println

以上兩種寫法完全等效: 第一種:拿到引數之後通過Lambda傳遞給 System.out.println 方法去處理。 第二種:直接讓 System.out 中的 println 方法來取代Lambda。

注意:lambda體中呼叫的方法的引數列表和返回值型別要與函式式介面的抽象方法的引數列表與返回值型別一致。 Lambda 中 傳遞的引數一定是方法引用中的那個方法可以接收的型別,否則會丟擲異常

image.png

3.1 通過物件名引用成員方法

    @Test
    public void test(){
        Person person = new Person();
        Supplier<String> supplier =() -> person.getName();
        System.out.println(supplier.get());

        Supplier<Integer> supplier1 = person::getAge;
        System.out.println(supplier1.get());
    }
複製程式碼

MyPrintable:

public interface MyPrintable {
    void print(String str);
}
複製程式碼

MethodReadObj:

public class MethodReadObj {
    public void printUpperCaseString(String str){
        System.out.println(str.toUpperCase());
    }
}
複製程式碼

MethodReference1:

public class MethodReference1 {

    public static void printString(MyPrintable p){
        p.print("hello");
    }

    public static void main(String[] args) {
        printString(str-> {
            MethodReadObj methodReadObj = new MethodReadObj();
            methodReadObj.printUpperCaseString(str);
        });

        /**
         * 使用方法引用:
         * 1.MethodReadObj物件已經存在
         * 2.成員方法printUpperCaseString已經存在
         * 所以可以使用物件名引用成員方法
         */
        MethodReadObj methodReadObj = new MethodReadObj();
        printString(methodReadObj::printUpperCaseString);

    }
}
複製程式碼

3.2 通過類名稱引用靜態方法

類已經存在,靜態方法已經存在,則可以通過類名直接引用靜態成員方法

@Test
public void test1(){
    Comparator<Integer> comparator = (x,y)->Integer.compare(x,y);
    Comparator<Integer> comparator1 = Integer::compare;
}
複製程式碼

MyCalc:

public interface MyCalc {
    int calc(int num);
}
複製程式碼

MethodRerference2:

public class MethodRerference2 {

    public static int method(int num,MyCalc c){
        return c.calc(num);
    }

    public static void main(String[] args) {
        int number = method(-10,num -> Math.abs(num));

        int number1 = method(-10,Math::abs);
        System.out.println(number);
        System.out.println(number1);
    }
}
複製程式碼

通過類名引用例項方法

@Test
public void test2(){
    BiPredicate<String,String> biPredicate = (x,y) -> x.equals(y);
    BiPredicate<String,String> biPredicate1 = String::equals;
}
複製程式碼

注意:以上這種情況,需要滿足一定的條件:lambda表示式中第一個引數是lambda體中的呼叫者,第二個引數是lambda體中的引數

3.3 通過super引用成員方法

如果存在繼承關係,當Lambda中需要出現super呼叫時,也可以使用方法引用進行替代。 MyMeet :

@FunctionalInterface
public interface MyMeet {
    void meet();
}
複製程式碼

Parent:

public class Parent {
    public void hello(){
        System.out.println("hello,I'm Parent");
    }
}
複製程式碼

Child:

public class Child extends Parent{
    @Override
    public void hello() {
        System.out.println("hello,I'm Child");
    }

    public void method(MyMeet myMeet){
        myMeet.meet();
    }

    public void show(){
        method(()->{
            Parent parent = new Parent();
            parent.hello();
        });

        //使用super關鍵字呼叫父類
        method(()->super.hello());

        /**
         * 使用方法引用:使用super引用父類的成員方法:
         * 1.super已經存在
         * 2.父類的成員方法hello已經存在
         * 可以直接使用super引用父類的成員方法
         */
        method(super::hello);
    }

    public static void main(String[] args) {
        new Child().show();
    }
}

複製程式碼

3.4 通過this引用成員方法

this代表當前物件,如果需要引用的方法就是當前類中的成員方法,那麼可以使用 this::成員方法的格式來使用方法引用 MyWallet:

@FunctionalInterface
public interface MyWallet {
    void buy();
}
複製程式碼

BuyThing:

public class BuyThing {

    public void buyCar(){
        System.out.println("買了一輛別摸我");
    }

    public void getSalary(MyWallet myWallet){
        myWallet.buy();
    }

    public void method(){
        getSalary(()->this.buyCar());

        /**
         * 1.this已經存在
         * 2.本類的成員方法buyCar已經存在
         * 所以可以直接使用this引用本類的成員方法buyCar
         */
        getSalary(this::buyCar);

    }

    public static void main(String[] args) {
        new BuyThing().method();
    }
}
複製程式碼

3.5 類的構造器引用

由於構造器的名稱與類名完全一樣,並不固定。所以構造器引用使用 類名稱::new 的格式表示。

public void test3(){
    Supplier<Person> personSupplier = ()->new Person();
    //構造器引用 此處引用的是無參構造器 ,因為Supplier中的get方法沒有引數
    Supplier<Person> personSupplier1 = Person::new;
}

public void test4(){
    Function<Integer,Person> personFunction = (x)->new Person(x);
    //構造器引用 此處引用的是整型的一個引數的構造器 ,因為Function中的apply方法只有一個引數
    Function<Integer,Person> personFunction1 = Person::new;
}
複製程式碼

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

Person:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {
    private String name;
}
複製程式碼

PersonBuilder:

@FunctionalInterface
public interface PersonBuilder {
    //根據傳遞的姓名,建立Perosn物件
    Person builderPerson(String name);
}
複製程式碼

Demo:

public class Demo {

    //傳遞姓名和PersonBuilder介面,通過姓名建立Person物件
    public static void printName(String name,PersonBuilder personBuilder){
        Person person = personBuilder.builderPerson(name);
        System.out.println(person.getName());
    }

    public static void main(String[] args) {
        printName("hongcaixia",str->new Person(str));

        /**
         * 使用方法引用:
         * 1.構造方法new Person(String name)已知
         * 2.建立物件已知
         * 可以使用Person引用new建立物件
         */
        printName("hongcaixia",Person::new);
    }

}
複製程式碼

3.6 陣列的構造器引用

陣列也是 Object 的子類物件,所以同樣具有構造器,只是語法稍有不同。 格式:Type[]::new

public void test5() {
    Function<Integer,String[]> function = (x) -> new String[x];
    String[] strings = function.apply(10);

    Function<Integer,String[]> function1 = String[]::new;
    String[] strings1 = function1.apply(10);
}
複製程式碼

ArrayBuilder:

@FunctionalInterface
public interface ArrayBuilder {
    //建立int型別陣列的方法,引數傳遞陣列的長度,返回建立好的int型別陣列
    int[] builderArray(int length);
}
複製程式碼

DemoArrayBuilder:

public class DemoArrayBuilder {

    public static int[] createArray(int length,ArrayBuilder arrayBuilder){
        return arrayBuilder.builderArray(length);
    }

    public static void main(String[] args) {
        int[] arr1 = createArray(5,length -> new int[length]);

        System.out.println(arr1.length);

        /**
         * 1.已知建立的是int[]陣列
         * 2.建立的陣列長度也是已知的
         * 使用方法引用,int[]引用new,根據引數傳遞的長度建立陣列
         */
        int[] arr2 = createArray(10,int[]::new);
        System.out.println(Arrays.toString(arr2));
        System.out.println(arr2.length);
    }
}
複製程式碼

四、StreamAPI

流是資料渠道,用於操作資料來源(集合、陣列等)生成的元素序列。

package com.hcx.stream;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by hongcaixia on 2019/10/31.
 */
public class MyStream1 {

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("陳奕迅");
        list.add("陳小春");
        list.add("鍾漢良");
        list.add("陳七");
        list.add("陳偉霆");
        //篩選陳開頭,名字是三個字的
        List<String> chenList = new ArrayList<>();
        for(String item : list){
            if(item.startsWith("陳")){
                chenList.add(item);
            }
        }

        List<String> threeList = new ArrayList<>();
        for(String item : chenList){
            if(item.length()==3){
                threeList.add(item);
            }
        }

        //遍歷輸出符合條件的
        for(String item : threeList){
            System.out.println(item);
        }

        System.out.println("=====================");

        //使用Stream流
        list.stream().filter(str->str.startsWith("陳"))
                .filter(str->str.length()==3)
                .forEach(str-> System.out.println(str));
    }
}

複製程式碼

Stream(流)是一個來自資料來源的元素佇列

  • 元素是特定型別的物件,形成一個佇列。
  • 資料來源流的來源。 可以是集合,陣列等。
  • Stream自己不會儲存元素,而是按需計算。
  • Stream不會改變源物件,並且能返回一個持有結果的新流
  • Stream操作是延遲操作,意味著他們會等到需要結果的時候才執行

和以前的Collection操作不同, Stream操作還有兩個基礎的特徵:

  • Pipelining: 中間操作都會返回流物件本身。 這樣多個操作可以串聯成一個管道, 如同流式風格(fluent style)。 這樣做可以對操作進行優化, 比如延遲執行(laziness)和短路( short-circuiting)。
  • 內部迭代: 以前對集合遍歷都是通過Iterator或者增強for的方式,顯式的在集合外部進行迭代, 這叫做外部迭代。 Stream提供了內部迭代的方式,流可以直接呼叫遍歷方法。

Stream操作的三個步驟:

  • 建立Stream:一個資料來源(如集合、陣列),獲取一個流
  • 中間操作:一個操作鏈,對資料來源的資料進行處理
  • 終止操作:一個終止操作,執行中間操作鏈併產生結果

注意:“Stream流”其實是一個集合元素的函式模型,它並不是集合,也不是資料結構,其本身並不儲存任何元素(或其地址值)。

import java.util.*;
import java.util.function.Predicate;

/**
 * Created by hongcaixia on 2019/10/31.
 */
public class MyPredicate {

    public static void main(String[] args) {
        List<Integer> nums = Arrays.asList(10,20,3,-5,-8);
        Collection<Integer> positiveNum = filter(nums,num->num>0);
        Collection<Integer> negativeNum = filter(nums,num->num<0);
        System.out.println(positiveNum);
        System.out.println(negativeNum);
        
    }

    private static <E> Collection<E> filter(Collection<E> source,Predicate<E> predicate){
        List<E> list = new ArrayList<>(source);
        Iterator<E> iterator = list.iterator();
        while (iterator.hasNext()){
            E element = iterator.next();
            if(!predicate.test(element)){
                iterator.remove();
            }
        }
        return Collections.unmodifiableList(list);
    }
}
複製程式碼

4.1獲取流

java.util.stream.Stream<T>是Java8新加入的常用的流介面。(不是函式式介面) 獲取一個流有以下幾種常用的方式: ①根據Collection獲取流 java.util.Collection介面中加入了default方法 stream 用來獲取流,所以其所有實現類均可獲取流。

  • stream():獲取序列流
  • parallelStream():獲取並行流
package com.hcx.stream;

import java.util.*;
import java.util.stream.Stream;

/**
 * Created by hongcaixia on 2019/10/31.
 */
public class GetStreamFromCollection {

    public static void main(String[] args) {
        
        List<String> list = new ArrayList<>();
        Stream<String> stream1 = list.stream();

        Set<String> set = new HashSet<>();
        Stream<String> stream2 = set.stream();

        Vector<String> vector = new Vector<>();
        Stream<String> stream3 = vector.stream();

    }
}
複製程式碼

②根據Map獲取流 java.util.Map 介面不是Collection的子介面,且其K-V資料結構不符合流元素的單一特徵,所以獲取對應的流需要分key、value或entry等情況:

package com.hcx.stream;

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;

/**
 * Created by hongcaixia on 2019/10/31.
 */
public class GetStreamFromMap {

    public static void main(String[] args) {
        Map<String,String> map = new HashMap<>();

        Stream<Map.Entry<String,String>> stream1 = map.entrySet().stream();

        Stream<String> stream2 = map.keySet().stream();
        Stream<String> stream3 = map.values().stream();
        
    }
}

複製程式碼

③根據陣列獲取流 如果使用的不是集合或對映而是陣列,由於陣列物件不可能新增預設方法,所以 Stream 介面中提供了靜態方法of

package com.hcx.stream;

import java.util.stream.Stream;

/**
 * Created by hongcaixia on 2019/10/31.
 */
public class GetStreamFromArray {

    public static void main(String[] args) {
        String[] array = {"陳奕迅","鍾漢良","楊千嬅"};
        Stream<String> stream = Stream.of(array);
    }
}
複製程式碼

④獲取無限流

public void test6() {
    Stream<Integer> stream = Stream.iterate(0,x -> x + 2);
}

public void test7() {
    Stream<Double> stream = Stream.generate(() -> Math.random());
}
複製程式碼

注意:of 方法的引數是一個可變引數,所以支援陣列。

總結:

  • 所有的 Collection 集合都可以通過stream預設方法獲取流;
  • Stream 介面的靜態方法of可以獲取陣列對應的流

4.2 流常用方法

方法可以被分成兩種:

  • 延遲方法:返回值型別仍然是 Stream 介面自身型別的方法,因此支援鏈式呼叫。(除了終結方法外,其餘方法均為延遲方法。)
  • 終結方法:返回值型別不再是 Stream介面自身型別的方法,因此不再支援類似 StringBuilder 那樣的鏈式呼叫。終結方法包括 count 和 forEach 方法。

①逐一處理:forEach

void forEach(Consumer<? super T> action);
複製程式碼

該方法接收一個Consumer介面函式,會將每一個流元素交給該函式進行處理。 Consumer是一個消費型的函式式介面,可傳遞lambda表示式,消費資料

package com.hcx.stream;

import java.util.stream.Stream;

/**
 * Created by hongcaixia on 2019/10/31.
 */
public class StreamForEach {

    public static void main(String[] args) {
        Stream<String> stream = Stream.of("張三","李四","王五");
        stream.forEach(str-> System.out.println(str));
    }
}
複製程式碼

②過濾:filter 可以通過 filter 方法將一個流轉換成另一個子集流。方法簽名:

Stream<T> filter(Predicate<? super T> predicate);
複製程式碼

該介面接收一個Predicate 函式式介面引數(可以是一個Lambda或方法引用)作為篩選條件。

java.util.stream.Predicate函式式介面唯一的抽象方法為: boolean test(T t); 該方法將會產生一個boolean值結果,代表指定的條件是否滿足: 如果結果為true,那麼Stream流的filter方法將會留用元素; 如果結果為false,那麼filter 方法將會捨棄元素。

public class StreamFilter {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("陳奕迅","陳偉霆","陳七","鍾漢良");
        Stream<String> stream1 = stream.filter(str -> str.startsWith("陳"));
        stream1.forEach(str-> System.out.println(str));
    }
}
複製程式碼

注意:Stream屬於管道流,只能被消費一次 Stream流呼叫完畢方法,資料就回流到下一個Steam上, 而這時第一個Stream流已經使用完畢,就會關閉了, 所以第一個Stream流就不能再呼叫方法了。

③對映:map 接收lambda,將元素轉換成其他形式或提取資訊,接收一個函式作為引數,該函式會被應用到每個元素上,並將其對映成一個新的元素。即將流中的元素對映到另一個流中。 方法簽名:

<R> Stream<R> map(Function<? super T, ? extends R> mapper);
複製程式碼

該介面需要一個 Function 函式式介面引數,可以將當前流中的T型別資料轉換為另一種R型別的流。 java.util.stream.Function 函式式介面,其中唯一的抽象方法為: R apply(T t); 這可以將一種T型別轉換成為R型別,而這種轉換的動作,就稱為“對映”。

public class StreamMap {

    public static void main(String[] args) {
        Stream<String> stream = Stream.of("1","2","3");
        Stream<Integer> integerStream = stream.map(str -> Integer.parseInt(str));
        integerStream.forEach(i-> System.out.println(i));
    }
}
複製程式碼
    public void test8() {
        Person person = new Person("hcx",24);
        Person person1 = new Person("hcx2",24);
        List<Person> list = new ArrayList<>();
        list.add(person);
        list.add(person1);
        list.stream().map(Person::getName).forEach(System.out::println);
    }
複製程式碼

④flatMap 接收一個函式作為引數,將流中的每個值都換成另一個流,然後把所有流連線成一個流。即對流扁平化處理,淺顯一點解釋就是把幾個小的list轉換到一個大的list 如:[['a','b'],['c','d']] - > ['a','b','c','d'] 如果我們使用常用的map()方法獲取的lowercaseWords資料結構為:[['a','c'],['m','d','w'],['k','e','t']]。如果我們要得到如:['a','m','w','k','t']這樣資料結構的資料,就需要使用flatMap()方法。

public void test9() {
    List<String> list = Arrays.asList("a","b","c");
    Stream<Stream<Character>> streamStream = list.stream().map(MethodReference::filterCharacter);
    streamStream.forEach((stream)->stream.forEach(System.out::println));

    //使用flatMap
    Stream<Character> characterStream = list.stream().flatMap(MethodReference::filterCharacter);
    characterStream.forEach(System.out::println);
}

public static Stream<Character> filterCharacter(String str){
    List<Character> list = new ArrayList<>();
    for(Character c : str.toCharArray()){
        list.add(c);
    }
    return list.stream();
}
複製程式碼

⑤規約reduce 將流中元素反覆結合起來,得到一個值

import java.util.stream.Stream;

/**
 * Created by hongcaixia on 2019/10/31.
 */
public class StreamReduce {

    public static void main(String[] args) {
        sum(1,4,5);
    }

    private static void sum(Integer... nums){
        Stream.of(nums).reduce(Integer::sum).ifPresent(System.out::println);
    }
}
複製程式碼
@Test
public void test10() {
    List<Integer> list = Arrays.asList(1,5);
    Integer sum = list.stream().reduce(0,(x,y)->x+y);
    System.out.println(sum);
}
複製程式碼

⑥統計個數:count 終結方法 流提供 count方法來數一數其中的元素個數 該方法返回一個long值代表元素個數:

package com.hcx.stream;

import java.util.ArrayList;
import java.util.stream.Stream;

/**
 * Created by hongcaixia on 2019/10/31.
 */
public class StreamCount {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        Stream<Integer> stream = list.stream();
        long count = stream.count();
        System.out.println(count);
    }
}
複製程式碼

⑦取用前幾個:limit limit 方法可以對流進行擷取,只取用前n個。方法簽名:

Stream<T> limit(long maxSize);
複製程式碼

引數是一個 long型,如果集合當前長度大於引數則進行擷取;否則不進行操作 延遲方法,只是對流中的元素進行擷取,返回的是一個新的流,還可以繼續呼叫Stream中的其他方法

public class StreamLimit {
    public static void main(String[] args) {
        String[] str = {"1","3","4","5"};
        Stream<String> stream = Stream.of(str);
        Stream<String> limitStream = stream.limit(3);
        limitStream.forEach(string-> System.out.println(string));
    }
}
複製程式碼

⑧跳過前幾個:skip 如果希望跳過前幾個元素,可以使用skip方法獲取一個擷取之後的新流:

Stream<T> skip(long n);
複製程式碼

如果流的當前長度大於n,則跳過前n個;否則將會得到一個長度為0的空流

public class StreamSkip {
    public static void main(String[] args) {
        String[] str = {"1","5"};
        Stream<String> stream = Stream.of(str);
        Stream<String> skipStream = stream.skip(3);
        skipStream.forEach(string-> System.out.println(string));
    }
}
複製程式碼

⑨組合:concat 如果有兩個流,希望合併成為一個流,那麼可以使用 Stream 介面的靜態方法 concat

static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
複製程式碼

注意:這是一個靜態方法,與 java.lang.String 當中的 concat 方法不同

public class StreamConcat {
    public static void main(String[] args) {
        Stream<String> stream1 = Stream.of("陳奕迅","鍾漢良");
        String[] arr = {"1","3"};
        Stream<String> stream2 = Stream.of(arr);
        Stream<String> concatStream = Stream.concat(stream1,stream2);
        concatStream.forEach(str-> System.out.println(str));
    }
}
複製程式碼

⑩排序:sorted

  • sorted() 自然排序
  • sorted(Comparator com) 定製排序
  • allMatch 檢查是否匹配所有元素
  • anyMatch 檢查是否至少匹配一個元素
  • noneMatch 檢查是否沒有匹配所有元素
  • findFirst 返回第一個元素
  • findAny 返回當前流中的任意元素
  • count 返回流中元素的總個數
  • max 返回流中最大值
  • min 返回流中最小值
public class StreamSort {
    public static void main(String[] args) {
        Integer[] nums = {2,9,5,-10,90};
        Stream<Integer> numsStream = Stream.of(nums);
        Stream<Integer> sortedStram = numsStream.sorted();
        sortedStram.forEach(num -> System.out.println(num));
    }
}
複製程式碼

⑪Collect 將流轉換為其他形式。接收一個Collector的實現,用於給stream中元素做彙總的方法

import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Created by hongcaixia on 2019/10/31.
 */
public class StreamCollect {

    public static void main(String[] args) {
        List<Integer> list = Stream.of(1,5).collect(Collectors.toList());
        List<Integer> list1 = Stream.of(1,5).collect(LinkedList::new,List::add,List::addAll);
        System.out.println(list.getClass());//class java.util.ArrayList
        System.out.println(list1.getClass());//class java.util.LinkedList
    }
}
複製程式碼
@Test
public void test11() {
    Person person = new Person("hcx",24);
    Person person1 = new Person("hcx2",24);
    List<Person> list = new ArrayList<>();
    list.add(person);
    list.add(person1);
    List<String> collect = list.stream().map(Person::getName).collect(Collectors.toList());
}
複製程式碼

4.3 並行流和順序流

並行流是把一個內容分成多個資料塊,並用不同的執行緒分別處理每個資料塊的流。 通過parallel()與sequential()可以實現並行流和順序流之間的切換。

Fork/Join框架.png
Fork/Join框架:在必要的情況下,將一個大任務進行拆分(fork)成若干小任務(拆到不可再拆時),再將一個個小任務運算的結果進行join彙總。

@Test
public void test12() {
    //順序流
    LongStream.rangeClosed(0,100).reduce(0,Long::sum);
    //並行流
    long reduce = LongStream.rangeClosed(0,100).parallel().reduce(0,Long::sum);
    //5050
    System.out.println(reduce);
}
複製程式碼