JDK1.8新特性(超詳細)

Java函數式設計

實現方法:java

  • @FunctionalInterface接口
  • Lambda語法
  • 方法引用
  • 接口default方法實現

1、lambda表達式

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

匿名內部類特色:數組

  • 基於多態(多數基於接口編程)
  • 實現類無需名稱
  • 容許多個抽象方法

Lambda的語法簡潔,沒有面向對象複雜的束縛。 特色:markdown

  1. 使用Lambda必須有接口,而且接口中有且僅有一個抽象方法。 只有當接口中的抽象方法存在且惟一時,纔可使用Lambda,但排除接口默認方法以及聲明中覆蓋Object的公開方法。
  2. 使用Lambda必須具備上下文推斷。 也就是方法的參數或局部變量類型必須爲Lambda對應的接口類型,才能使用Lambda做爲該接口的實例。

備註:有且僅有一個抽象方法的接口,稱爲「函數式接口」。數據結構

標準格式由三部分組成:多線程

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

箭頭操做符的左側對應接口中參數列表(lambda表達式的參數列表), 箭頭右側爲該抽象方法的實現(lambda表達式所需執行的功能)。app

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

(參數列表):括號中參數列表的數據類型,能夠省略不寫
(參數列表):括號中的參數只有一個,那麼類型和()均可以省略
 {一些代碼} :若是{}中的代碼只有一行,不管是否有返回值,均可以省略({},return,分號)
複製代碼

注意:要省略{},return,分號 必須一塊兒省略dom

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")ide

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、函數式接口

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);
        },(message)->{
            //消費方式:對message進行切割,獲取年齡,按照指定的格式輸出
            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> pre1,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.length()>5,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> pre1, 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("女"),
                s -> s.split(",")[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, Function<String,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, Function<String,String> fun1,Function<String,Integer> fun2, Function<Integer,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指令,不會有多餘的類

3、方法引用

若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);
    }
}
複製代碼

4、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','b','c'],['m','d','w'],['k','e','t']]。若是咱們要獲得如:['a','b','c','m','d','w','k','e','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,2,3,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,2,3,4,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","2","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","2","3","4","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","2","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,0,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,2,3,4,5).collect(Collectors.toList());
        List<Integer> list1 = Stream.of(1,2,3,4,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);
}
複製代碼
相關文章
相關標籤/搜索