Java知識點——函數式接口

函數式接口

1. 函數式接口

1.1 概述
	如果說一個接口內有且只有一個方法,而且該方法是一個缺省屬性爲public abstract方法,該接口可以稱之爲是一個函數式接口。
	自定義函數式接口,還有系統中提供的函數式接口
	Comparator<T> Runnable
	
	可以直接理解JDK1.8的新特徵,Lambda表達式來使用。
	
	Lambda表達式對比匿名內部類使用
		1. 簡化了代碼結構
		2. 節約了內存資源
		3. 讓程序員更加關注,我要做什麼,而不是爲了做什麼需要完成什麼
1.2 @FunctionalInterface 使用
類似於
	@Override
	開啓代碼重寫格式嚴格檢查
/**
 * 使用@FunctionalInterface檢查函數式接口格式問題
 * 要求當前接口中有且只有一個缺省屬性爲public abstract的方法
 *
 * @author Anonymous 2020/3/11 9:55
 */
@FunctionalInterface
public interface FunctionalType {
    void test();
}
1.3 使用自定義的函數式接口作爲方法的參數使用
package com.qfedu.a_functional;

/**
 * 自定義函數式接口作爲方法的參數演示
 *
 * @author Anonymous 2020/3/11 9:55
 */
public class Demo1 {
    public static void main(String[] args) {
        /*
        使用匿名內部類來完成函數式接口的使用,但是這種方式有悖於函數式接口的目的
        Low

        useFunctionalInterface(new FunctionalType() {
            @Override
            public void test() {
                System.out.println("匿名內部類的匿名對象直接作爲方法的參數。");
            }
        });
        int(*) (char **, int *)
            C語言中的函數指針
            如果是C語言中,這需要的參數是方法名
        */
       useFunctionalInterface(() -> System.out.println("函數式接口 lambda表達式實現完成方法,實現目的"));
    }

    /**
     * 使用一個函數式接口作爲方法的參數
     *
     * @param ft 函數式接口的實現類對象,或者說直接操作本質,直接傳入Lambda表達式
     */
    public static void useFunctionalInterface(FunctionalType ft) {
        ft.test();
    }
}
代碼中使用函數式接口
	1. 讓程序的目的性更強
	2. 提供複用,普適性的價值
	3. 節約資源

2. 函數式編程思想

2.1 Lambda延遲執行
2.1.1 日誌記錄
日誌是否保存會存在等級限制
	演示一個根據不同的等級來記錄log日誌
要求:	
	等級 == 1 記錄log日誌,其他情況不記錄
package com.qfedu.b_lambda;

enum Level {
    /**
     * 枚舉測試
     */
    HIGH, MIDDLE, LOWER
}

/**
 * 日誌等級記錄操作
 *
 * @author Anonymous 2020/3/11 10:17
 */
public class Demo1 {
    public static void main(String[] args) {
        /*
        這裏存問題:
            "異常位置XXX," + "異常問題XXX," + "異常時間XXX" 字符疊加過程,會產生
            5個字符串
            這5個字符串如果Level.HIGH等級是有用的,但是除此之外是沒有任何作用的。
            MIDDLE,LOWER是沒有必要進行字符串累加,存在資源浪費問題。

            字符串的累加過程,需要經過Level判斷之後纔可以執行,避免沒有必要的性能浪
            費。

            這裏就可以使用Lambda表達式執行的延遲性,在沒有滿足level情況下,不去做字
            符串累加過程。
            這裏需要【函數式接口】
         */
        log(Level.MIDDLE, "異常位置XXX," + "異常問題XXX," + "異常時間XXX");
    }

    /**
     * 判斷等級是否需要記錄當前日誌信息
     *
     * @param level  等級,枚舉類型
     * @param logMsg 需要記錄的日誌信息
     */
    public static void log(Level level, String logMsg) {
        // 判斷是否滿足枚舉類型 Level.HIGH要求
        if (Level.HIGH == level) {
            System.err.println(logMsg);
        }
    }
}
2.1.2 使用函數式接口提供日誌信息功能
這裏需要一個函數式接口,返回值類型是String類型,其他的無所謂。
package com.qfedu.b_lambda;

/**
 * 提供返回值爲String類型方法的函數式接口
 *
 * @author Anonymous 2020/3/11 10:52
 */
@FunctionalInterface
public interface LogMessage {
    /**
     * 函數式接口中方法內容,該方法的返回值是String類型
     *
     * @return String類型返回值
     */
    String returnLogMessage();
}
package com.qfedu.b_lambda;

/**
 * 使用函數式接口完成Log日誌記錄問題
 *
 * @author Anonymous 2020/3/11 10:53
 */
public class Demo2 {
    public static void main(String[] args) {
        String msg1 = "異常位置XXX,";
        String msg2 = "異常問題XXX,";
        String msg3 = "異常時間XXX";

        log(Level.LOWER, () -> {
            System.out.println("Lambda表達式執行!!!");
            return msg1 + msg2 + msg3;
        });
    }

    /**
     * 根據日誌等級Level來確定是否需要記錄日誌
     *
     * @param level Level枚舉類型,有三個數據 HIGH MIDDLE LOWER
     * @param lm LogMessage函數式接口做方法的參數
     */
    public static void log(Level level, LogMessage lm) {
        /*
        發現當Level等級爲HIGH,執行對應的lm.returnLogMessage();
        Level等級不是HIGH不執行對應的方法。

        Lambda執行延遲問題不是Lambda效率執行慢,而是在執行之前多了一個判斷
        是在判斷之後纔可以執行對應的代碼。
            不執行代碼字符串不會產生拼接導致的資源浪費問題,從而提高效率。
         */
        if (Level.HIGH == level) {
            // 通過函數式接口獲取調用對應的returnLogMessage()方法
            System.err.println(lm.returnLogMessage());
        }
    }

    public static void testEnum(int level) {
        if (Level.HIGH.getStatus() == level) {

        }
    }
}
2.2 Lambda作爲方法參數和返回值
參數演示:
	Runnable接口
package com.qfedu.b_lambda;

/**
 * Runnable接口函數式接口使用,作爲方法的參數
 *
 * @author Anonymous 2020/3/11 11:18
 */
public class Demo3 {
    public static void main(String[] args) {
        // 匿名內部類來完成對應當前Runnable接口實現類對象使用,作爲Thread構造方法參數
        // low
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("線程代碼");
            }
        }).start();

        // Lambda表達式直接作爲方法的參數
        Thread thread = new Thread(() -> {

            System.out.println("線程執行需要時間");

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("線程執行");
        }
        , "線程");

        thread.start();
    }
}
Java中提供的比較接口Comparator<T>
	利用一些返回值作爲方法中操作的調節
	
public interface Comparator<T> {
	int compare(T o1, T o2);
}
package com.qfedu.b_lambda;

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

/**
 * Lambda表示完成函數式接口,利用返回值作爲其他操作所需的數據
 *
 * @author Anonymous 2020/3/11 11:25
 */
public class Demo4 {
    public static void main(String[] args) {
        /*
        字符串排序默認是一個字典順序
         */
        String[] arr = {"eeeeeeee", "2a","dddd",  "1bb", "ccccccc", "3fffff"};

        Arrays.sort(arr);
        System.out.println(Arrays.toString(arr));

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

        // 利用一個函數式接口完成的方法,利用方法的返回值作爲當前sort方法運行
        // 所需參數,該參數用於比較規則約束
        Arrays.sort(arr, stringComparator());
        System.out.println(Arrays.toString(arr));

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

        Arrays.sort(arr, (a, b) -> a.length() - b.length());

        Arrays.sort(arr, Comparator.comparingInt(String::length));
        /*
        Comparator.comparingInt(String::length)

        Comparator:接口
        comparingInt:按照Int類型方式比較判斷
        String:表示比較的類型是String,比較什麼類型用什麼類型
        length:按照String類型的哪一種方式比較
        */
        System.out.println(Arrays.toString(arr));


    }

    /**
     * 按照字符串長度排序,返回值類型是一個Comparator<String>接口
     * 這裏需要完成Comparator接口中的compare方法
     *
     * @return 已經完成方法體的Comparator接口,並且數據類型是String類型
     */
    public static Comparator<String> stringComparator() {
        /*
        public interface Comparator<T> {
            int compare(T o1, T o2);
        }
         */
        return (a, b) -> b.length() - a.length();
    }
}

3. Java中提供的常用函數式接口

3.1 JDK常用函數式接口概述
java.util.function包名 。提供了很多函數式接口
	規範了一些操作,提升了開發效率,更加專注於目的性!!!
	
	Supplier<T> 生產者, 返回一個指定類型的數據
	Consumer<T> 消費者, 消耗一個指定類型的數據
	Predicate<T> 判斷調節,過濾使用
	Function<T,R> 類型轉換,根據你指定的類型T, 轉換成對應類型R
3.2 Supplier 生產者,返回一個指定的數據類型
java.util.function.Supplier<T>
	有且只有一個方法
		T get();
		不需要參數,返回指定T類型數據
		什麼都不喫,擠的都是輸出。。。
package com.qfedu.c_supplier;

import com.qfedu.b_lambda.Level;

import java.util.function.Supplier;

/**
 * Supplier函數式接口演示
 *
 * @author Anonymous 2020/3/11 14:44
 */
public class Demo1 {
    public static void main(String[] args) {
        String msg1 = "異常位置XXX,";
        String msg2 = "異常問題XXX,";
        String msg3 = "異常時間XXX";

        /*
        這裏需要的是一個函數式接口,直接傳入一個lambda表達式
         */
        log(Level.HIGH, () -> {
            System.out.println("Lambda表達式執行!!!");
            return msg1 + msg2 + msg3;
        });

        /*
        Lambda表達式優化
         */
        log(Level.HIGH, () -> msg1 + msg2 + msg3);
    }

    /**
     * 根據日誌等級Level來確定是否需要記錄日誌
     *
     * @param level Level枚舉類型,有三個數據 HIGH MIDDLE LOWER
     * @param supplier Supplier函數式接口,利用T get() 完成提供數據操作
     */
    public static void log(Level level, Supplier<String> supplier) {
        /*
        Supplier函數式接口利用get方法,提供對應的返回指定String類型數據的操作
         */
        if (Level.HIGH == level) {
            // 通過函數式接口獲取調用對應的returnLogMessage()方法
            System.err.println(supplier.get());
        }
    }
}
找出數組中最大值所在下標位置
package com.qfedu.c_supplier;

import java.util.function.Supplier;

/**
 * 利用函數式接口Supplier其中get方法,找出數組中最大值下標位置
 *
 * @author Anonymous 2020/3/11 14:54
 */
public class Demo2 {
    public static void main(String[] args) {
        int[] array = {1, 3, 5, 7, 9, 2, 4, 6, 8, 10};

        /*
        getMax方法需要的參數是一個Supplier函數式接口,這裏可以使用Lambda表達式完成
         */
        int max = getMax(() -> {
            int index = 0;

            for (int i = 1; i < array.length; i++) {
                if (array[index] < array[i]) {
                    index = i;
                }
            }

            return index;
        });

        System.out.println(max);
    }

    /**
     * 利用函數式接口完成一個getMax,提供一個操作思想
     *
     * @param sup Supplier函數式接口
     * @return 最大值下標位置
     */
    public static int getMax(Supplier<Integer> sup) {
        return sup.get();
    }
}
引出滿足更多普適性代碼的函數式接口使用方式
package com.qfedu.c_supplier;

import java.util.function.Supplier;

/**
 * 演示Supplier函數式接口使用,對於方法的拓展能力和
 * 普適性滿足
 *
 * @author Anonymous 2020/3/11 15:06
 */
public class Demo3 {
    public static void main(String[] args) {
        Integer[] array = {1, 3, 5, 7, 9, 2, 4, 6, 8, 10};

        Integer max = getMax(array, () -> {
            Integer temp = array[0];

            for (int i = 1; i < array.length; i++) {
                if (temp < array[i]) {
                    temp = array[i];
                }
            }

            return temp;
        });

        System.out.println(max);

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

        Person[] persons = {new Person(1, "騷磊", 16),
                new Person(2, "老黑", 66),
                new Person(3, "老付", 36),
                new Person(4, "老高", 26),
                new Person(5, "污雲", 96),
                new Person(6, "帥棟", 106)
        };

        Person max1 = getMax(persons, () -> {
            Person p = persons[0];

            for (int i = 1; i < persons.length; i++) {
                if (p.getAge() < persons[i].getAge()) {
                    p = persons[i];
                }
            }

            return p;
        });

        System.out.println(max1);
    }

    /**
     * 找出任意類型數組中最大數據
     *
     * @param arr 任意類型數組,同時約束當前泛型的使用規則
     * @param sup Supplier函數式接口,要利用get方法,返回指定T類型數據
     * @param <T> 泛型
     * @return 和數組類型一致的數據
     */
    public static <T> T getMax(T[] arr, Supplier<T> sup) {
        return sup.get();
    }
}
3.3 Consumer消費者,處理數據
Consumer<T>
	操作使用的方式是
	void accept(T t);
		根據接口指定的數據類型接收對應數據,進行處理和消費,對外沒有任何的返回
		至於處理的過程,展示,處理,計算。。。
package com.qfedu.d_consumer;

import java.util.function.Consumer;

/**
 * 使用Consumer處理數據
 *
 * @author Anonymous 2020/3/11 15:24
 */
public class Demo1 {
    public static void main(String[] args) {
        // 該方法需要的參數是一個String類型,同時使用Consumer接口處理數據
        // 因爲Consumer接口是一個函數式接口,可以使用Lambda表達式
        testConsumer("宮保雞丁,番茄牛腩,醬牛肉,黃燜雞米飯", (str) -> {
            String[] split = str.split(",");
            for (String s : split) {
                System.out.println(s);
            }
        });
    }

    /**
     * 給予當前方法一個String類型,通過Consumer函數式接口中的accept方法完成對應
     * 字符串處理
     *
     * @param str String類型字符串
     * @param consumer Consumer處理數據的函數式接口
     */
    public static void testConsumer(String str, Consumer<String> consumer) {
        consumer.accept(str);
    }
}
andThen
package com.qfedu.d_consumer;

import java.util.function.Consumer;

/**
 * Consumer接口andThen使用
 * 需要兩個Consumer接口,兩個Consumer接口進行組合處理,對數據進行消費
 *
 * andThen(Consumer<T> con)
 *      調用格式
 *          con1.andThen(con2).accept(T t);
 *          等價於一下操作
 *          con1.accept(t);
 *          con2.accept(t);
 *
 * @author Anonymous 2020/3/11 15:57
 */
public class Demo2 {
    public static void main(String[] args) {
        /*
        該方法需要兩個Consumer函數式接口,這裏可以使用兩個Lambda表達式操作
         */
        testAndThen("鄭州加油!!!中國加油!!!",
                (str) -> System.out.println(str)
                ,
                (str) -> System.err.println(str)
                );
    }

    /**
     * 使用兩個Consumer消費者方式處理str數據,首先是con1處理,再來con2處理
     *
     * @param str  需要處理的String類型數據
     * @param con1 Consumer<String> 處理String類型函數式接口
     * @param con2 Consumer<String> 處理String類型函數式接口
     */
    public static void testAndThen(String str, Consumer<String> con1, Consumer<String> con2) {
        /*
        con1.accept(str);
        con2.accept(str);

        允許組合拳
        con1.andThen(con2).andThen(con1).andThen(con2).andThen(con1).andThen(con2).accept(str);
        */
        con1.andThen(con2).accept(str);
    }
}
3.4 Predicate 判斷數據是否合適,返回true/false
Predicate<T>一般用於條件判斷,過濾數據的方法
	函數式接口中指定的方法
	boolean test(T t);
		處理T類型數據,返回boolean true / false
package com.qfedu.e_predicate;

import java.util.function.Predicate;

/**
 * 演示Predicate<T>基本使用
 *      boolean test(T t)
 *
 * @author Anonymous 2020/3/11 16:11
 */
public class Demo1 {
    public static void main(String[] args) {

        // Predicate函數式接口,使用Lambda表達式作爲方法的參數
        boolean b = testPredicate("鄭州奧力給!!!中國奧力給!!!",
                (str) -> {
                    return str.contains("加油");
                });

        System.out.println("ret : " + b);

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

        /*
        優化Lambda表達式,
            因爲是一個參數,小括號可以省略
            就一行代碼,大括號可以省略
            return也可以省略
         */
        testPredicate("鄭州奧力給!!!中國奧力給!!!", str -> str.contains("加油"));
    }

    /**
     * 使用Predicate函數式接口利用boolean test(T t)對於當前數據進行判斷操作,
     * 返回boolean類型數據
     *
     * @param str 需要進行判斷數據的String類型字符串
     * @param pre 處理使用Predicate函數式接口
     * @return 判斷接口是否滿足要求,滿足返回true,不滿足返回false
     */
    public static boolean testPredicate(String str, Predicate<String> pre) {
        return pre.test(str);
    }
}

and 與
package com.qfedu.e_predicate;

import java.util.function.Predicate;

/**
 * Predicate and使用
 * default修飾方法add(Predicate<T> pre)
 *      and就是邏輯運算符裏面的 &&
 *      同真爲真,有假【即】假
 *      需要對兩個Predicate進行判斷處理
 *
 *      例如:
 *          pre1.test(str) && pre2.test(srt);
 *          ==> pre1.and(pre2).test(str);
 *
 * @author Anonymous 2020/3/11 16:19
 */
public class Demo2 {
    public static void main(String[] args) {

        /*
        這裏需要量Predicate接口,使用Lambda
         */
        boolean ret = testAnd("趕緊復工吧,不要搞事情了!!!",
                str -> str.length() > 5,
                str -> str.startsWith("趕緊"));

        System.out.println(ret);
    }

    /**
     * 組合判斷
     *
     * @param str  需要判斷的字符串
     * @param pre1 判斷方式1
     * @param pre2 判斷方式2
     * @return 處理結果 true, false
     */
    public static boolean testAnd(String str, Predicate<String> pre1, Predicate<String> pre2) {
        // return pre1.test(str) && pre2.test(str)
        return pre1.and(pre2).test(str);
    }
}
or 或
package com.qfedu.e_predicate;

import java.util.function.Predicate;

/**
 * Predicate or演示
 *
 * @author Anonymous 2020/3/11 16:32
 */
public class Demo3 {
    public static void main(String[] args) {
        boolean ret = testOr("國家之強大,國外人羨慕不得~~",
                str -> str.length() < 10,
                str -> str.contains("國家"));

        System.out.println(ret);
    }

    /**
     * or 組合判斷
     *
     * @param str  需要判斷的字符串
     * @param pre1 判斷方式1
     * @param pre2 判斷方式2
     * @return 處理結果 true, false
     */
    public static boolean testOr(String str, Predicate<String> pre1, Predicate<String> pre2) {
        // return pre1.test(str) || pre2.test(str);
        return pre1.or(pre2).test(str);
    }
}
negate 非
package com.qfedu.e_predicate;

import java.util.function.Predicate;

/**
 * Predicate negate()操作
 *
 * @author Anonymous 2020/3/11 16:36
 */
public class Demo4 {
    public static void main(String[] args) {
        boolean ret = testNegate("疫情總會過去的!!!",
                str -> str.length() < 5);
        System.out.println(ret);
    }

    /**
     * negate操作
     *
     * @param str 字符串
     * @param pre Predicate函數式接口
     * @return 處理結果
     */
    public static boolean testNegate(String str, Predicate<String> pre) {
        // return !pre.test(str);
        return pre.negate().test(str);
    }
}
ArrayList中使用Predicate刪除指定數據
package com.qfedu.e_predicate;

import com.qfedu.c_supplier.Person;

import java.util.ArrayList;

/**
 * ArrayList,使用Predicate作爲條件約束刪除對應的數據
 *
 * @author Anonymous 2020/3/11 16:41
 */
public class Demo5 {
    public static void main(String[] args) {
        ArrayList<Person> list = new ArrayList<>();

        list.add(new Person(1, "騷磊", 16));
        list.add(new Person(2, "老黑", 66));
        list.add(new Person(3, "老付", 36));
        list.add(new Person(4, "老高", 26));
        list.add(new Person(5, "污雲", 96));
        list.add(new Person(6, "帥棟", 96));

        // 直接安排Predicate函數式接口來約束對應的條件,進行刪除操作
        // 代碼的閱讀性,普適性提升
        // 以及代碼的冗餘程度降低
        list.removeIf(person -> person.getAge() > 40 && person.getId() > 3);

        System.out.println(list);
    }
}
3.5 Function<T,R> 類型轉換
使用R apply(T t)
	轉換指定類型T到R
package com.qfedu.f_function;

import com.qfedu.c_supplier.Person;

import java.util.function.Function;

/**
 * Function<T, R> 函數式接口
 *      R apply(T)
 *
 * @author Anonymous 2020/3/11 16:50
 */
public class Demo1 {
    public static void main(String[] args) {
        // Integer類型轉換成一個String
        String change = change(10, i -> i + "");
        System.out.println(change);

        // 利用函數式接口處理一個String類型,轉換成對應的Person類型
        Person person1 = change("1,騷磊,16", str -> {
            String[] split = str.split(",");
            Person person = new Person();

            person.setId(Integer.parseInt(split[0]));
            person.setName(split[1]);
            person.setAge(Integer.parseInt(split[2]));

            return person;
        });

        System.out.println(person1);
    }

    /**
     * 轉換格式的方法,要求數據從Integer類型轉換到指定的String類型
     *
     * @param i 需要轉換的Integer類型
     * @param fun 轉換使用的Function函數式接口
     * @return 返回值的是String類型
     */
    public static String change(Integer i, Function<Integer, String> fun) {
        return fun.apply(i);
    }

    public static Person change(String str, Function<String, Person> fun) {
        return fun.apply(str);
    }
}
andThen
package com.qfedu.f_function;

import java.util.function.Function;

/**
 * Function<T, R>
 *     default修飾andThen方法使用
 *
 * @author Anonymous 2020/3/11 17:01
 */
public class Demo2 {
    public static void main(String[] args) {
        String s = testAndThen(10,
                i -> i + "",
                i -> i + "測試");

        System.out.println(s);
    }

    /**
     * 兩次轉換過程
     *
     * @param i 需要處理的類型
     * @param fun1 Function函數接口
     * @param fun2 Function函數接口
     * @return String類型
     */
    public static String testAndThen(int i, Function<Integer, String> fun1, Function<String, String> fun2) {

        // andThen使用,最後apply方法參數類型是fun1要求的轉換參數類型
        return fun1.andThen(fun2).apply(i);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章