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();
}
}