JDK8新特性

zxtwonderful發表於2020-12-01

一 Lambda 表示式

Lambda 表示式,也可稱為閉包,它是推動 Java 8 釋出的最重要新特性。Lambda 允許把函式作為一個方法的引數(函式作為引數傳遞進方法中)。使用 Lambda 表示式可以使程式碼變的更加簡潔緊湊,Lambda 屬於函數語言程式設計思想

1.1 語法

lambda 表示式的語法格式如下:

(parameters) -> expression
或
(parameters) ->{ statements; }

Lambda 表示式的標準格式:

(引數型別 引數名) -> {
    方法體;
    return 返回值;
}

Lambda 表示式的省略規則:

  1. 小括號中的引數型別可以省略。
  2. 如果小括號中只有一個引數,那麼可以省略小括號。
  3. 如果大括號中只有一條語句,那麼可以省略大括號,return,分號。

格式解釋:

  • 小括號中的引數和之前方法的引數寫法一樣,可以寫任意個引數,如果多個引數,要使用逗號隔開。
  • -> 是一個運算子,表示指向性動作。
  • 大括號中的方法體以及 return 返回值的寫法和之前方法的大括號中的寫法一樣。 Lambda 表示式是函數語言程式設計思想。
public class Demo04SimpleLambda {
    //定義方法,使用介面當做引數
    public static void method(MyInterface m) {
        m.printStr("hello");
    }

    public static void main(String[] args) {
        //呼叫method方法,引數傳遞MyInterface實現類物件
        method(new MyInterface() {
            @Override
            public void printStr(String str) {
                System.out.println(str);
            }
        });
        //使用Lambda表示式的標準格式。
        method((String str) -> {
            System.out.println(str);
        });

        //1. 小括號中的引數型別可以省略。
        method((str) -> {
            System.out.println(str);
        });
        //2. 如果小括號中只有一個引數,那麼可以省略小括號。
        method(str -> {
            System.out.println(str);
        });
        //3. 如果大括號中只有一條語句,那麼可以省略大括號,return,分號。
        method(str -> System.out.println(str));
    }
}

以下是lambda表示式的重要特徵:

  • 可選型別宣告:不需要宣告引數型別,編譯器可以統一識別引數值。
  • 可選的引數圓括號:一個引數無需定義圓括號,但多個引數需要定義圓括號。
  • 可選的大括號:如果主體包含了一個語句,就不需要使用大括號。
  • 可選的返回關鍵字:如果主體只有一個表示式返回值則編譯器會自動返回值,大括號需要指定明表示式返回了一個數值。

Lambda 表示式的使用前提:

  • 必須有介面(不能是抽象類),介面中有且僅有一個需要被重寫的抽象方法。
  • 必須支援上下文推導,要能夠推匯出來 Lambda 表示式表示的是哪個介面中的內容。

可以使用介面當做引數,然後傳遞 Lambda 表示式(常用),也可以將 Lambda 表示式賦值給一個介面型別的變數。

public class Demo05BeforeLambda {
    //使用介面當做引數
    public static void method(MyInterface m) {//m = s -> System.out.println(s)
        m.printStr("HELLO");
    }

    public static void main(String[] args) {
        //使用介面當做引數,然後傳遞Lambda表示式。
        //method(s -> System.out.println(s));

        //使用匿名內部類方式建立物件
        /*
        MyInterface m = new MyInterface() {
            @Override
            public void printStr(String str) {
                System.out.println(str);
            }
        };
        */

        MyInterface m = str -> System.out.println(str);
        m.printStr("Hello");
    }
}

1.2 Lambda 與匿名內部類對比

匿名內部類的格式:

new 父類或介面() {​
    重寫的方法;}

在匿名內部類中,有很多內容都是冗餘的。比如在使用匿名內部類實現多執行緒的程式碼中。
因為 Thread 構造方法中需要傳遞 Runnable 介面型別的引數,所以我們不得不 new Runnable。
因為要重寫 Runnable 中的 run 方法,所以不得不寫了public void run。
整個匿名內部類中最關鍵的東西是方法,方法中最關鍵的有前中後三點。

  • 前:引數。
  • 中:方法體
  • 後:返回值

最好的情況是隻關注匿名內部類中最核心的這些內容(方法引數,方法體,返回值)如果使用Lambda表示式,可以只關注最核心的內容,Lambda 表示式是匿名內部類的簡化寫法。

Lambda 屬於函數語言程式設計思想

  • 物件導向思想:怎麼做。
  • 函數語言程式設計思想:做什麼。
public class Demo01Inner {
    public static void main(String[] args) {
        //使用匿名內部類的方式實現多執行緒。
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "執行了");
            }
        }).start();

        //使用Lambda表示式實現多執行緒
        new Thread(() -> System.out.println(Thread.currentThread().getName() + "執行了")).start();
    }
}

匿名內部類可以省去單獨建立 .java 檔案的操作。但是匿名內部類也是有缺點的,寫法太冗餘了,裡面有很多多餘的部分。

匿名內部類也有簡化寫法,匿名內部類的簡化寫法是 Lambda 表示式匿名內部類中最關鍵的內容是方法的引數,方法體,以及返回值,而在 Lambda 表示式中,關注的就是這三個關鍵的東西。

函數語言程式設計:可推導,就是可省略。

  • 因為在 Thread 構造方法中需要 Runnable 型別的引數,所以可以省略 new Runnable。
  • 因為 Runnable 中的只有一個抽象方法 run,所以重寫的必然是這個 run 方法,所以可以省略 run 方法的宣告部分(public void run)

Lambda 表示式可以省略物件導向中的一些條條框框,讓我們只關注最核心的內容。

public class Demo02Lambda {
    public static void main(String[] args) {
        //實現多執行緒(單獨建立.java)
        new Thread(new Task()).start();
        //使用匿名內部類的方式實現多執行緒
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "執行了");
            }
        }).start();
        //使用Lambda表示式完成多執行緒
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "執行了");
        }).start();
    }
}

匿名內部類與 Lambda 函式比較

public class Demo03Collections {
    public static void main(String[] args) {
        //建立集合
        List<Student> list = new ArrayList<>();
        //新增元素
        list.add(new Student("嫐", 20));
        list.add(new Student("嬲", 18));
        list.add(new Student("挊", 22));
        //使用比較器排序對集合中的學生物件根據年齡升序排序
        //Collections.sort(list, new Rule());

        //使用匿名內部類
        Collections.sort(list, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return o1.getAge() - o2.getAge();
            }
        });
       
        //使用Lambda表示式
        Collections.sort(list, (Student o1, Student o2) -> {
            return o1.getAge() - o2.getAge();
        });

        Collections.sort(list, (o1, o2) -> o1.getAge() - o2.getAge());

        System.out.println(list);

    }
}

1.3 Lambda 表示式例項

Lambda 表示式的簡單例子:

// 1. 不需要引數,返回值為 5  
() -> 5  
  
// 2. 接收一個引數(數字型別),返回其2倍的值  
x -> 2 * x  
  
// 3. 接受2個引數(數字),並返回他們的差值  
(x, y) -> x – y  
  
// 4. 接收2個int型整數,返回他們的和  
(int x, int y) -> x + y  
  
// 5. 接受一個 string 物件,並在控制檯列印,不返回任何值(看起來像是返回void)  
(String s) -> System.out.print(s)

在 Java8Tester.java 檔案輸入以下程式碼:
Java8Tester.java 檔案

public class Java8Tester {
   public static void main(String args[]){
      Java8Tester tester = new Java8Tester();
        
      // 型別宣告
      MathOperation addition = (int a, int b) -> a + b;
        
      // 不用型別宣告
      MathOperation subtraction = (a, b) -> a - b;
        
      // 大括號中的返回語句
      MathOperation multiplication = (int a, int b) -> { return a * b; };
        
      // 沒有大括號及返回語句
      MathOperation division = (int a, int b) -> a / b;
        
      System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
      System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
      System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
      System.out.println("10 / 5 = " + tester.operate(10, 5, division));
        
      // 不用括號
      GreetingService greetService1 = message ->
      System.out.println("Hello " + message);
        
      // 用括號
      GreetingService greetService2 = (message) ->
      System.out.println("Hello " + message);
        
      greetService1.sayMessage("Runoob");
      greetService2.sayMessage("Google");
   }
    
   interface MathOperation {
      int operation(int a, int b);
   }
    
   interface GreetingService {
      void sayMessage(String message);
   }
    
   private int operate(int a, int b, MathOperation mathOperation){
      return mathOperation.operation(a, b);
   }
}

執行以上指令碼,輸出結果為:

$ javac Java8Tester.java 
$ java Java8Tester
10 + 5 = 15
10 - 5 = 5
10 x 5 = 50
10 / 5 = 2
Hello Runoob
Hello Google

使用 Lambda 表示式需要注意以下兩點:

  1. Lambda 表示式主要用來定義行內執行的方法型別介面,例如,一個簡單方法介面。在上面例子中,我們使用各種型別的Lambda表示式來定義MathOperation介面的方法。然後我們定義了sayMessage的執行。
  2. Lambda 表示式免去了使用匿名方法的麻煩,並且給予Java簡單但是強大的函式化的程式設計能力。

1.4 變數作用域

lambda 表示式只能引用標記了 final 的外層區域性變數,這就是說不能在 lambda 內部修改定義在域外的區域性變數,否則會編譯錯誤。

在 Java8Tester.java 檔案輸入以下程式碼:

public class Java8Tester {
 
   final static String salutation = "Hello! ";
   
   public static void main(String args[]){
      GreetingService greetService1 = message -> 
      System.out.println(salutation + message);
      greetService1.sayMessage("Runoob");
   }
    
   interface GreetingService {
      void sayMessage(String message);
   }
}

執行以上指令碼,輸出結果為:

$ javac Java8Tester.java 
$ java Java8Tester
Hello! Runoob

我們也可以直接在 lambda 表示式中訪問外層的區域性變數:

public class Java8Tester {
    public static void main(String args[]) {
        final int num = 1;
        Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
        s.convert(2);  // 輸出結果為 3
    }
 
    public interface Converter<T1, T2> {
        void convert(int i);
    }
}

lambda 表示式的區域性變數可以不用宣告為 final,但是必須不可被後面的程式碼修改(即隱性的具有 final 的語義)

int num = 1;  
Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
s.convert(2);
num = 5;  
//報錯資訊:Local variable num defined in an enclosing scope must be final or effectively 
 final

在 Lambda 表示式當中不允許宣告一個與區域性變數同名的引數或者區域性變數。

String first = "";  
Comparator<String> comparator = (first, second) -> Integer.compare(first.length(), second.length());  //編譯會出錯 

注:本文轉自菜鳥教程.

相關文章