詳解JAVA8函數式接口{全}


 

1: 函數式接口

1.1 概念

1.2 格式

1.3@FunctionalInterface注解

1.4 調用自定義函數接口

2:函數式編程

2.1:lambda的延遲執行

2.2 使用Lambda作為參數和返回值

3:常用函數式接口

3.1 Supplier接口(供應接口)

3.2 練習:求數組元素最大值

3.3 Consumer接口

3.5 Predicate接口

3.6 練習:集合信息篩選

3.7 Function接口

3.8 練習:自定義函數模型拼接

 

 

 

 

主要內容:

1: 自定義函數式接口

2: 函數式編程

3: 常用函數式接口

   3.1 Supplier 你要作為一個供應者,自己生產數據

   3,2 Consumer 你要作為一個消費者,利用已經准備數據

   3.1 Supplier 你要作為一個供應者,自己生產數據

   3,2 Consumer 你要作為一個消費者,利用已經准備數據

   3.3 Function  輸入一個或者兩個不同或者相同的值轉為另一個值

   3.4 Predicate 輸入一個或者兩個不同或者相同的值總是輸出boolean

   3.5 UnaryOperator 輸入一個值轉換為相同值輸出

   3.6 BinaryOperator 輸入兩個相同類型的值 轉為相同類型的值輸出

主要語法:

  1.  () -> 代表了 lambda的一個表達式
  2.  單行代碼無需寫return (無論函數式接口有沒有返回值),花括號
  3.  多行代碼必須寫花括號,有返回值的一定要寫返回值
  4.  單行代碼且有參數的情況下可以不寫 ()   如  s->System.out.println(s)
  5.  (T t)中的參數類型可寫可不寫

 

1: 函數式接口

1.1 概念

函數式接口在java中是指:有且僅有一個抽象方法的接口

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

備注:語法糖"是指使用更加方便,但是原理不變的代碼語法。例如在遍歷集合時使用的for-each語法,其實底層的實現原理仍然是迭代器,這便是語法糖。從應用層面來講,Java中的Lambda可以被當做是匿名內部類的語法糖,但是二者在原理上是不同的。

 

1.2 格式

接口中只能存在一個抽象方法

 

修飾符 interface 接口名稱{
    public abstract 返回值 方法名稱(參數列表)
    // 其他方式 
}
// public abstract 可以不寫 編譯器自動加上
修飾符 interface 接口名稱{
       返回值 方法名稱(參數列表)
    // 其他方式 
}

 

 

 

 

1.3@FunctionalInterface注解

@FunctionalInterface // 標明為函數式接口
public abstract MyFunctionInterface{
    void mrthod(); //抽象方法
}

 

一旦使用該注解來定義接口,編譯器將會強制檢查該接口是否確實有且僅有一個抽象方法,否則將會報錯。需要注意的是,即使不使用該注解,只要滿足函數式接口的定義,這仍然是一個函數式接口,使用起來都一樣。(該接口是一個標記接口)

1.4 調用自定義函數接口

public class Test_Functional {
    // 定義一個含有函數式接口的方法
    public static void doSomthing(MyFunctionalInterface functionalInterface) {
        functionalInterface.mehod();//調用自定義函數式接口的方法
    }
    public static void main(String[] args) {
        //調用函數式接口的方法
        doSomthing(()->System.out.println("excuter lambda!"));
    }
}

 

 

2:函數式編程

2.1:lambda的延遲執行

 有些場景的代碼執行后,結果不一定會被使用,從而造成性能浪費。而Lambda表達式是延遲執行的,這正好可以作為解決方案,提升性能。

性能浪費的日志案例

注:日志可以幫助我們快速的定位問題,記錄程序運行過程中的情況,以便項目的監控和優化。一種典型的場景就是對參數進行有條件使用,

例如對日志消息進行拼接后,在滿足條件的情況下進行打印輸出:

public class Demo01Logger {
    private static void log(int level, String msg) {
        if (level == 1) {
            System.out.println(msg);
        }
     }
    public static void main(String[] args) {
        String msgA = "Hello";
        String msgB = "World";
        String msgC = "Java";
        log(1, msgA + msgB + msgC);//級別1 不一定能夠滿足 但是 字符串連接操作還是執行了 那么字符串的拼接操作就白做了,存在性能浪費
    }
}

 

 

備注:SLF4J是應用非常廣泛的日志框架,它在記錄日志時為了解決這種性能浪費的問題,並不推薦首先進行字符串的拼接,而是將字符串的若干部分作為可變參數(包裝為數組)傳入方法中,

僅在日志級別滿足要求的情況下才會進行字符串拼接。例如: LOGGER.debug("變量{}的取值為{}", "os", "macOS") ,其中的大括號 {} 為占位符。如果滿足日志級別要求,

則會將“os”“macOS”兩個字符串依次拼接到大括號的位置;否則不會進行字符串拼接。這也是一種可行解決方案,但Lambda可以做到更好。

體驗Lambda的更優寫法

@FunctionalInterface
public interface MessageBuilder {
    String buildMessage();
}
public class Demo02LoggerLambda {
    private static void log(int level, MessageBuilder builder) {
        if (level == 1) {
            System.out.println(builder.buildMessage());// 實際上利用內部類 延遲的原理,代碼不相關 無需進入到啟動代理執行
        }
    }
    public static void main(String[] args) {
        String msgA = "Hello";
        String msgB = "World";
        String msgC = "Java";
       log(2,()->{
                System.out.println("lambda 是否執行了");
                return msgA + msgB + msgC;
        });
    }
}

 

2.2 使用Lambda作為參數和返回值

假設有一個 方法使用該函數式接口作為參數,那么就可以使用Lambda進行傳參.如線程中的Runable接口

public class Runnable {
    private static void startThread(Runnable task) {
            new Thread(task).start();
    }
    public static void main(String[] args) {
            startThread(() ‐> System.out.println("線程任務執行!"));
    }
}

 

如果一個方法的返回值類型是一個函數式接口,那么就可以直接返回一個Lambda表達式(類始於new 內部類的實現方式 方法中可以實現構造內部類)

public class lambda_Comparator {
    //下面給出 lambda 以及實際替代的內部類寫法
    private static Comparator<String> newComparator(){
        return (a,b)->b.length()-a.length();
    }
    private static Comparator<String> newComparator1(){
        return new Comparator<String>() {
            @Override
            public int compare(String a, String b) {
                return b.length()-a.length();    
            }
        };
    }
    public static void main(String[] args) {
        String[] array={"abc","ab","abcd"};
        System.out.println(Arrays.toString(array));
        Arrays.sort(array, newComparator1()); // 方式一
        Arrays.sort(array,(a,b)->b.length()-a.length());//更簡單的方式
        System.out.println(Arrays.toString(array));
    }
}

 

3:常用函數式接口

3.1 Supplier接口(供應接口)

java.util.function.Supplier<T> 接口僅包含一個無參的方法: T get() 。用來獲取一個泛型參數指定類型的對象數據。由於這是一個函數式接口,

這也就意味着對應的Lambda表達式需要對外提供一個符合泛型類型的對象數據。

public class Test_Supplier {
    private static String test_Supplier(Supplier<String> suply) {
        return suply.get(); //供應者接口
    }
    public static void main(String[] args) {
         // 產生的數據作為 sout 作為輸出
         System.out.println(test_Supplier(()->"產生數據"));
         
         System.out.println(String.valueOf(new Supplier<String>() {
             @Override
            public String get() {
                return "產生數據";
            }
        }));
    }
}

 

 

3.2 練習:求數組元素最大值

public class use_Supplier_Max_Value {
    private static int getMax(Supplier<Integer> suply) {
        return suply.get();
    }
    public static void main(String[] args) {
        Integer [] data=new Integer[] {6,5,4,3,2,1};
        int reslut=getMax(()->{
            int max=0;
            for (int i = 0; i < data.length; i++) {
                max=Math.max(max, data[i]);
            }
            return max;
        });
        System.out.println(reslut);
    }
}

 

 

3.3 Consumer接口

java.util.function.Consumer<T> 接口則正好與Supplier接口相反,它不是生產一個數據,而是消費一個數據,其數據類型由泛型決定

抽象方法:accept

Consumer 接口中包含抽象方法 void accept(T t) ,意為消費一個指定泛型的數據。基本使用如:

public class Test_Comsumer {
    public static void generateX(Consumer<String> consumer) {
        consumer.accept("hello consumer");
    }
    public static void main(String[] args) {
        generateX(s->System.out.println(s));
    }
}

 

默認方法:andThen

如果一個方法的參數和返回值全都是 Consumer 類型,那么就可以實現效果:消費數據的時候,首先做一個操作,然后再做一個操作,實現組合

default Consumer<T> andThen(Consumer<? super T> after) {
    Objects.requireNonNull(after);
    return (T t) ‐> { accept(t); after.accept(t); }; 
    //1:  返回值為Consumer 那么需要 ()-> 表示函數式接口
    //2:  accept(t);為生產一個數據供應給 (T t)中的t
    //3:  after.accept(t);為利用這個t再次生成新的函數式接口 實現類始於builder的設計模式
}

 

 

3.4 練習:格式化打印信息

 請按照格式姓名:XX。性別:XX的格式將信息打印出來

public class use_Consumer_FormattorName {
    public static void formattorPersonMsg(Consumer<String[]> con1, Consumer<String[]> con2) {
        // con1.accept(new String[]{ "迪麗熱巴,女", "古力娜扎,女", "馬爾扎哈,男" });
        // con2.accept(new String[]{ "迪麗熱巴,女", "古力娜扎,女", "馬爾扎哈,男" });
        // 一句代碼搞定
        con1.andThen(con2).accept(new String[] { "迪麗熱巴,女", "古力娜扎,女", "馬爾扎哈,男" });
    }
    public static void main(String[] args) {
        formattorPersonMsg((s1) -> {
            for (int i = 0; i < s1.length; i++) {
                System.out.print(s1[i].split("\\,")[0] + " ");
            }
        }, (s2) -> {
            System.out.println();
            for (int i = 0; i < s2.length; i++) {
                System.out.print(s2[i].split("\\,")[1] + " ");
            }
        });
        System.out.println();
        printInfo(s->System.out.print(s.split("\\,")[0]),
                  s->System.out.println(","+s.split("\\,")[1]),datas);
    }
    // 自身自銷 有意思
    private static void printInfo(Consumer<String> one, Consumer<String> two, String[] array) {
        for (String info : array) { // 這里每次產生 {迪麗熱巴。性別:女 } String 數據 邏輯那邊順序處理就行
            one.andThen(two).accept(info); // 姓名:迪麗熱巴。性別:女。 } }
        }
    }
}

 

 

3.5 Predicate接口

有時候我們需要對某種類型的數據進行判斷,從而得到一個boolean值結果。這時可以使用java.util.function.Predicate<T> 接口

 (s)->  函數式接口有參數 表示有有產生數據

 (s)-> 具體的返回數據 看要是否原函數式接口給出了

抽象方法:test

Predicate 接口中包含一個抽象方法: boolean test(T t) 。用於條件判斷的場景:

默認方法:and or nagte (取反)

既然是條件判斷,就會存在與、或、非三種常見的邏輯關系。其中將兩個 Predicate 條件使用邏輯連接起來實

並且的效果時,類始於 Consumer接口 andThen()函數 其他三個雷同

default Predicate<T> and(Predicate<? super T> other) {
    Objects.requireNonNull(other); 
    return (t) ‐> test(t) && other.test(t);
}

 

判斷字符串是否包含o h and or not

   

public class Use_Predicate {
    // 判斷字符串是否存在o  即使生產者 又是消費者接口
    private static void method_test(Predicate<String> predicate) {
         boolean b = predicate.test("OOM SOF");
         System.out.println(b);
    }
    // 判斷字符串是否同時存在o h 同時
    private static void method_and(Predicate<String> predicate1,Predicate<String> predicate2) {
        boolean b = predicate1.and(predicate2).test("OOM SOF");
        System.out.println(b);
    }
    //判斷字符串是否一方存在o h 
    private static void method_or(Predicate<String> predicate1,Predicate<String> predicate2) {
        boolean b = predicate1.or(predicate2).test("OOM SOF");
        System.out.println(b);
    }
    // 判斷字符串不存在o 為真   相反結果
    private static void method_negate(Predicate<String> predicate) {
         boolean b = predicate.negate().test("OOM SOF");
         System.out.println(b);
    }
    public static void main(String[] args) {
        method_test((s)->s.contains("O"));
        method_and(s->s.contains("O"), s->s.contains("h"));
        method_or(s->s.contains("O"), s->s.contains("h"));
        method_negate(s->s.contains("O"));
    }
}

 

 

靜態方法:not  isEquals

返回值為Predicate<T> 說明最終由tets為最終處理結果

 

 static <T> Predicate<T> not(Predicate<? super T> target) {
        Objects.requireNonNull(target);
        return (Predicate<T>)target.negate();
    }

 

boolean b = Predicate.not((String s)->s.contains("J")).test("Java"); // 直接構造lambda 斷言可能包
    // 使用靜態方法判斷是否為同一個對象
    private static void method_isEqual(Predicate<String> predicate) {
        boolean b = Predicate.isEqual(predicate).test(predicate);
        System.out.println(b);
    }

 

 

3.6 練習:集合信息篩選

請通過 Predicate 接口的拼裝將符合要求的字符串篩選到集合

ArrayList 中,需要同時滿足兩個條件:

1. 必須為女生;

2. 姓名為4個字。

/**
     * 1. 必須為女生; 
     * 2. 姓名為4個字。
     */
    public static void main(String[] args) {
        String[] array = { "迪麗熱巴,女", "古力娜扎,女", "馬爾扎哈,男", "趙麗穎,女" };
        getFemaleAndname((s) -> s.split("\\,")[0].length() == 4, 
                (s) -> s.split("\\,")[1].equals(""), array);
    }
    private static void getFemaleAndname(Predicate<String> one,
            Predicate<String> two, String[] arr) {
        for (String string : arr) {
            if (one.and(two).test(string)) {
                System.out.println(string);
            }
        }
    }

 

 

3.7 Function接口

 java.util.function.Function<T,R> 接口用來根據一個類型的數據得到另一個類型的數據,前者稱為前置條件,后者稱為后置條件

        T 轉為 R

 // 將數字轉換為String類型
    private static void numberToString(Function<Number, String> function) {
        String apply = function.apply(12);
        System.out.println("轉換結果:"+apply);
    }
    public static void main(String[] args) {
        numberToString((s)->String.valueOf(s));
    }

 

 

 

默認方法 andThen  compose():

Function 接口中有一個默認的 andThen  compose方法,用來進行組合操作。JDK源代碼如:

 

  default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));// 先執行調用者,再執行after的apply犯法
     }  // 這里的V 一個是作為輸入值 一個是作為輸出值  按照調用的順序的不同 對於 T V 做輸入 輸出的順序也不同 注意看
     default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));// 后執行before的apply方法,后執行調用者apply方法
    }

 

    注意調用的先后順序

// 靜態方法
    private static void method_andThen(Function<Integer, Integer> f1,Function<Integer, Integer> f2) {
        Integer apply = f1.andThen(f2).apply(2);
        System.out.println(apply);
    }
    private static void method_compose(Function<Integer, Integer> f1,Function<Integer, Integer> f2) {
        Integer apply = f1.compose(f2).apply(2);
        System.out.println(apply);
    }
    public static void main(String[] args) {
        numberToString((s)->String.valueOf(s));
        method_andThen(s->s+1, s->s=s*2);//6
        method_compose(s->s+1, s->s=s*s);//5
    }

 

           靜態方法  identity

輸入對象就是輸出對象

//返回一個執行了apply()方法之后只會返回輸入參數的函數對象
        Object apply = Function.identity().apply(2);
        System.out.println(apply);

 

 

3.8 練習:自定義函數模型拼接

題目

請使用 Function 進行函數模型的拼接,按照順序需要執行的多個函數操作為:

String str = "趙麗穎,20";

1. 將字符串截取數字年齡部分,得到字符串;

2. 將上一步的字符串轉換成為int類型的數字;

3. 將上一步的int數字累加100,得到結果int數字。

Main{String str = "趙麗穎,20";
        solotion(s->s.split("\\,")[1],s->Integer.parseInt(s),s->s+=100,str);
    }

    private static void solotion(Function<String, String> o1, Function<String, 
            Integer> o2, Function<Integer, Integer> o3, String str) {
            Integer apply = o1.andThen(o2).andThen(o3).apply(str);
            System.out.println(apply);
    }

 

4.擴展函數接口

  擴展函數接口太多了, 但是掌握了基本的五大函數接口,相信你 其他函數接口都能夠掌握,下面有時間會更新相關函數接口的用法

  4.1: Operator 相同類型互相轉換的接口

   BinaryOperator<Integer>的andthen() 方法不支持兩個鏈接操作 也就是不需要再次BinaryOperator<Integer> 因為源代碼規定不允許使用兩次輸入

// 單個同類型操作
        UnaryOperator<String> u_str=(s)->s.split(" ")[0];
        UnaryOperator<String> u_str1=(s)->s.concat(" ok");
        
        String apply = u_str.andThen(u_str1).apply("lambda Ok");
        System.out.println(apply);
        String apply1 = u_str.compose(u_str1).apply("lambda Ok");
        System.out.println(apply1);
        // 兩個同類型操作
        BinaryOperator<Integer> way_add=(k,v)->k+v;
        BinaryOperator<Integer> way_mul=(k,v)->k*v;
        
        Integer res1 = way_add.apply(1,2);
        Integer res2 = way_mul.apply(1,2);
        System.out.println(res1);
        System.out.println(res2);
        
        Function<Integer, Integer> x=(k)->k*2;
        //注意不允許 way_add.andThen(way_mul).apply(1,2); andThen需要的是Function
        Integer apply2 = way_add.andThen(x).apply(1,2);
        System.out.println(apply2);
        
        // 最大值 最小值 (這里不能連續寫 連U型apply 要報錯)
        BinaryOperator<Integer> bi_max = BinaryOperator.maxBy(Comparator.naturalOrder());
        BinaryOperator<Integer> bi_min = BinaryOperator.minBy(Comparator.naturalOrder());
        
        Integer res3 = bi_max.apply(2, 3);
        Integer res4 = bi_max.apply(3, 2);
        System.out.println(res3);
        System.out.println(res4);

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM