Java從入門到高級(第三天)


通過前兩天Java的學習

學到了不少進步了不少

今天我們繼續我們的Java高級之路


目錄

1. 多線程

1.1 併發與並行

1.2 線程與進程

1.3 創建線程類

1.4 Thread類

1.5 Runnable接口

1.6 Thread和Runnable的區別

1.7 匿名內部類方式實現線程的創建

2. 線程安全問題

2.1 線程同步

2.2 同步代碼塊

2.3 同步方法

2.4 Lock鎖

3. 線程狀態

3.1 Timed Waiting(計時等待)

3.2 BLOCKED(阻塞態)

3.3 Waiting(等待態)

3.4 Timed Waiting(計時等待態) 與 Waiting(等待態) 聯繫

4. 線程間通信

4.1 等待喚醒機制

4.2 等待喚醒中的方法

5. 線程池

5.1 線程池的使用

6. Lambda表達式

6.1 冗餘的Runnable代碼與Lambda寫法

6.2 匿名內部類的好處與弊端

6.3 Lambda標準格式

6.4 Lambda的使用前提

6.5 Lambda的參數、返回值及省略寫法


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 類來創建並啓動多線程的步驟如下:

  1. 定義 Thread 類的子類,並重寫該類的 run() 方法,該 run() 方法的方法體就代表了線程需要完成的任務,因此把 run() 方法稱爲線程執行體;

  2. 創建 Thread 子類的實例,即創建了線程對象;

  3. 調用線程對象的 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() 方法即可。步驟如下:

  1. 定義 Runnable 接口的實現類,並重寫該接口的 run() 方法,該 run() 方法的方法體同樣是該線程的線程執行體;

  2. 創建 Runnable 實現類的實例,並以此實例作爲 Thread target 來創建 Thread 對象,該 Thread 對象纔是真正的線程對象;

  3. 調用線程對象的 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 類所具有的優勢:

  1. 適合多個相同的程序代碼的線程去共享同一個資源。

  2. 可以避免java中的單繼承的侷限性。

  3. 增加程序的健壯性,實現解耦操作,代碼可以被多個線程共享,代碼和線程獨立。

  4. 線程池只能放入實現 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 來解決。

同步操作有三種方式:

  1. 同步代碼塊。

  2. 同步方法。

  3. 鎖機制。


2.2 同步代碼塊

同步代碼塊: synchronized 關鍵字可以用於方法中的某個區塊中,表示只對這個區塊的資源實行互斥訪問。

格式:

synchronized(同步鎖){
          // 需要同步操作的代碼
}

同步鎖:對象的同步鎖只是一個概念,可以想象爲在對象上標記了一個鎖。

  1. 鎖對象可以是任意類型。

  2. 多個線程對象要使用同一把鎖。

代碼實現:使用同步代碼塊實現電影院三個窗口同時賣票問題

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.sleepObject.wait
Teminated(終止態) 因爲run方法正常退出而死亡,或者因爲沒有捕獲的異常終止了run方法而死亡。

3.1 Timed Waiting(計時等待)

Timed Waiting在API中的描述爲:一個正在限時等待另一個線程執行一個(喚醒)動作的線程處於這一狀態。在我們寫賣票的案例中,爲了減少線程執行太快,現象不明顯等問題,我們在 run() 方法中添加了 sleep 語句,這樣就強制當前正在執行的線程休眠(暫停執行),以“減慢線程”。其實當我們調用了 sleep 方法之後,當前執行的線程就進入到“休眠狀態”,其實就是所謂的Timed Waiting(計時等待)。

 sleep 方法使用的幾點注意事項:

  1. 進入Timed Waiting狀態的一種常見情形是調用的 sleep 方法,單獨的線程也可以調用,不一定非要有協作關係。

  2. 爲了讓其他線程有機會執行,可以將 Thread.sleep() 的調用放線程 run() 之內,這樣才能保證該線程執行過程中會睡眠。

  3.  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個方法的含義如下:

  1.  wait() :線程不再活動,不再參與調度,進入 wait set 中,因此不會浪費CPU資源,也不會去競爭鎖了,這時的線程狀態即是 WAITING。它還要等着別的線程執行一個特別的動作,也即是“通知(notify)”在這個對象上等待的線程從 wait set 中釋放出來,重新進入到調度隊列中。

  2.  notify() :則選取所通知對象的 wait set 中的一個線程釋放。

  3.  notifyAll() :則釋放所通知對象的 wait set 上的全部線程。

  • 如果能獲取鎖,線程就從 WAITING 狀態變成 RUNNABLE 狀態;

  • 否則,從 wait set 出來,又進入 entry set ,線程就從 WAITING 狀態又變成 BLOCKED 狀態。

調用wait和notify方法需要注意的細節:

  1.  wait() 方法與 notify() 方法必須要由同一個鎖對象調用。因爲:對應的鎖對象可以通過 notify() 喚醒使用同一個鎖對象調用的 wait() 方法後的線程。

  2.  wait() 方法與 notify() 方法是屬於 Object 類的方法的。因爲:鎖對象可以是任意對象,而任意對象的所屬類都是繼承了 Object 類的。

  3.  wait() 方法與 notify() 方法必須要在同步代碼塊或者是同步函數中使用。因爲:必須要通過鎖對象調用這2個方法。


5. 線程池

線程池:就是一個容納多個線程的容器,其中的線程可以反覆使用,省去了頻繁創建線程對象的操作,無需反覆創建線程而消耗過多資源。

合理利用線程池能夠帶來三個好處:

  1. 降低資源消耗。減少了創建和銷燬線程的次數,每個工作線程都可以被重複利用,可執行多個任務。

  2. 提高響應速度。當任務到達時,任務可以不需要的等到線程創建就能立即執行。

  3. 提高線程的可管理性。可以根據系統的承受能力,調整線程池中工作線線程的數目,防止因爲消耗過多的內存,而把服務器累死。


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) :獲取線程池中的某一個線程對象,並執行。

使用線程池中線程對象的步驟:

  1. 創建線程池對象。

  2. 創建Runnable接口子類對象。

  3. 提交Runnable接口子類對象。

  4. 關閉線程池(一般不做)。

代碼實現:創建使用線程池(模擬餐廳點單)

// 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的語法非常簡潔,完全沒有面向對象複雜的束縛。但是使用時有幾個問題需要特別注意:

  1. 使用Lambda必須具有接口,且要求接口中有且僅有一個抽象方法。 無論是JDK內置的 Runnable Comparator 接口還是自定義的接口,只有當接口中的抽象方法存在且唯一時,纔可以使用Lambda。

  2. 使用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標準格式的基礎上,使用省略寫法的規則爲:

  1. 小括號內參數的類型可以省略;

  2. 如果小括號內有且僅有一個參,則小括號可以省略;

  3. 如果大括號內有且僅有一個語句,則無論是否有返回值,都可以省略大括號、return關鍵字及語句分號。


 

歡迎關注博主,歡迎互粉,一起學習!

感謝您的閱讀,不足之處歡迎指正!

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章