1. 程式人生 > >Java8新特性之Lambda表示式解析及其常見用法

Java8新特性之Lambda表示式解析及其常見用法

Lambda表示式是Java8更新的一大新特性,與同期更新的Stream是此版本的最大亮點。
“這是你從來沒有玩過的全新版本” —– 扎扎輝

-> 舉個栗子 : 建立一個執行緒

//old
Thread thread1 = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("thread1");
    }
});

//lambda  
Thread thread2 = new Thread(() -> {
    System.out.println("thread2"
); });

結構:(引數1,引數2) -> {程式碼塊}

  • “->” 符號前面是引數,可以有0個、1個、多個。0個引數時就寫個(),1個引數時可以不寫括號,多個引數時要寫括號。引數型別最好寫上,方便閱讀,可不寫,同樣可以通過編譯,不寫的時候就涉及到”型別推斷“這個新特性了。
  • “->” 符號後面就是程式碼塊,只有一行程式碼時可不寫大括號”{}”,多行程式碼時要寫,這裡的程式碼塊和普通方法中的程式碼塊沒有區別,也可以通過返回或者丟擲異常來退出。

提下這裡的new Runnable(),其實這裡並不是例項化了一個介面,大家也知道介面不能例項化,參考內部類和匿名內部類的知識,這裡應該是這樣的:

  • ①首先構造了一個”implements Runnable “的無名內部類(方法內的內部類)
  • ②然後構造了這個無名local內部類的一個例項,(把①中構造的內部類例項化了)
  • ③然後用Runnable來表示這個無名local內部類的type

此處new Thread 的方法,接收的是一個Runnable目標物件
這裡寫圖片描述
回到Lambda,為什麼可以不宣告引數型別?

  • 所有Lambda表示式中的引數型別都是由編譯器推斷得出的。
  • 將Lambda表示式賦值給一個區域性變數或者傳遞給一個方法作為引數,區域性變數或方法引數的型別就是Lambda表示式的目標型別。

看幾段程式碼:

JButton button = new
JButton(); //old寫法 button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.out.println("button clicked"); } }); //第一層:Lambda表示式寫法 button.addActionListener(e -> System.out.println("button clicked")); 第二層:這是addActionListener()方法及簽名 /** * Adds an <code>ActionListener</code> to the button. * @param l the <code>ActionListener</code> to be added */ public void addActionListener(ActionListener l) { listenerList.add(ActionListener.class, l); } 第三層:這是介面ActionListener public interface ActionListener extends EventListener { /** * Invoked when an action occurs. */ public void actionPerformed(ActionEvent e); }

這裡我標了3層,一層一層看,

  • addActionListener(ActionListener l)方法接收一個ActionListener型別引數
  • ActionListener介面**有且只有一個方法**actionPerformed(ActionEvent e)
  • actionPerformed(ActionEvent e)有個引數,型別是ActionEvent
    第一層宣告Lambda表示式的時候,就是這一段程式碼:”e -> System.out.println(“button clicked”)”,可以斷定,編譯器通過上下文推斷出:這裡需要一個ActionListener()型別的匿名內部類,所以就把old寫法簡化成了這一行程式碼,這個介面唯一的方法接收的型別也可以知道就是ActionEvent,所以引數型別也省略不寫了。編譯成class檔案的時候會加上。

這裡就牽扯出另一個概念–函式式介面,有沒有發現一個共同點,文中提到的Runnable介面、ActionListener介面都只有一個方法(或許是這樣才能直接推斷出引數的型別,如果有多個方法那不是不好確定是哪個方法,什麼引數型別了)。
* - 可用作Lambda表示式型別的介面都有且只有一個方法。*
檢視新版本(1.8開始)Runnable介面原始碼會發現多了一個註解:@FunctionalInterface,檢視此註解原始碼,裡面有一段註釋這樣說:

/**
*
* <p>Note that instances of functional interfaces can be created with
* lambda expressions, method references, or constructor references.
* 翻譯過來大概就是:可以建立功能介面的例項。lambda表示式、方法引用或建構函式引用。
* @jls 4.3.2. The Class Object
* @jls 9.8 Functional Interfaces
* @jls 9.4.3 Interface Method Body
* @since 1.8 
* /
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}

最後提一個點,Lambda表示式中需要引用它所在方法裡的變數時,變數只能是final或者既成事實上的final變數。換句話說,Lambda表示式引用的是值,是在使用賦給該變數的一個特定的值。

String name = "Mistra";
name = "A";
Runnable runnable2 = () -> {
    System.out.println("runnable1" + name);
};

這樣寫的話 System.out.println(“runnable1” + name);處會報錯:Variable used in lambda expression should be final or effectively final
如果試圖多次改變變數的值,並在Lambda中引用它就會報錯
什麼是既成事實上的final變數?比如說這裡name我就宣告並賦值了,沒有第二次改變它的值,它的值就是原始值並不會改變了,就是既成事實上的final變數。在這裡值不會改變了,相當於跟用final修飾是一樣的了。

最後貼程式碼,Lambda表示式的常見用法:

package mistra.com.jdk8.lambda;


import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;
import java.util.List;
import java.util.function.BinaryOperator;

/**
 * @Author: Mistra.WangRui
 * @Time 2018-09-01 15:04
 * @Description: Lambda表示式的常見用法
 **/
public class LambdaTest1 {

    public static void main(String[] args) {
        /**
         * 1.遍歷集合
         */
        String[] strings = {"A", "B", "C", "D", "E"};
        List<String> names = Arrays.asList(strings);
        //"->"符號的前面就是引數,後面就是程式碼塊,沒有引數時前面就寫空括號(),
        // 程式碼塊跟普通方法的程式碼塊一樣可以用返回或者異常來退出
        names.forEach(name -> {
            System.out.println(name);
        });
        //Java8雙冒號操作符
        names.forEach(System.out::println);

        /**
         * 2.代替匿名內部類
         */
        JButton button = new JButton();
        //old
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("button clicked");
            }
        });
        //lambda  只有一個引數時可以省略括號,程式碼塊只有一行時可以不寫大括號{}
        button.addActionListener(e -> System.out.println("button clicked"));
        //old
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread1");
            }
        });
        thread1.start();
        //lambda  沒有引數,所以是空括號()
        Thread thread2 = new Thread(() -> {
            System.out.println("thread2");
        });
        thread2.start();

        Runnable runnable1 = () -> {
            System.out.println("runnable1");
        };
        runnable1.run();

        /**
         * 3.建立函式 變數add不是兩個數字之和,而是將兩個數字相加的那行程式碼
         */
        BinaryOperator<Long> add = (x, y) -> x + y + 1;
        Long result = add.apply(5l,5l);
        System.out.println(result);//result=11

        String name = "Mistra";
        //name = "A";
        Runnable runnable2 = () -> {
            System.out.println("runnable1" + name);
        };
        runnable1.run();
    }

}

這裡寫圖片描述