1. 程式人生 > 其它 >JAVA函數語言程式設計之函式式介面

JAVA函數語言程式設計之函式式介面

技術標籤:java# Java8新特性

前言:

方法引用和Lambda表示式必須被賦值,同時編譯器需要識別型別資訊以確保型別正確。但是你怎麼知道傳遞給方法的引數的型別?

案例1:

x->x.toString()我們清楚這裡返回型別必須是String,但x是什麼型別呢?Lambda表示式包含型別推導(編譯器會自動推匯出型別資訊,避免了程式設計師顯式地宣告)。編譯器必須能夠以某種方式推匯出x的型別。

案例2:
(x,y)->x+y現在x和y可以是任何支援+運算子連線的資料型別,可以是兩個不同的數值型別或者是1個String加任意一種可自動轉換為String的資料型別(這包括了大多數型別)。但是,當Lambda表示式被賦值時,編譯器必須確定x和y的確切型別以生成正確的程式碼。

為了解決這個問題,Java8引入了java.util.function包。它包含一組介面,這些介面是Lambda表示式和方法引用的目標型別。每個介面只包含一個抽象方法,稱為函式式方法。在編寫介面時,可以使用@FunctionalInterface註解強制執行此“函式式方法”

一、簡單應用

@FunctionalInterface
public interface Functional {
    String goodbye(String arg);
}


public interface FunctionalNoAnn {
    String goodbye(String arg);
}


@FunctionalInterface
public interface NotFunctional {
  //@FunctionalInterface的值在NotFunctional的,定義中可見:介面中如果有多個方法則會產生編譯時錯誤訊息
  //String goodbye(String arg);
  String hello(String arg);
}

public class FunctionalAnnotation {
    public String goodbye(String arg){
        System.out.println("Goodbye," + arg);
        return "";
    }
    public static void main(String[] args){
        FunctionalAnnotation fa = new FunctionalAnnotation();
        Functional f = fa::goodbye;
        FunctionalNoAnn fna = fa::goodbye;
//        Functional fac = fa; //Incompatible
        Functional fl = a -> "Goodbye," + a ;
        FunctionalNoAnn fnal = a->"Goodbye," + a;

        System.out.println(f.goodbye("FunctionalAnnotation"));
        System.out.println(fna.goodbye("FunctionalAnnotation"));
        System.out.println(fl.goodbye("Functional"));
        System.out.println(fnal.goodbye("FunctionalNoAnn"));
    }
}

執行結果
---------------------------------------------------------------------------


Goodbye,FunctionalAnnotation

Goodbye,FunctionalAnnotation

Goodbye,Functional
Goodbye,FunctionalNoAnn

  • @FunctionalInterface註解是可選的;
  • Java在main()中把Functional和Func-tionalNoAnn都當作函式式介面
  • @FunctionalInterface的值在NotFunctional的定義中可見:介面中如果有多個方法則會產生編譯時錯誤訊息。
  • 觀察一下f和fna兩個變數呼叫的是類中的方法,而並不是自己介面中的方法,但是我們的FunctionalAnnotation類並沒有實現兩個函式是介面,。Java8新特性:如果將方法引用或Lambda表示式賦值給函式式介面(型別需要匹配),Java會適配你的賦值到目標介面。編譯器會自動包裝方法引用或Lambda表示式到實現目標介面的類的例項中(大白話就是,引用匹配方法至關注的是引數類表與返回型別,不關注繼承實現引)。
  • Functional fac = fa; 會編譯錯誤,因為它沒有明確地實現Functional介面。但是,Java8允許我們以簡便的語法為介面賦值函式。java.util.function包建立一組完整的目標介面,使得我們一般情況下不需再定義自己的介面。

二、使用函式介面時,名稱無關緊要

class In1{}

class In2{}

public class MethodConversion {
    static void accept(In1 i1,In2 i2){
        System.out.println("accept()");
    }
    static void someOtherName(In1 i1,In2 i2){
        System.out.println("someOtherName()");
    }
    public static void main(String[]args){
        BiConsumer<In1,In2> bic;
        bic=MethodConversion::accept;
        bic.accept(new In1(),new In2());
        bic=MethodConversion::someOtherName;
       //bic.someOtherName(newIn1(),newIn2());//Nope
        bic.accept(new In1(),new In2());
    }
}
-----------------------------------------------------------------

accept()
someOtherName()
  • 觀察輸出結果:在使用函式介面時,名稱無關緊要——只要引數型別和返回型別相同。Java會將你的方法對映到介面方法。

三、高階函式

高階函式(Higher-orderFunction)只是一個消費或產生函式的函式。

public interface FuncSS extends Function<String,String> {

}


public class ProduceFunction {
    static FuncSS produce(){
        return s->s.toLowerCase();//[2]
    }
    public static void main(String[] args){
        FuncSS f = produce();
        System.out.println(f.apply("YELLING"));
    }
}

--------------------------------------------------------------

yelling
  • 這裡,produce()是高階函式。

四、andThen()使用

class Apple{
    @Override
    public String toString(){
        return"Apple";
    }
}
class Banana{
    @Override
    public String toString(){
        return"Banana";
    }
 }

public class TransformFunction {
    static Function<Apple,Banana> transform(Function<Apple,Banana> in){
        return in.andThen(o->{
            System.out.println(o);
            return o;
        });
    }
    public static void main(String[] args){
        Function<Apple,Banana> f2 = transform(i->{
            System.out.println(i);
            return new Banana();
        });
        Banana banana = f2.apply(new Apple());
    }
}

這裡使用到了Function介面中名為andThen()的預設方法,該方法專門用於操作函式。顧名思義,在呼叫in函式之後呼叫toThen()(還有個compose()方法,它在in函式之前應用新函式)。要附加一個andThen()函式,我們只需將該函式作為引數傳遞。transform()產生的是一個新函式,它將in的動作與andThen()引數的動作結合起來。

五、函式式介面替代反射

現實開發中我閱讀過好多程式碼滿篇if() else if()...... else(),最長的見過else if() 超越50個,這種程式碼初入職場寫寫也就ok,但是真的遇到過5年以上的程式設計師還是這麼寫,技術好些的會在業務上分析,或是用反射,但是大家都知道反射的效能並不是很好,如果在資料量較少,效能要求不高的情況下可以使用,java8之後我們好的場景下可以用函式式介面替代反射。

public class Function {
    private String var1;
    private String var2;
    private String var3;
    private String var4;
    private String var5;
    private String var6;
    private String var7;
    private String var8;
    private String var9;
    private String var10;

    public String getVar1() {
        return var1;
    }

    public void setVar1(String var1) {
        this.var1 = var1;
    }

    public String getVar2() {
        return var2;
    }

    public void setVar2(String var2) {
        this.var2 = var2;
    }

    public String getVar3() {
        return var3;
    }

    public void setVar3(String var3) {
        this.var3 = var3;
    }

    public String getVar4() {
        return var4;
    }

    public void setVar4(String var4) {
        this.var4 = var4;
    }

    public String getVar5() {
        return var5;
    }

    public void setVar5(String var5) {
        this.var5 = var5;
    }

    public String getVar6() {
        return var6;
    }

    public void setVar6(String var6) {
        this.var6 = var6;
    }

    public String getVar7() {
        return var7;
    }

    public void setVar7(String var7) {
        this.var7 = var7;
    }

    public String getVar8() {
        return var8;
    }

    public void setVar8(String var8) {
        this.var8 = var8;
    }

    public String getVar9() {
        return var9;
    }

    public void setVar9(String var9) {
        this.var9 = var9;
    }

    public String getVar10() {
        return var10;
    }

    public void setVar10(String var10) {
        this.var10 = var10;
    }
}

public class ReflectFunction {
    static String[] arr = { "fun01", "fun02", "fun03", "fun04", "fun05", "fun06", "fun07", "fun08", "fun09", "fun10" };

    static List<BiConsumer<Function, String>> biConsumers = Arrays.asList(Function::setVar1, Function::setVar2, Function::setVar3,
            Function::setVar4, Function::setVar5, Function::setVar6, Function::setVar7, Function::setVar8, Function::setVar9, Function::setVar10);

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        IntStream.range(0, 100000).forEach(i -> testReflect());
        long end = System.currentTimeMillis();
        System.out.println(end - start);

        long start1 = System.currentTimeMillis();
        IntStream.range(0, 100000).forEach(i -> testMethodReference());
        long end1 = System.currentTimeMillis();

        System.out.println(end1 - start1);
    }

    static void testReflect() {
        Function function = new Function();

        try {
            Class<?> clazz = Class.forName("test.function.Function");
            for (int i = 0; i < arr.length - 1; i++) {
                Method method = clazz.getMethod("setVar" + (i + 1), String.class);
                method.invoke(function, arr[0]);
            }

        } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException
                | IllegalArgumentException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    static void testMethodReference() {
        Function function = new Function();

        for (int i = 0; i < arr.length; i++) {
            biConsumers.get(i).accept(function, arr[i]);
        }
    }
}