Function原始碼解析與實踐

2022-11-29 18:00:08

作者:陳昌浩

1 導讀

if…else…在程式碼中經常使用,聽說可以通過Java 8的Function介面來消滅if…else…!Function介面是什麼?如果通過Function介面介面消滅if…else…呢?讓我們一起來探索一下吧。

2 Function介面

Function介面就是一個有且僅有一個抽象方法,但是可以有多個非抽象方法的介面,Function介面可以被隱式轉換為 lambda 表示式。可以通過FunctionalInterface註解來校驗Function介面的正確性。Java 8允許在介面中加入具體方法。介面中的具體方法有兩種,default方法和static方法。

@FunctionalInterface
interface TestFunctionService
{
    void addHttp(String url);
}

那麼就可以使用Lambda表示式來表示該介面的一個實現。

TestFunctionService testFunctionService = url -> System.out.println("http:" + url);

2.1 FunctionalInterface

2.1.1 原始碼
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
2.1.2 說明

上圖是FunctionalInterface的註解說明。通過上面的註解說明,可以知道FunctionalInterface是一個註解,用來說明一個介面是函數式介面。 函數式介面只有一個抽象方法。 可以有預設方法,因為預設方法有一個實現,所以不是抽象的。函數介面的範例可以用lambda表示式、方法參照或建構函式參照建立。

FunctionalInterface會校驗介面是否滿足函數式介面:

  • 型別必須是介面型別,不能是註釋型別、列舉或類。
  • 只能有一個抽象方法。
  • 可以有多個預設方法和靜態方法。
  • 可以顯示覆蓋java.lang.Object中的抽象方法。

編譯器會將滿足函數式介面定義的任何介面視為函數式介面,而不管該介面宣告中是否使用FunctionalInterface註解。

3 Function介面主要分類

Function介面主要分類:

  • Function:Function函數的表現形式為接收一個引數,並返回一個值。
  • Supplier:Supplier的表現形式為不接受引數、只返回資料。
  • Consumer:Consumer接收一個引數,沒有返回值。
  • Runnable:Runnable的表現形式為即沒有引數也沒有返回值。

3.1 Function

Function函數的表現形式為接收一個引數,並返回一個值。

3.1.1 原始碼
@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }
    static <T> Function<T, T> identity() {
        return t -> t;
    }
}
3.1.2 方法說明
  • apply:抽象方法。將此函數應用於給定的引數。引數t通過具體的實現返回R。
  • compose:default方法。返回一個複合函數,首先執行fefore函數應用於輸入,然後將該函數應用於結果。如果任意一個函數的求值引發異常,則將其傳遞給組合函數的呼叫者。
  • andThen:default方法。返回一個複合函數,該複合函數首先對其應用此函數它的輸入,然後對結果應用after函數。如果任意一個函數的求值引發異常,則將其傳遞給組合函數的呼叫者。
  • identity:static方法。返回一個始終返回其輸入引數的函數。
3.1.3 方法舉例

1)apply

測試程式碼:

public  String upString(String str){
    Function<String, String> function1 = s -> s.toUpperCase();
    return function1.apply(str);
}
 public static void main(String[] args) {
     System.out.println(upString("hello!"));
 }

通過apply呼叫具體的實現。執行結果:

2)compose

測試程式碼:

public static void main(String[] args) {
    Function<String, String> function1 = s -> s.toUpperCase();
    Function<String, String> function2 = s -> "my name is "+s;
    String result = function1.compose(function2).apply("zhangSan");
    System.out.println(result);
}

執行結果

如結果所示:compose 先執行function2 後執行function1。

3)andThen

測試程式碼:

public static void main(String[] args) {
    Function<String, String> function1 = s -> s.toUpperCase();
    Function<String, String> function2 = s -> "my name is "+s;
    String result = function1.andThen(function2).apply("zhangSan");
    System.out.println(result);
}

執行結果:

如結果所示:

andThen先執行function1 後執行function2。

  • identity

測試程式碼:

public static void main(String[] args) {
    Stream<String> stream = Stream.of("order", "good", "lab", "warehouse");
    Map<String, Integer> map = stream.collect(Collectors.toMap(Function.identity(), String::length));
    System.out.println(map);
}

執行結果:

3.2 Supplier

Supplier的表現形式為不接受引數、只返回資料。

3.2.1 原始碼
@FunctionalInterface
public interface Supplier<T> {
    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}
3.2.2 方法說明

get:抽象方法。通過實現返回T。

3.2.3 方法舉例
public class SupplierTest {
    SupplierTest(){
        System.out.println(Math.random());
        System.out.println(this.toString());
    }
}
    public static void main(String[] args) {
        Supplier<SupplierTest> sup = SupplierTest::new;
        System.out.println("呼叫一次");
        sup.get();
        System.out.println("呼叫二次");
        sup.get();
}

執行結果:

如結果所示:Supplier建立時並沒有建立新類,每次呼叫get返回的值不是同一個。

3.3 Consumer

Consumer接收一個引數,沒有返回值。

3.3.1 原始碼
@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}
3.3.2 方法說明
  • accept:對給定引數T執行一些操作。
  • andThen:按順序執行Consumer -> after ,如果執行操作引發異常,該異常被傳遞給呼叫者。
3.3.3 方法舉例
public static void main(String[] args) {
    Consumer<String> consumer = s -> System.out.println("consumer_"+s);
    Consumer<String> after = s -> System.out.println("after_"+s);
    consumer.accept("isReady");
    System.out.println("========================");
    consumer.andThen(after).accept("is coming");
}

執行結果:

如結果所示:對同一個引數T,通過andThen 方法,先執行consumer,再執行fater。

3.4 Runnable

Runnable:Runnable的表現形式為即沒有引數也沒有返回值。

3.4.1 原始碼
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
3.4.2 方法說明

run:抽象方法。run方法實現具體的內容,需要將Runnale放入到Thread中,通過Thread類中的start()方法啟動執行緒,執行run中的內容。

3.4.3 方法舉例
public class TestRun implements Runnable {
    @Override
    public void run() {
        System.out.println("TestRun is running!");
    }
}
    public static void main(String[] args) {
        Thread thread = new Thread(new TestRun());
        thread.start();
    }

執行結果:

如結果所示:當執行緒實行start方法時,執行Runnable 的run方法中的內容。

4 Function介面用法

Function的主要用途是可以通過lambda 表示式實現方法的內容。

4.1 差異處理

原始碼:

@Data
public class User {
    /**
     * 姓名
     */
    private String name;
    /**
     * 年齡
     */
    private int age;
    /**
     * 組員
     */
    private List<User> parters;
}
    public static void main(String[] args) {
        User user =new User();
        if(user ==null ||user.getAge() <18 ){
            throw new RuntimeException("未成年!");
        }
}

執行結果:

使用Function介面後的程式碼:

@FunctionalInterface
public interface testFunctionInfe {
    /**
     * 輸入異常資訊
     * @param message
     */
    void showExceptionMessage(String message);
}
    public static testFunctionInfe doException(boolean flag){
        return (message -> {
            if (flag){
                throw new RuntimeException(message);
            }
        });
    }
    public static void main(String[] args) {
        User user =new User();
        doException(user ==null ||user.getAge() <18).showExceptionMessage("未成年!");
}

執行結果:

使用function介面前後都丟擲了指定的異常資訊。

4.2 處理if…else…

原始碼:

public static void main(String[] args) {
    User user =new User();
    if(user==null){
        System.out.println("新增使用者");
    }else {
        System.out.println("更新使用者");
    }
}

使用Function介面後的程式碼:

public static void main(String[] args) {
    User user =new User();
    Consumer trueConsumer = o -> {
        System.out.println("新增使用者");
    };
    Consumer falseConsumer= o -> {
        System.out.println("更新使用者");
    };
    trueOrFalseMethdo(user).showExceptionMessage(trueConsumer,falseConsumer);
}
public static testFunctionInfe trueOrFalseMethdo(User user){
    return ((trueConsumer, falseConsumer) -> {
        if(user==null){
            trueConsumer.accept(user);
        }else {
            falseConsumer.accept(user);
        }
    });
}
@FunctionalInterface
public interface testFunctionInfe {
    /**
     * 不同分處理不同的事情
     * @param trueConsumer
     * @param falseConsumer
     */
    void showExceptionMessage(Consumer trueConsumer,Consumer falseConsumer);
}

執行結果:

4.3 處理多個if

原始碼:

public static void main(String[] args) {
    String flag="";
    if("A".equals(flag)){
        System.out.println("我是A");
    }else if ("B".equals(flag)) {
        System.out.println("我是B");
    }else if ("C".equals(flag)) {
        System.out.println("我是C");
    }else {
        System.out.println("沒有對應的指令");
    }
}

使用Function介面後的程式碼:

public static void main(String[] args) {
    String flag="B";
    Map<String, Runnable> map =initFunctionMap();
    trueOrFalseMethdo(map.get(flag)==null).showExceptionMessage(()->{
        System.out.println("沒有相應指令");
    },map.get(flag));
}
public static   Map<String, Runnable> initFunctionMap(){
    Map<String,Runnable> result  = Maps.newHashMap();
    result.put("A",()->{System.out.println("我是A");});
    result.put("B",()->{System.out.println("我是B");});
    result.put("C",()->{System.out.println("我是C");});
    return result;
}
public static testFunctionInfe trueOrFalseMethdo(boolean flag){
    return ((runnable, falseConsumer) -> {
        if(flag){
            runnable.run();
        }else {
            falseConsumer.run();
        }
    });
}

執行結果:

5 總結

Function函數式介面是java 8新加入的特性,可以和lambda表示式完美結合,是非常重要的特性,可以極大的簡化程式碼。