Java 函數式接口 lambda 應用

函數式接口

理解Functional Interface(函數式接口,如下簡稱FI)是學習Java8 Lambda表達式的關鍵所在,因此放在最開始討論。FI的定義其實很簡單:任何接口,若是隻包含惟一一個抽象方法,那麼它就是一個FI。爲了讓編譯器幫助咱們確保一個接口知足FI的要求(也就是說有且僅有一個抽象方法),Java8提供了@FunctionalInterface註解。舉個簡單的例子,Runnable接口就是一個FI,下面是它的源代碼:html

@Functional Interface
public interface Runnable{
    public abstract void run ();
}

public interface Runnable {
    public abstract void run (); 
}
複製代碼

ps: 上述兩種都是函數式接口,在Java 8 提供新的註解Function Interface 聲明一個接口爲函數式接口,聲明以後這個接口必須符合函數式接口的規範。@FunctionalInterface 對於接口是否是函數式接口沒有影響,但該註解知識提醒編譯器去檢查該接口是否僅包含一個抽象方法。java

下面的用法就是錯誤:bash

@Function Interface
public interface test{
    public void test1();
    public void test2();
複製代碼

ps: 接口中聲明的方法默認是抽象的,使用註解後,編譯器自動檢查發現存在兩個抽象方法,會報錯。app

函數式接口的規範

  1. 函數式接口裏是能夠包含默認方法,由於默認方法不是抽象方法,其有一個默認實現,因此是符合函數式接口的定義的;
@FunctionalInterface
    interface GreetingService {
        void sayMessage(String message);

        default void doSomeMoreWork1() {
            // Method body
        }

        default void doSomeMoreWork2() {
            // Method body
        }
    }
複製代碼
  1. 函數式接口裏是能夠包含靜態方法,由於靜態方法不能是抽象方法,是一個已經實現了的方法,因此是符合函數式接口的定義的;
@FunctionalInterface
    interface GreetingService {
        public void sayMessage(String message);
        public static void printHello(){
            System.out.println("Hello");
        }
    }
複製代碼
  1. 函數式接口裏是能夠包含Object裏的public方法,這些方法對於函數式接口來講,不被當成是抽象方法(雖然它們是抽象方法);由於任何一個函數式接口的實現,默認都繼承了Object類,包含了來自java.lang.Object裏對這些抽象方法的實現。
@FunctionalInterface
    interface GreetingService {
        void sayMessage(String message);
        
        @Override
        boolean equals(Object obj);
    }
複製代碼

Java 內部類

爲何講內部類,由於在 lamada 表達式又與 Java 內部類應用存在類似之處,特別是匿名內部類。在學習這部分前專門又複習了一遍內部類的概念。ide

分類

成員內部類
局部內部類
靜態內部類
匿名內部類函數

成員內部類

  1. 定義成員內部類後在建立該內部類的對象是不一樣於普通類的,成員內部類是其外部類的屬性。所以在建立時必須首先建立其外部類對象,再建立內部類的對象。內部類 對象名 = 外部類對象.new 內部類( );
  2. 外部類是不能直接使用內部類的成員和方法滴,可先建立內部類的對象,而後經過內部類的對象來訪問其成員變量和方法。
  3. 可先建立內部類的對象,而後經過內部類的對象來訪問其成員變量和方法。
public class Outer {
    
    private static int i = 1;
    private int j = 10;
    private int k = 20;


    public static void outerF1() {
    }

    /** * 外部類的靜態方法訪問成員內部類,與在外部類外部訪問成員內部類同樣 */
    public static void outerF4() {
        //step1 創建外部類對象
        Outer out = new Outer();
        //step2 根據外部類對象創建內部類對象
        Inner inner = out.new Inner();
        //step3 訪問內部類的方法
        inner.innerF1();
    }

    public static void main(String[] args) {

        /* * outerF4();該語句的輸出結果和下面三條語句的輸出結果同樣 *若是要直接建立內部類的對象,不能想固然地認爲只需加上外圍類Outer的名字, *就能夠按照一般的樣子生成內部類的對象,而是必須使用此外圍類的一個對象來 *建立其內部類的一個對象: *Outer.Inner outin = out.new Inner() *所以,除非你已經有了外圍類的一個對象,不然不可能生成內部類的對象。由於此 *內部類的對象會悄悄地連接到建立它的外圍類的對象。若是你用的是靜態的內部類, *那就不須要對其外圍類對象的引用。 */
        Outer out = new Outer();
        Outer.Inner outin = out.new Inner();
        outin.innerF1();
    }

    public void outerF2() {
    }

    /** * 外部類的非靜態方法訪問成員內部類 */
    public void outerF3() {
        Inner inner = new Inner();
        inner.innerF1();
    }

    /** * 成員內部類中,不能定義靜態成員 * 成員內部類中,能夠訪問外部類的全部成員 */
    class Inner {
        // static int innerI = 100;內部類中不容許定義靜態變量
        // 內部類和外部類的實例變量能夠共存
        int j = 100;
        int innerI = 1;


        void innerF1() {
            System.out.println(i);
            //在內部類中訪問內部類本身的變量直接用變量名
            System.out.println(j);
            //在內部類中訪問內部類本身的變量也能夠用this.變量名
            System.out.println(this.j);
            //在內部類中訪問外部類中與內部類同名的實例變量用外部類名.this.變量名
            System.out.println(Outer.this.j);
            //若是內部類中沒有與外部類同名的變量,則能夠直接用變量名訪問外部類變量
            System.out.println(k);
            outerF1();
            outerF2();
        }
    }
}
複製代碼
  1. 局部內部類

在方法中定義的內部類稱爲局部內部類。與局部變量相似,局部內部類不能有訪問說明符,由於它不是外圍類的一部分,可是它能夠訪問當前代碼塊內的常量,和此外圍類全部的成員。學習

public class Outer {

    private int s = 100;
    private int outI = 1;

    public static void main(String[] args) {
        // 訪問局部內部類必須先有外部類對象
        Outer out = new Outer();
        out.f(3);
    }

    public void f(final int k) {
        final int s = 200;
        int i = 1;
        final int j = 10;


        /**
         * 定義在方法內部
         */
        class Inner {
            // 能夠定義與外部類同名的變量
            int s = 300;
            int innerI = 100;

            // static int m = 20; 不能夠定義靜態變量
            Inner(int k) {
                innerF(k);
            }
            void innerF(int k) {
                // java若是內部類沒有與外部類同名的變量,在內部類中能夠直接訪問外部類的實例變量
                System.out.println(outI);
                // 能夠訪問外部類的局部變量(即方法內的變量),可是變量必須是final的
                System.out.println(j);
                //System.out.println(i);
                // 若是內部類中有與外部類同名的變量,直接用變量名訪問的是內部類的變量
                System.out.println(s);
                // 用this.變量名訪問的也是內部類變量
                System.out.println(this.s);
                // 用外部類名.this.內部類變量名訪問的是外部類變量
                System.out.println(Outer.this.s);
            }
        }
        new Inner(k);
    }
}
複製代碼
  1. 靜態內部類(嵌套類)

若是你不須要內部類對象與其外圍類對象之間有聯繫,那你能夠將內部類聲明爲static。這一般稱爲嵌套類(nested class)。想要理解static應用於內部類時的含義,你就必須記住,普通的內部類對象隱含地保存了一個引用,指向建立它的外圍類對象。然而,當內部類是static的時,就不是這樣了。 要建立嵌套類的對象,並不須要其外圍類的對象。 不能從嵌套類的對象中訪問非靜態的外圍類對象。ui

public class Outer {
    private static int i = 1;
    private int j = 10;

    public static void outerF1() {
    }

    public static void main(String[] args) {
        new Outer().outerF3();
    }

    public void outerF2() {
    }

    public void outerF3() {
        // 外部類訪問內部類的靜態成員:內部類.靜態成員
        System.out.println(Inner.inner_i);
        Inner.innerF1();
        // 外部類訪問內部類的非靜態成員:實例化內部類便可
        Inner inner = new Inner();
        inner.innerF2();
    }

    /**
     * 靜態內部類能夠用public,protected,private修飾
     * 靜態內部類中能夠定義靜態或者非靜態的成員
     */
    static class Inner {
        static int inner_i = 100;
        int innerJ = 200;

        static void innerF1() {
            // 靜態內部類只能訪問外部類的靜態成員(包括靜態變量和靜態方法)
            System.out.println("Outer.i" + i);
            outerF1();
        }


        void innerF2() {
            // 靜態內部類不能訪問外部類的非靜態成員(包括非靜態變量和非靜態方法)
            // System.out.println("Outer.i"+j);
            // outerF2();
        }
    }
}
複製代碼

匿名內部類

這是咱們今天的主角,匿名內部類, 字面意思沒有名字的類 {}。匿名內部做爲最特殊的內部類,須要講解的內容。(think in java)this

爲何使用匿名內部類:編碼

  1. 只用到類的一個實例
  2. 類在定義後立刻用到
  3. 類很是小(SUN推薦是在4行代碼如下)
  4. 給類命名並不會致使你的代碼更容易被理解

在使用匿名內部類時,要記住如下幾個原則:

  1. 匿名內部類通常不能有構造方法。
  2. 匿名內部類不能定義任何靜態成員、方法和類。
  3. 匿名內部類不能是public,protected,private,static。
  4. 只能建立匿名內部類的一個實例。
  5. 一個匿名內部類必定是在new的後面,用其隱含實現一個接口或實現一個類。
  6. 因匿名內部類爲局部內部類,因此局部內部類的全部限制都對其生效。

你可能見過以下的代碼:

List<Integer> var1 = new ArrayList<Integer>()
    {
        {
            add(1);
            add(2);
        }
    };
複製代碼

就是用到匿名類的語法糖。

// 在方法中返回一個匿名內部類
public class Parcel6 {
    public static void main(String[] args) {
        Parcel6 p = new Parcel6();
        Contents c = p.cont();
    }

    public Contents cont() {
        return new Contents() {
            private int i = 11;


            public int value() {
                return i;
            }
        }; // 在這裏須要一個分號
    }
}
複製代碼

cont()方法將下面兩個動做合併在一塊兒:返回值的生成,與表示這個返回值的類的定義。 return new Contents() 可是,在到達語句結束的分號以前,你卻說:「等一等,我想在這裏插入一個類的定義」:
這種奇怪的語法指的是:「建立一個繼承自Contents的匿名類的對象。」經過new 表達式返回的引用被自動向上轉型爲對Contents的引用。匿名內部類的語法是下面例子的簡略形式:

class MyContents implements Contents {
    private int i = 11;

    public int value() {
        return i;
    }
}
return new MyContents();
複製代碼

上述這類寫法是最多見。

在Java中,一般就是編寫另一個類或類庫的人規定一個接口,而後你來實現這個接口,而後把這個接口的一個對象做爲參數傳給別人的程序,別人的程序必要時就會經過那個接口來調用你編寫的函數,執行後續的一些方法。

public class CallBack {

    public static void main(String[] args) {
        CallBack callBack = new CallBack();
        callBack.toDoSomethings(100, new CallBackInterface() {
            public void execute() {
                System.out.println("個人請求處理成功了");
            }
        });

    }

    public void toDoSomethings(int a, CallBackInterface callBackInterface) {
        long start = System.currentTimeMillis();
        if (a > 100) {
            callBackInterface.execute();
        } else {
            System.out.println("a < 100 不須要執行回調方法");
        }
        long end = System.currentTimeMillis();
        System.out.println("該接口回調時間 : " + (end - start));
    }
}
public interface CallBackInterface {

    void execute();
}

複製代碼

Java裏的回調,能夠說是匿名內部類精彩表演,優美的編碼風格,真是讓人陶醉~ this is so amazing 。

通過上述的鋪墊引出下面的主角 lamada 表達式實現函數式接口。

Lambda語法糖

爲了可以方便、快捷、幽雅的建立出FI的實例,Java8提供了Lambda表達式這顆語法糖。下面我用一個例子來介紹Lambda語法。假設咱們想對一個List按字符串長度進行排序,那麼在Java8以前,能夠藉助匿名內部類來實現:

List<String> words = Arrays.asList("apple", "banana", "pear");
words.sort(new Comparator<String>() {
 
    @Override
    public int compare(String w1, String w2) {
        return Integer.compare(w1.length(), w2.length());
    }
 
});

複製代碼

上面的匿名內部類簡直能夠用醜陋來形容,惟一的一行邏輯被五行垃圾代碼淹沒。根據前面的定義(並查看Java源代碼)可知,Comparator是個FI,因此,能夠用Lambda表達式來實現:

words.sort((String w1, String w2) -> {
    return Integer.compare(w1.length(), w2.length());
});
複製代碼

ps: 看起來像一個匿名的方法,實際就是一個匿名類對象的引用,代碼看起來更加簡潔。能夠認爲 lambda表達式實現了接口的抽象方法,由於函數式接口默認只有一個抽象方法。

參考文獻:
函數式接口概念
詳解內部類

相關文章
相關標籤/搜索