天天看點

如何使用Lambda表達式重構代碼

1.從匿名類到 Lambda 表達式的轉換

是将實作單一抽象方法的匿名類轉換為Lambda表達式。

Runnable r1 = new Runnable(){ 
 public void run(){ 
 System.out.println("Hello"); 
 } 
}; 
Runnable r2 = () -> System.out.println("Hello");
           

但是某些情況下,将匿名類轉換為Lambda表達式可能是一個比較複雜的過程:

  • 匿名類和Lambda表達式中的this和super的含義是不同的。

    在匿名類中,this代表的是類自身,但是在Lambda中,它代表的是包含類。

  • 匿名類可以屏蔽包含類的變量,而Lambda表達式不能(它們會導緻編譯錯誤)
    int a = 10; 
    
    Runnable r1 = () -> { 
       int a = 2;     --編譯報錯
       System.out.println(a); 
    }; 
    
    Runnable r2 = new Runnable(){
        public void run(){ 
           int a = 2;   --一切正常
           System.out.println(a); 
        } 
    };
               
  • 在涉及重載的上下文裡,将匿名類轉換為Lambda表達式可能導緻最終的代碼更加晦澀。

    實際上,匿名類的類型是在初始化時确定的,而Lambda的類型取決于它的上下文。

    interface Task{ 
     public void execute(); 
    } 
    public static void doSomething(Runnable r){ r.run(); } 
    public static void doSomething(Task a){ a.execute(); } 
    
    //現在,你再傳遞一個匿名類實作的Task,不會碰到任何問題:
    doSomething(new Task() { 
     	public void execute() { 
     		System.out.println("Danger danger!!"); 
     	} 
    });
    
    //但是将這種匿名類轉換為Lambda表達式時,就導緻了一種晦澀的方法調用,
    //因為Runnable和Task都是合法的目标類型:
    doSomething(() -> System.out.println("Danger danger!!")); //麻煩來了: doSomething(Runnable) 									
    														  //和doSomething(Task)都比對該類型
    
    //你可以對Task嘗試使用顯式的類型轉換來解決這種模棱兩可的情況:
    doSomething((Task)() -> System.out.println("Danger danger!!")); 
    //但是不要是以而放棄對Lambda的嘗試。好消息是,目前大多數的內建開發環境,
    //比如NetBeans和IntelliJ都支援這種重構,它們能自動地幫你檢查,避免發生這些問題。															  
               

2.從 Lambda 表達式到方法引用的轉換

Lambda表達式非常适用于需要傳遞代碼片段的場景。不過,為了改善代碼的可讀性,也請盡量使用方法引用。因為方法名往往能更直覺地表達代碼的意圖。

3.從指令式的資料處理切換到 Stream

  • 我們建議你将所有使用疊代器這種資料處理模式處理集合的代碼都轉換成Stream API的方式。
  • Stream API能更清晰地表達資料處理管道的意圖。
  • 除此之外,通過短路和延遲載入以及利用現代計算機的多核架構,我們可以對Stream進行優化

4. 增加代碼的靈活性

4.1.采用函數接口

沒有函數接口,你就無法使用Lambda表達式。是以,你需要在代碼中引入函數接口

4.2.有條件的延遲執行

我們經常看到這樣的代碼,控制語句被混雜在業務邏輯代碼之中。典型的情況包括進行安全

性檢查以及日志輸出。比如,下面的這段代碼,它使用了Java語言内置的Logger類:

if (logger.isLoggable(Log.FINER)){ 
 logger.finer("Problem: " + generateDiagnostic()); 
}
           

這段代碼有什麼問題嗎?其實問題不少。

  • 日志器的狀态(它支援哪些日志等級)通過isLoggable方法暴露給了用戶端代碼。
  • 為什麼要在每次輸出一條日志之前都去查詢日志器對象的狀态?這隻能搞砸你的代碼。

更好的方案是使用log方法,該方法在輸出日志消息之前,會在内部檢查日志對象是否已經

設定為恰當的日志等級:

  • 這種方式更好的原因是你不再需要在代碼中插入那些條件判斷,與此同時日志器的狀态也不再被暴露出去
  • 不過,這段代碼依舊存在一個問題。不管日志器是哪個狀态,日志消息總是先已建構好(即"Problem: " + generateDiagnostic()做了字元串拼接)

Lambda表達式可以解決上述問題,Lambda具有延遲執行的特點,隻有在真正需要最終執行時才執行。是以,你需要做的僅僅是延遲消息構造:

你可以通過下面的方式對它進行調用:

如果日志器的級别設定恰當,log方法會在内部執行作為參數傳遞進來的Lambda表達式(即"Problem: " + generateDiagnostic()才進行字元串拼接,然後在輸出)。這裡介紹的Log方法的内部實作如下:

public void log(Level level, Supplier<String> msgSupplier){ 
	 if(logger.isLoggable(level)){ 
	 log(level, msgSupplier.get()); 
	 } 
}
           

4.3.環繞執行

如果你發現雖然你的業務代碼千差萬别,但是它們擁有同樣的準備和清理階段,這時,你完全可以将這部分代碼用Lambda實作。

String oneLine = processFile((BufferedReader b) -> b.readLine()); 
String twoLines = processFile((BufferedReader b) -> b.readLine() + b.readLine()); 
public static String processFile(BufferedReaderProcessor p) throws 
	IOException { 
	try(BufferedReader br = new BufferedReader(new FileReader("java8inaction/ 
	chap8/data.txt"))){ 
		return p.process(br); 
	} 
} 
public interface BufferedReaderProcessor{ 
	String process(BufferedReader b) throws IOException; 
}