java8新特性之函式式介面、lambda表示式、介面的預設方法、方法和建構函式的引用

LY破曉發表於2020-11-09

函式式介面
當介面裡只有一個抽象方法的時候,就是函式式介面,可以使用註解(@FunctionalInterface)強制限定介面是函式式介面,即只能有一個抽象方法。

例如:

public interface Integerface1 {
     void test();
}

上面的介面只有一個抽象方法,則預設是函式式介面。

interface Integerface3 {
     void test();
     void test2();
}

該介面有兩個抽象方法,不是函式式介面

@FunctionalInterface
interface Integerface2 {
    
}

上面這樣寫編譯會報錯,因為@FunctionalInterface註解宣告瞭該介面是函式式介面,必須且只能有一個抽象方法。
如:

@FunctionalInterface
interface Integerface2 {
     void test();
}

Lambda表示式只能針對函式式介面使用。

介面裡的靜態方法
從java8開始介面裡可以有靜態方式,用static修飾,但是介面裡的靜態方法的修飾符只能是public,且預設是public。

     interface TestStaticMethod {
     static void test1() {
           System.out.println("介面裡的靜態方法!");
     }
}

用介面類名呼叫靜態方法:

public class Test {
     public static void main(String[] args) {
           TestStaticMethod.test1();
     }
}

在這裡插入圖片描述

//函式式介面
@FunctionalInterface
interface TestStaticMethod {
     //這是一個抽象方法
     void test();
     //靜態方法,不是抽象方法
     static void test1() {
           System.out.println("介面裡的靜態方法!");
     }
}

上面的程式碼編譯器並不會報錯,可以看到該介面仍然是函式式介面。
介面的預設方法
java8裡,除了可以在介面裡寫靜態方法,還可以寫非靜態方法,但是必須用default修飾,且只能是public,預設也是public。

//非靜態default方法
interface TestDefaultMethod{
    default void test() {
          System.out.println("這個是介面裡的default方法test");
    }
    public default void test1() {
          System.out.println("這個是介面裡的default方法test1");
    }
    //編譯報錯
//   private default void test2() {
//         System.out.println("這個是介面裡的default方法");
//   }
}

由於不是靜態方法,所以必須例項化才可以呼叫。

public class Test {
     public static void main(String[] args) {
 
           //使用匿名內部類初始化例項
           TestDefaultMethod tx = new TestDefaultMethod() {
           };
           tx.test();
           tx.test1();
     }
}

在這裡插入圖片描述
預設方法可以被繼承。但是要注意,如果繼承了兩個介面裡面的預設方法一樣的話,那麼必須重寫。
如:

interface A {
     default void test() {
           System.out.println("介面A的預設方法");
     }
}
interface B {
     default void test() {
           System.out.println("介面B的預設方法");
     }
}
interface C extends A,B {
 
}

這裡介面c處會報錯,因為編譯器並不知道你到底繼承的是A的預設方法還說B的預設方法。可以修改如下進行重寫,用super明確呼叫哪個介面的方法:

interface C extends A,B {
 
     @Override
     default void test() {
           A.super.test();
     }
 
}

測試:

public class Test {
     public static void main(String[] args) {
           C c = new C() {
           };
           c.test();
     }
}

在這裡插入圖片描述
類繼承兩個有同樣預設方法的介面也是一樣,必須重寫。
下面的程式碼編譯會報錯

class D implements A,B {
     void test() {
 
     }
}

因為A或B的test方法是預設方法,修飾符為public,重寫該方法修飾符必須等於或者大於它,而public已經是最大的訪問修飾符,所以這裡修飾符必須是public

class D implements A,B {
     @Override
     public void test() {
           A.super.test();
     }
}
     public static void main(String[] args) {
 
           D d = new D();
           d.test();
     }

在這裡插入圖片描述
注意:預設方法並不是抽象方法,所以下面這個介面仍然是函式式介面。

@FunctionalInterface
interface A {
   default void test() {
         System.out.println("介面A的預設方法");
   }
   void test1();
}

在介面裡可以使用預設方法來實現父介面的抽象方法。如:

interface C extends A,B {
 
     @Override
     default void test() {
           A.super.test();
     }
     default void test1() {
           System.out.println("在子介面實現父介面的抽象方法");
     }
 
}
C c = new C() {
 };
c.test1();

在這裡插入圖片描述
在實際使用匿名函式呼叫時可以重寫:

           C c = new C() {
                @Override
                public void test1() {
                     System.out.println("呼叫時重寫");
                }
           };
           c.test1();

在這裡插入圖片描述
可以在子介面裡重寫父介面的預設方法,使其成為抽象方法。
例如:

interface E {
     default void test() {
           System.out.println("介面E的預設方法");
     }
}
interface F extends E {
    void test();
}

下面main方法裡這樣寫不會報錯

           E e = new E(){
 
           };
           e.test();

但如果是這樣:

           F f = new F(){
 
           };
           f.test();

則編譯報錯,要求你必須實現test()方法:
在這裡插入圖片描述
可以改為:

     public static void main(String[] args) {
 
           F f = new F(){
                @Override
                public void test() {
                     System.out.println("F介面實現");
                }
           };
           f.test();
     }

在這裡插入圖片描述
Lanbda表示式
可以認為是一種特殊的匿名內部類
lambda只能用於函式式介面。
lambda語法:
([形參列表,不帶資料型別])-> {
//執行語句
[return…;]
}
注意:
1、如果形參列表是空的,只需要保留()即可
2、如果沒有返回值。只需要在{}寫執行語句即可
3、如果介面的抽象方法只有一個形參,()可以省略,只需要引數的名稱即可
4、如果執行語句只有一行,可以省略{},但是如果有返回值時,情況特殊。
5、如果函式式介面的方法有返回值,必須給定返回值,如果執行語句只有一句,還可以簡寫,即省去大括號和return以及最後的;號。
6、形參列表的資料型別會自動推斷,只需要引數名稱。

package com.Howard.test12;
 
public class TestLambda {
     public static void main(String[] args) {
           TestLanmdaInterface1 t1 = new TestLanmdaInterface1() {
                @Override
                public void test() {
                     System.out.println("使用匿名內部類");
 
                }
           };
           //與上面的匿名內部類執行效果一樣
           //右邊的型別會自動根據左邊的型別進行判斷
           TestLanmdaInterface1 t2 = () -> {
                System.out.println("使用lanbda");
           };
           t1.test();
           t2.test();
 
           //如果執行語句只有一行,可以省略大括號
           TestLanmdaInterface1 t3 = () -> System.out.println("省略執行語句大括號,使用lanbda");
           t3.test();
 
           TestLanmdaInterface2 t4 = (s) -> System.out.println("使用lanbda表示式,帶1個引數,引數為:"+s);
           t4.test("字串引數1");
 
           TestLanmdaInterface2 t5 = s -> System.out.println("使用lanbda表示式,只帶1個引數,可省略引數的圓括號,引數為:"+s);
           t5.test("字串引數2");
 
           TestLanmdaInterface3 t6 = (s,i) -> System.out.println("使用lanbda表示式,帶兩個引數,不可以省略圓括號,引數為:"+s+"  "+ i);
           t6.test("字串引數3",50);
     }
}
 
@FunctionalInterface
interface TestLanmdaInterface1 {
     //不帶引數的抽象方法
     void test();
}
@FunctionalInterface
interface TestLanmdaInterface2 {
     //帶引數的抽象方法
     void test(String str);
}
@FunctionalInterface
interface TestLanmdaInterface3 {
     //帶多個引數的抽象方法
     void test(String str,int num);
}

在這裡插入圖片描述

package com.Howard.test12;
 
public class CloseDoor {
     public void doClose(Closeable c) {
           System.out.println(c);
           c.close();
     }
 
     public static void main(String[] args) {
           CloseDoor cd = new CloseDoor();
           cd.doClose(new Closeable() {
                @Override
                public void close() {
                     System.out.println("使用匿名內部類實現");
 
                }
           });
 
           cd.doClose( () -> System.out.println("使用lambda表示式實現"));
     }
}
@FunctionalInterface
interface Closeable {
     void close();
}

在這裡插入圖片描述
可以看出,lambda表示式和匿名內部類並不完全相同
在這裡插入圖片描述
觀察生成的class檔案可以看出,lambda表示式並不會生成額外的.class檔案,而匿名內部類會生成CloseDoor$1.class

和匿名內部類一樣,如果訪問區域性變數,要求區域性變數必須是final,如果沒有加final,會自動加上。
例如:

public class TestLambdaReturn {
     void re(LambdaReturn lr) {
           int i = lr.test();
           System.out.println("lambda表示式返回值是:"+i);
     }
 
     public static void main(String[] args) {
           int i = 1000;
           tlr.re( () -> i);
           
     }
}
interface LambdaReturn {
     int test();
}
          
如果只是上面那樣寫,編譯不會報錯,但是如果改為:
     public static void main(String[] args) {
           int i = 1000;
           tlr.re( () -> i); //報錯
           i = 10;
     }

把i當作非final變數用,則lambda表示式那行會報錯。

方法的引用
引用例項方法:自動把呼叫方法的時候的引數,全部傳給引用的方法
<函式式介面> <變數名> = <例項> :: <例項方法名>
//自動把實參傳遞給引用的例項方法
<變數名>.<介面方法>([實參])
引用類方法:自動把呼叫方法的時候的引數,全部傳給引用的方法
引用類的例項方法:定義、呼叫介面方法的時候需要多一個引數,並且引數的型別必須和引用例項方法的型別必須一致,
把第一個引數作為引用的例項,後面的每個引數全部傳遞給引用的方法。
interface <函式式介面> {
<返回值> <方法名>(<類名><名稱> [,其它引數…])
}
<變數名>.<方法名>(<類名的例項>[,其它引數])
具體例子參考下面程式碼
構造器的引用
把方法的所有引數傳遞給引用的構造器,根據引數的型別來推斷呼叫的構造器。
參考下面程式碼

package com.Howard.test12;

import java.io.PrintStream;
import java.util.Arrays;

/**
* 測試方法的引用
* @author Howard
* 2017年4月14日
*/
public class TestMethodRef {
    public static void main(String[] args) {
          MethodRef r1 = (s) -> System.out.println(s);
          r1.test("普通方式");

          //使用方法的引用:例項方法的引用
          //System.out是一個例項  out是PrintStream 型別,有println方法
          MethodRef r2 = System.out::println;
          r2.test("方法引用");

          //MethodRef1 r3 =(a)-> Arrays.sort(a);
          //引用類方法
          MethodRef1 r3 = Arrays::sort;
          int[] a = new int[]{4,12,23,1,3};
          r3.test(a);
          //將排序後的陣列輸出
          r1.test(Arrays.toString(a));

          //引用類的例項方法
          MethodRef2 r4 = PrintStream::println;
          //第二個之後的引數作為引用方法的引數
          r4.test(System.out, "第二個引數");

          //引用構造器
          MethodRef3 r5 = String::new;
          String test = r5.test(new char[]{'測','試','構','造','器','引','用'});
          System.out.println(test);
          //普通情況
          MethodRef3 r6 = (c) -> {
               return new String(c);
          };
          String test2 = r6.test(new char[]{'測','試','構','造','器','引','用'});
          System.out.println(test2);
    }
}

interface MethodRef {
    void test(String s);
}

interface MethodRef1 {
    void test(int[] arr);
}

interface MethodRef2 {
    void test(PrintStream out,String str);
}
//測試構造器引用
interface MethodRef3 {
    String test(char[] chars);
}

在這裡插入圖片描述

相關文章