通過前兩天Java的學習
學到了不少進步了不少
今天我們繼續我們的Java高級之路
目錄
3.4 Timed Waiting(計時等待態) 與 Waiting(等待態) 聯繫
1. 多線程
多線程在Java語言中有十分重要的作用,在《操作系統》這門課中也十分重要,關乎系統或者進程的運行時間和效率。
1.1 併發與並行
-
併發:指兩個或多個事件在同一個時間段內發生。
-
並行:指兩個或多個事件在同一時刻發生(同時發生)。
在操作系統中,安裝了多個程序,併發指的是在一段時間內宏觀上有多個程序同時運行,這在單CPU系統中,每一時刻只能有一道程序執行,即微觀上這些程序是分時的交替運行,只不過是給人的感覺是同時運行,那是因爲分時交替運行的時間是非常短的。而在多個 CPU 系統中,則這些可以併發執行的程序便可以分配到多個處理器上(CPU),實現多任務並行執行,即利用每個處理器來處理一個可以併發執行的程序,這樣多個程序便可以同時執行。(目前電腦市場上說的多核 CPU,便是多核處理器,核越多,並行處理的程序越多,能大大的提高電腦運行的效率)
1.2 線程與進程
-
進程:是指一個內存中運行的應用程序,每個進程都有一個獨立的內存空間,一個應用程序可以同時運行多個進程;進程也是程序的一次執行過程,是系統運行程序的基本單位;系統運行一個程序即是一個進程從創建、運行到消亡的過程。
-
線程:線程是進程中的一個執行單元,負責當前進程中程序的執行,一個進程中至少有一個線程。一個進程中是可以有多個線程的,這個應用程序也可以稱之爲多線程程序。
注意:一個程序運行後至少有一個進程,一個進程中可以包含多個線程,但一個進程中至少包含一個線程。
(操作系統知識)
線程狀態:
-
新建(NEW):新創建了一個線程對象。
-
可運行(RUNNABLE):線程對象創建後,其他線程調用了該對象的start()方法。該狀態的線程位於可運行線程池中,等待被線程調度選中,獲取CPU的使用權 。
-
運行態(RUNNING):可運行狀態(runnable)的線程獲得了CPU時間片,執行程序代碼。
-
阻塞態(BLOCKED):阻塞狀態是指線程因爲某種原因放棄了CPU使用權,暫時停止運行。
-
死亡態(DEAD):線程run()、main() 方法執行結束,或者因異常退出了run()方法,則該線程結束生命週期。死亡的線程不可再次復生。
線程調度:
-
分時調度:所有線程輪流使用 CPU 的使用權,平均分配每個線程佔用 CPU 的時間。
-
搶佔式調度:優先讓優先級高的線程使用 CPU,如果線程的優先級相同,那麼會隨機選擇一個(線程隨機性),Java使用的爲搶佔式調度。
其實,多線程程序並不能提高程序的運行速度,但能夠提高程序運行效率,讓CPU的使用率更高。
1.3 創建線程類
Java使用 java.lang.Thread
類代表線程,所有的線程對象都必須是 Thread
類或其子類的實例。每個線程的作用是完成一定的任務,實際上就是執行一段程序流即一段順序執行的代碼。Java使用線程執行體來代表這段程序流。Java中通過繼承 Thread
類來創建並啓動多線程的步驟如下:
-
定義
Thread
類的子類,並重寫該類的run()
方法,該run()
方法的方法體就代表了線程需要完成的任務,因此把run()
方法稱爲線程執行體; -
創建
Thread
子類的實例,即創建了線程對象; -
調用線程對象的
start()
方法來啓動該線程。
1.4 Thread類
構造方法:
-
public Thread()
:分配一個新的線程對象。 -
public Thread(String name)
:分配一個指定名字的新的線程對象。 -
public Thread(Runnable target)
:分配一個帶有指定目標新的線程對象。 -
public Thread(Runnable target,String name)
:分配一個帶有指定目標新的線程對象並指定名字。
常用方法:
-
public String getName()
:獲取當前線程名稱。 -
public void start()
:導致此線程開始執行;JVM調用此線程的run()
方法。 -
public void run()
:此線程要執行的任務在此處定義代碼。 -
public static void sleep(long millis)
:使當前正在執行的線程以指定的毫秒數暫停(暫時停止執行)。 -
public static Thread currentThread()
:返回對當前正在執行的線程對象的引用。
代碼實現:繼承 Thread
類來創建並啓動多線程
// 測試類實現
public class Demo {
public static void main(String[] args) {
// 創建自定義線程對象
MyThread mt = new MyThread("新線程!");
// 開啓新線程
mt.start();
// 在主方法中執行for循環
for (int i = 0; i < 10; i++) {
System.out.println("main線程!"+i);
}
}
}
// 自定義線程類
public class MyThread extends Thread {
// 定義指定線程名稱的構造方法
public MyThread(String name) {
// 調用父類的String參數的構造方法,指定線程的名稱
super(name);
}
// 重寫run方法
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+":正在執行!"+i);
}
}
}
1.5 Runnable接口
翻閱API後得知創建線程的方式總共有兩種,一種是繼承 Thread
類方式,一種是實現 Runnable
接口方式,採用 java.lang.Runnable
也是非常常見的一種,我們只需要重寫 run()
方法即可。步驟如下:
-
定義
Runnable
接口的實現類,並重寫該接口的run()
方法,該run()
方法的方法體同樣是該線程的線程執行體; -
創建
Runnable
實現類的實例,並以此實例作爲Thread
的target
來創建Thread
對象,該Thread
對象纔是真正的線程對象; -
調用線程對象的
start()
方法來啓動線程。
代碼實現:實現 Runnable
接口方式來創建並啓動多線程
// 定義Runnable接口的實現類
public class MyRunnable implements Runnable{
// 重寫run()方法
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
// 測試類
public class Demo {
public static void main(String[] args) {
// 創建自定義類對象
MyRunnable mr = new MyRunnable();
// 創建線程對象
Thread t = new Thread(mr, "玩家1");
t.start();
for (int i = 0; i < 20; i++) {
System.out.println("玩家2" + i);
}
}
}
通過實現 Runnable
接口,使得該類有了多線程類的特徵。 run()
方法是多線程程序的一個執行目標。所有的多線程代碼都在 run()
方法裏面。 Thread
類實際上也是實現了Runnable
接口的類。在啓動的多線程的時候,需要先通過Thread
類的構造方法:
Thread(Runnable target)
構造出對象,然後調用 Thread
對象的 start()
方法來運行多線程代碼。
實際上所有的多線程代碼都是通過運行 Thread
的 start()
方法來運行的。因此,不管是繼承 Thread
類還是實現 Runnable
接口來實現多線程,最終還是通過 Thread
的對象的API來控制線程的,熟悉 Thread
類的API是進行多線程編程的基礎。
1.6 Thread和Runnable的區別
如果一個類繼承 Thread
,則不適合資源共享。但是如果實現了 Runnable
接口的話,則很容易的實現資源共享。
實現 Runnable
接口比繼承 Thread
類所具有的優勢:
-
適合多個相同的程序代碼的線程去共享同一個資源。
-
可以避免java中的單繼承的侷限性。
-
增加程序的健壯性,實現解耦操作,代碼可以被多個線程共享,代碼和線程獨立。
-
線程池只能放入實現
Runnable
或Callable
類線程,不能直接放入繼承Thread
的類。
1.7 匿名內部類方式實現線程的創建
使用線程的內匿名內部類方式,可以方便的實現每個線程執行不同的線程任務操作。
代碼實現:使用匿名內部類的方式實現 Runnable
接口
public class NoNameInnerClassThread {
public static void main(String[] args) {
// 第一種方式:調用線程接口重寫run方法
Runnable r = new Runnable(){
@Override
public void run(){
// 代碼段
}
};
new Thread(r).start();
// 第二種方式:重寫Thread類中run方法
new Thread(new Runnable(){
@Override
public void run(){
// 代碼段
}
}).start();
}
2. 線程安全問題
如果有多個線程在同時運行,而這些線程可能會同時運行這段代碼。程序每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。
線程安全問題都是由全局變量及靜態變量引起的。若每個線程中對全局變量、靜態變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;若有多個線程同時執行寫操作,一般都需要考慮線程同步,否則的話就可能影響線程安全。
2.1 線程同步
當我們使用多個線程訪問同一資源的時候,且多個線程中對資源有寫的操作,就容易出現線程安全問題。要解決上述多線程併發訪問一個資源的安全性問題,Java中提供了同步機制 synchronized
來解決。
同步操作有三種方式:
-
同步代碼塊。
-
同步方法。
-
鎖機制。
2.2 同步代碼塊
同步代碼塊: synchronized
關鍵字可以用於方法中的某個區塊中,表示只對這個區塊的資源實行互斥訪問。
格式:
synchronized(同步鎖){
// 需要同步操作的代碼
}
同步鎖:對象的同步鎖只是一個概念,可以想象爲在對象上標記了一個鎖。
-
鎖對象可以是任意類型。
-
多個線程對象要使用同一把鎖。
代碼實現:使用同步代碼塊實現電影院三個窗口同時賣票問題
public class Ticket implements Runnable{
private int ticket = 100;
Object lock = new Object();
// 執行賣票操作
@Override
public void run() {
// 每個窗口賣票的操作,窗口都開啓
while(true){
synchronized (lock){
if(ticket > 0){
//出票操作
try {
// 模擬出票時間
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 獲取當前線程對象的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在賣:"+ticket‐‐);
}
}
}
}
2.3 同步方法
同步方法:使用 synchronized
修飾的方法,就叫做同步方法,保證某一線程執行該方法的時候,其他線程只能在方法外等着。
格式:
public synchronized void method(){
// 可能會產生線程安全問題的代碼
}
同步鎖:對於非static方法,同步鎖就是this,對於static方法,我們使用當前方法所在類的字節碼對象(類名.class)。
代碼實現:使用同步方法實現電影院三個窗口同時賣票問題
public class Ticket implements Runnable{
private int ticket = 100;
// 重寫run()方法
@Override
public void run() {
// 每個窗口賣票窗口都開啓
while(true){
sellTicket();
}
}
// 同步方法
public synchronized void sellTicket(){
if(ticket>0){
//出票操作
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//獲取當前線程對象的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在賣:"+ticket‐‐);
}
}
}
2.4 Lock鎖
java.util.concurrent.Lock
機制提供了比 synchronized
代碼塊和 synchronized
方法更廣泛的鎖定操作,同步代碼塊/同步方法具有的功能 Lock
都有,除此之外更強大,更體現面向對象。
Lock
鎖也稱同步鎖,有如下方法:
-
public void lock()
:加同步鎖。 -
public void unlock()
:釋放同步鎖。
代碼實現:使用 Lock
鎖實現電影院三個窗口同時賣票問題
public class Ticket implements Runnable{
private int ticket = 100;
Lock lock = new ReentrantLock();
// 重寫run方法
@Override
public void run() {
// 每個窗口賣票
while(true){
// 加同步鎖
lock.lock();
if(ticket>0){
//出票操作
try{
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
//獲取當前線程對象的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在賣:"+ticket‐‐);
}
// 釋放同步鎖
lock.unlock();
}
}
}
3. 線程狀態
當線程被創建並啓動以後,它既不是一啓動就進入了執行狀態,也不是一直處於執行狀態。在線程的生命週期中,API中 java.lang.Thread.State
這個枚舉中給出了六種線程狀態:
線程狀態 | 導致狀態發生條件 |
---|---|
NEW(新建) | 線程剛被創建,但是並未啓動。還沒調用start方法。 |
Runnable(可運行) | 線程可以在java虛擬機中運行的狀態,可能正在運行自己代碼,也可能沒有,這取決於操作系統處理器。 |
Blocked(阻塞態) | 當一個線程試圖獲取一個對象鎖,而該對象鎖被其他的線程持有,則該線程進入Blocked狀態;當該線程持有鎖時,該線程將變成Runnable狀態。 |
Waiting(等待態) | 一個線程在等待另一個線程執行一個(喚醒)動作時,該線程進入Waiting狀態。進入這個狀態後是不能自動喚醒的,必須等待另一個線程調用notify或者notifyAll方法才能夠喚醒。 |
Timed Waiting(計時等待態) | 同waiting狀態,有幾個方法有超時參數,調用他們將進入Timed Waiting狀態。這一狀態將一直保持到超時期滿或者接收到喚醒通知。帶有超時參數的常用方法有Thread.sleep、Object.wait。 |
Teminated(終止態) | 因爲run方法正常退出而死亡,或者因爲沒有捕獲的異常終止了run方法而死亡。 |
3.1 Timed Waiting(計時等待)
Timed Waiting在API中的描述爲:一個正在限時等待另一個線程執行一個(喚醒)動作的線程處於這一狀態。在我們寫賣票的案例中,爲了減少線程執行太快,現象不明顯等問題,我們在 run()
方法中添加了 sleep
語句,這樣就強制當前正在執行的線程休眠(暫停執行),以“減慢線程”。其實當我們調用了 sleep
方法之後,當前執行的線程就進入到“休眠狀態”,其實就是所謂的Timed Waiting(計時等待)。
sleep
方法使用的幾點注意事項:
-
進入Timed Waiting狀態的一種常見情形是調用的
sleep
方法,單獨的線程也可以調用,不一定非要有協作關係。 -
爲了讓其他線程有機會執行,可以將
Thread.sleep()
的調用放線程run()
之內,這樣才能保證該線程執行過程中會睡眠。 -
sleep
與鎖無關,線程睡眠到期自動甦醒,並返回到Runnable可運行狀態。
Timed Waiting 線程狀態圖:
3.2 BLOCKED(阻塞態)
Blocked狀態在API中的介紹爲:一個正在阻塞等待一個監視器鎖(鎖對象)的線程處於這一狀態。比如,線程A與線程B代碼中使用同一鎖,如果線程A獲取到鎖,線程A進入到Runnable狀態,那麼線程B就進入到Blocked阻塞態。這是由Runnable狀態進入Blocked狀態。除此Waiting狀態以及Time Waiting狀態也會在某種情況下進入阻塞狀態。
Blocked 線程狀態圖:
3.3 Waiting (等待態)
Wating狀態在API中介紹爲:一個正在無限期等待另一個線程執行一個特別的(喚醒)動作的線程處於這一狀態。
其實Waiting狀態並不是一個線程的操作,它體現的是多個線程間的通信,可以理解爲多個線程之間的協作關係,多個線程會爭取鎖,同時相互之間又存在協作關係。當多個線程協作時,比如A,B線程,如果A線程在Runnable可運行狀態中調用了 wait()
方法那麼A線程就進入了Waiting等待態,同時失去了同步鎖。假如這個時候B線程獲取到了同步鎖,在運行狀態中調用了 notify()
方法,那麼就會將無限等待的A線程喚醒。注意是喚醒,如果獲取到鎖對象,那麼A線程喚醒後就進入Runnable可運行狀態;如果沒有獲取鎖對象,那麼就進入到Blocked阻塞態。
Waiting 線程狀態圖:
3.4 Timed Waiting(計時等待態) 與 Waiting(等待態) 聯繫
我們在翻閱API的時候會發現Timed Waiting(計時等待態) 與 Waiting(等待態) 聯繫還是很緊密的,比如Waiting等待態中 wait()
方法是空參的,而Timed Waiting計時等待態中 wait()
方法是帶參的。這種帶參的方法,其實是一種倒計時操作,相當於我們生活中的小鬧鐘,我們設定好時間,到時通知,可是如果提前得到(喚醒)通知,那麼設定好時間在通知也就顯得多此一舉了,那麼這種設計方案其實是一舉兩得。如果沒有得到(喚醒)通知,那麼線程就處於Timed Waiting狀態,直到倒計時完畢自動醒來;如果在倒計時期間得到(喚醒)通知,那麼線程從Timed Waiting狀態立刻喚醒。
4. 線程間通信
概念:多個線程在處理同一個資源,但是處理的動作(線程的任務)卻不相同。
爲什麼要處理線程間通信:
多個線程併發執行時, 在默認情況下CPU是隨機切換線程的,當我們需要多個線程來共同完成一件任務,並且我們希望他們有規律的執行, 那麼多線程之間需要一些協調通信,以此來幫我們達到多線程共同操作一份數據。
如何保證線程間通信有效利用資源:
多個線程在處理同一個資源,並且任務不同時,需要線程通信來幫助解決線程之間對同一個變量的使用或操作。 就是多個線程在操作同一份數據時, 避免對同一共享變量的爭奪。也就是我們需要通過一定的手段使各個線程能有效的利用資源。而這種手段即——等待喚醒機制。
4.1 等待喚醒機制
這是多個線程間的一種協作機制。談到線程我們經常想到的是線程間的競爭(race),比如去爭奪鎖,線程間也會有協作機制。就是在一個線程進行了規定操作後,就進入等待狀態 wait()
,等待其他線程執行完他們的指定代碼過後 再將其喚醒 notify()
;在有多個線程進行等待時, 如果需要,可以使用 notifyAll()
來喚醒所有的等待線程。wait/notify 就是線程間的一種協作機制。
4.2 等待喚醒中的方法
等待喚醒機制就是用於解決線程間通信的問題的,使用到的3個方法的含義如下:
-
wait()
:線程不再活動,不再參與調度,進入wait set
中,因此不會浪費CPU資源,也不會去競爭鎖了,這時的線程狀態即是 WAITING。它還要等着別的線程執行一個特別的動作,也即是“通知(notify)”在這個對象上等待的線程從wait set
中釋放出來,重新進入到調度隊列中。 -
notify()
:則選取所通知對象的wait set
中的一個線程釋放。 -
notifyAll()
:則釋放所通知對象的wait set
上的全部線程。
-
如果能獲取鎖,線程就從 WAITING 狀態變成 RUNNABLE 狀態;
-
否則,從
wait set
出來,又進入entry set
,線程就從 WAITING 狀態又變成 BLOCKED 狀態。
調用wait和notify方法需要注意的細節:
-
wait()
方法與notify()
方法必須要由同一個鎖對象調用。因爲:對應的鎖對象可以通過notify()
喚醒使用同一個鎖對象調用的wait()
方法後的線程。 -
wait()
方法與notify()
方法是屬於 Object 類的方法的。因爲:鎖對象可以是任意對象,而任意對象的所屬類都是繼承了 Object 類的。 -
wait()
方法與notify()
方法必須要在同步代碼塊或者是同步函數中使用。因爲:必須要通過鎖對象調用這2個方法。
5. 線程池
線程池:就是一個容納多個線程的容器,其中的線程可以反覆使用,省去了頻繁創建線程對象的操作,無需反覆創建線程而消耗過多資源。
合理利用線程池能夠帶來三個好處:
-
降低資源消耗。減少了創建和銷燬線程的次數,每個工作線程都可以被重複利用,可執行多個任務。
-
提高響應速度。當任務到達時,任務可以不需要的等到線程創建就能立即執行。
-
提高線程的可管理性。可以根據系統的承受能力,調整線程池中工作線線程的數目,防止因爲消耗過多的內存,而把服務器累死。
5.1 線程池的使用
Java裏面線程池的頂級接口是 java.util.concurrent.Executor
,但是嚴格意義上講 Executor
並不是一個線程池,而只是一個執行線程的工具。真正的線程池接口是 java.util.concurrent.ExecutorService
。
要配置一個線程池是比較複雜的,尤其是對於線程池的原理不是很清楚的情況下,很有可能配置的線程池不是較優的,因此在 java.util.concurrent.Executors
線程工廠類裏面提供了一些靜態工廠,生成一些常用的線程池。官方建議使用 Executors
工程類來創建線程池對象。
Executors
類中有個創建線程池的方法如下:
-
public static ExecutorService newFixedThreadPool(int nThreads)
:返回線程池對象。(創建的是有界線程池,也就是池中的線程個數可以指定最大數量)。
使用線程池對象的方法如下:
-
public Future<?> submit(Runnable task)
:獲取線程池中的某一個線程對象,並執行。
使用線程池中線程對象的步驟:
-
創建線程池對象。
-
創建Runnable接口子類對象。
-
提交Runnable接口子類對象。
-
關閉線程池(一般不做)。
代碼實現:創建使用線程池(模擬餐廳點單)
// Runnable實現類
public class MyRunnable implements Runnable {
// 重寫run()方法
@Override
public void run() {
System.out.println("顧客點菜");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("服務員來了:" + Thread.currentThread().getName());
System.out.println("點完菜,服務員離開");
}
}
// 線程池測試類
public class ThreadPoolDemo {
public static void main(String[] args) {
// 創建線程池對象,包含2個線程對象
ExecutorService service = Executors.newFixedThreadPool(2);
// 創建Runnable實例對象
MyRunnable r = new MyRunnable();
// 從線程池中獲取線程對象,然後調用MyRunnable中的run()
service.submit(r);
// 再獲取個線程對象,調用MyRunnable中的run()
service.submit(r);
service.submit(r);
// 關閉線程池
//service.shutdown();
}
}
6. Lambda表達式
面向對象的思想:做一件事情,找一個能解決這個事情的對象,調用對象的方法,完成事情。
函數式編程思想:只要能獲取到結果,誰去做的,怎麼做的都不重要,重視的是結果,不重視過程。
6.1 冗餘的Runnable代碼與Lambda寫法
代碼實現:冗餘的Runnable代碼與Lambda寫法對比
// 冗餘的Runnable代碼
public class DemoRunnable {
public static void main(String[] args) {
// 匿名內部類
Runnable task = new Runnable() {
// 覆蓋重寫run()方法
@Override
public void run() {
System.out.println("多線程任務執行!");
}
};
// 啓動線程
new Thread(task).start();
}
}
// Lambda的更優寫法
public class DemoLambdaRunnable {
public static void main(String[] args) {
// 啓動線程
new Thread(() -> System.out.println("多線程任務執行!")).start();
}
}
6.2 匿名內部類的好處與弊端
一方面,匿名內部類可以幫我們省去實現類的定義;另一方面,匿名內部類的語法——確實太複雜了!
Runnable
接口只有一個 run
方法的定義:
-
public abstract void run()
即制定了一種做事情的方案(其實就是一個函數):
-
無參數:不需要任何條件即可執行該方案。
-
無返回值:該方案不產生任何結果。
-
代碼塊(方法體):該方案的具體執行步驟。
同樣的語義體現在Lambda語法中,要更加簡單:
() -> System.out.println("多線程任務執行!")
-
前面的一對小括號即
run
方法的參數(無),代表不需要任何條件; -
中間的一個箭頭代表將前面的參數傳遞給後面的代碼;
-
後面的輸出語句即業務邏輯代碼。
6.3 Lambda標準格式
Lambda省去面向對象的條條框框,格式由3個部分組成:
-
一些參數
-
一個箭頭
-
一段代碼
Lambda表達式的標準格式爲:
(參數類型 參數名稱) -> { 代碼語句 }
格式說明:
-
小括號內的語法與傳統方法參數列表一致:無參數則留空;多個參數則用逗號分隔。
-
->是新引入的語法格式,代表指向動作。
-
大括號內的語法與傳統方法體要求基本一致。
6.4 Lambda的使用前提
Lambda的語法非常簡潔,完全沒有面向對象複雜的束縛。但是使用時有幾個問題需要特別注意:
-
使用Lambda必須具有接口,且要求接口中有且僅有一個抽象方法。 無論是JDK內置的
Runnable
、Comparator
接口還是自定義的接口,只有當接口中的抽象方法存在且唯一時,纔可以使用Lambda。 -
使用Lambda必須具有上下文推斷。 也就是方法的參數或局部變量類型必須爲Lambda對應的接口類型,才能使用Lambda作爲該接口的實例。
6.5 Lambda的參數、返回值及省略寫法
下面舉例演示 java.util.Comparator<T>
接口的使用場景代碼,其中的抽象方法定義爲:
-
public abstract int compare(T o1, T o2)
當需要對一個對象數組進行排序時, Arrays.sort
方法需要一個 Comparator
接口實例來指定排序的規則。
假設有一個 Person
類,含有 String name
和 int age
兩個成員變量。
代碼實現:對數組中的 Person
對象使用 Arrays
的 sort
方法通過年齡進行升序排序(附Lambda省略寫法)
// 定義一個Person類
public class Person {
private String name;
private int age;
// 省略構造器、toString方法與Getter Setter等
}
// Lambda寫法
public class DemoComparatorLambda {
public static void main(String[] args) {
Person[] array = {
new Person("玩家1", 19),
new Person("玩家2", 18),
new Person("玩家3", 20) };
// Lambda寫法
Arrays.sort(array, (Person a, Person b) -> {
return a.getAge() - b.getAge();
});
for (Person person : array) {
System.out.println(person);
}
}
}
// Lambda省略寫法
public class DemoComparatorLambda {
public static void main(String[] args) {
Person[] array = {
new Person("玩家1", 19),
new Person("玩家2", 18),
new Person("玩家3", 20) };
// Lambda省略寫法
Arrays.sort(array, (a, b) ->a.getAge() - b.getAge());
for (Person person : array) {
System.out.println(person);
}
}
}
在Lambda標準格式的基礎上,使用省略寫法的規則爲:
-
小括號內參數的類型可以省略;
-
如果小括號內有且僅有一個參,則小括號可以省略;
-
如果大括號內有且僅有一個語句,則無論是否有返回值,都可以省略大括號、return關鍵字及語句分號。
歡迎關注博主,歡迎互粉,一起學習!
感謝您的閱讀,不足之處歡迎指正!