樂字節-Java8核心特性實戰之Lambda表達式

圖片描述

Java8 引入Lambda表達式,容許開發者將函數當成參數傳遞給某個方法,或者把代碼自己看成數據進行處理。使用Lambda表達式,使得應用變得簡潔而緊湊。 不少語言(Groovy、Scala等)從設計之初就支持Lambda表達式。可是java中使用的是 匿名內部類代替。最後藉助強大的社區力量,找了一個折中的Lambda實現方案,能夠實現簡潔而緊湊的語言結構。java

1、匿名內部類到Lambda的演化

匿名內部類,即一個沒有名字的,存在於一個類或方法內部的類。當咱們須要用某個類且只須要用一次,建立和使用和二爲一時,咱們能夠選擇匿名內部類,省掉咱們定義類的步驟。數據庫

匿名內部類會隱士的繼承一個類或實現一個接口,或者說匿名內部類是一個繼承了該類或者實現了該接口的子類匿名對象。下面看一個匿名內部類的例子express

package com.java8;
/*
    定義和使用匿名內部類
 */
public class NoNameClass {
    public static void main(String[] args) {

        Model m = new Model(){
            @Override
            public void func() {
                System.out.println("方法的實現");
            }
        };
        m.func();
    }
}
// 須要被實現的接口
interface Model{
    void func();
}

等價的Lambda 代碼編程

package com.java8;
/*
    定義和使用Lambda 簡化代碼
 */
public class NoNameClass {
    public static void main(String[] args) {

        Model m = new Model(){()->{
            System.out.println("方法的實現");
        }};
        m.func();
    }
}

能夠看出使用Lambda 表達式替代了匿名內部類代碼,使得代碼更加簡化、緊湊。後端

一、語法運維

(parameters) -> expression  或  (parameters) ->{ statements; }
  • 可選類型聲明
    不須要聲明參數類型,編譯器能夠統一識別參數值。
  • 可選的參數圓括號
    一個參數無需定義圓括號,但多個參數須要定義圓括號。
  • 可選的大括號
    若是主體包含了一個語句,就不須要使用大括號。
  • 可選的返回關鍵字
    若是主體只有一個表達式返回值則編譯器會自動返回值,大括號須要指明表達式返回了一個數值

二、Lambda 表達式示例
圖片描述dom

2、Lambda使用形式

使用Lambda時,實現方法能夠有參數,也能夠有返回值,若是沒指定參數類型,則由編譯器自行推斷得出。ide

一、無參帶返回值

生成[1,10]之間的任意整數函數式編程

interface Model2{
    int func();
}
Model2 md2 = () -> {return (int)(Math.random()*10+1)};

Lambda的改寫須要有對應的抽象方法,當沒有參數時須要使用()佔位,當表達式只有一行代碼時,能夠省略return和{}函數

以上的Lambda等價於:

Model2 md2 = () -> (int)(Math.random()*10+1);

二、帶參帶返回值

返回一個對數字描述的字符串。

interface Model3{
    String func(int a);
}
Model3 md3 = (int a) -> {
    return "This is a number " + a;
};

形參寫在()內便可,參數的類型能夠省略,此時將由編譯器自行推斷得出,同時還能夠省略()

以上的Lambda等價於:

md3 =  a -> "This is a number " + a;

省略了參數類型,小括號,同時連帶實現體的括號和return一併省去。

三、帶多個參數

根據輸入的運算符計算兩個數的運算,並返回結果

interface Model4{
    String func(int a, int b, String oper);
}
 Model4 md4 = (a, b, s) -> {
      String res = "";
      if("+".equals(s)){
            res = ( a+b ) + "";
      }else if("-".equals(s)){
            res = ( a-b ) + "";
      }else if("*".equals(s)){
            res = ( a*b ) + "";
      }else if("/".equals(s)){
            res = ( a/b ) + ""; // 暫不考慮除0的狀況
      }else{
            res =  "操做有失誤";
      }
      return res;
 };
 System.out.println(md4.func(1,1,"+"));

以上例子爲多個參數的Lambda表達式,其中省略掉了每個參數的類型,編譯器自動推斷。多條語句時實現體的{}不能省。

3、Lambda做爲參數

在Java8以前,接口能夠做爲方法參數傳入,執行時必須提供接口實現類的實例。從java8開始,Lambda能夠做爲接口方法實現,看成參數傳入,不管從形式上仍是實際上都省去了對象的建立。使代碼更加的緊湊簡單高效。

一、定義接口

在接口中,必須有且僅有一個抽象方法,以肯定Lambda模板

// 無參無返回值的方法
interface LambdaInterface1{
    void printString();
}
// 帶參無返回值的方法
interface  LambdaInterface2{
    void printString(String str);
}

二、定義方法接收參數

在某方法中須要使用接口做爲參數

// 無參
  public static void testLambda(LambdaInterface1 lam1){
    lam1.printString();
  }
  // 帶參
  public static void testLambda2(String s,LambdaInterface2 lam2){
    lam2.printString(s);
  }

三、Lambda表達式做爲參數傳入

// 無參Lambda做爲參數
testLambda(()->{
    System.out.println("能夠簡單,能夠複雜");
});
// 帶參Lambda做爲參數
testLambdaParam("hello",(a)->{
    System.out.println(a);
});

4、Lambda中使用變量

在Lambda中能夠定義本身的局部變量,也可使用外層方法的局部變量,還可使用屬性。這一點也不難理解,既然是一個方法的實現,只寫了一個代碼塊,那麼使用自己所屬方法的局部變量和類的屬性也並不過度。

public static void main(String[] args) {
    List<String> strs = new ArrayList<String>(){
        {
            add("aaa");
            add("bbb");
            add("ccc");
        }
    };
    int j = 1;
    strs.forEach((str)->{
        int i = 0;
        System.out.println(str + "  " + i + "  " + j);
    });
}

5、Lambda類型推斷

一、類型檢查

Lambda的類型是從使用Lambda的上下文推斷出來的。 Lambda表達式的參數與函數式接口內方法的參數,返回值類型相互對應。Lambda表達式須要的類型,或者說Lambda實現的那個函數式接口稱之爲目標類型。

二、類型推斷

利用目標類型來檢查一個Lambda是否能夠用於某個特定的上下文,推斷Lambda參數的類型。

6、Lambda表達式實戰

熱銷商品排序

圖片描述

排序對於久經開發的你來講可能並不陌生,假如原來你作過電商項目,相信對於電商場景下的商品記錄排序操做頗有感情,下面咱們使用Lambda 來看看熱銷商品排序的操做。

測試數據這裏以手機測試數據爲例

/**
* 實際開發數據一般從數據庫獲取
* 這裏使用測試數據
*/
Goods g01=new Goods(1,"小米9",1789,200, BigDecimal.valueOf(2500));
Goods g02=new Goods(2,"華爲Mate20",5000,3000, BigDecimal.valueOf(7000));
Goods g03=new Goods(3,"OPPO R17",2000,2827, BigDecimal.valueOf(1500));
Goods g04=new Goods(4,"魅族 Note9",2000,1600, BigDecimal.valueOf(1600));
Goods g05=new Goods(5,"一加6T",8000,5000, BigDecimal.valueOf(3500));
List<Goods> goods= Arrays.asList(g01,g02,g03,g04,g05);
  • Collections.sort 靜態方法實現排序

    Collections.sort(goods,(g1,g2)->g1.getSale()-g2.getSale());

  • List.sort 默認方法實現集合排序

圖片描述

// 使用Lambda 對商品記錄按銷量進行排序
goods.sort((g1,g2)->g1.getSale()-g2.getSale());
  • Stream.sorted 方法實現元素排序

    // 多個條件排序狀況 Lambda 配置Stream 銷量+價格排序 銷量相等時按照價格排序
    goods =goods.stream().sorted((g1,g2)->g1.getSale()-g2.getSale())
    .sorted((g1,g2)->g1.getPrice().compareTo(g2.getPrice()))
    .collect(Collectors.toList());

日誌輸出優化

圖片描述

       對於項目開發日誌打印是一項不可獲取的模塊,不管實在開發階段仍是項目部署上線後,日誌信息的輸出對於開發人員來以及運維人員來講都是一項重要的參考指標。

日誌輸出場景這裏以用戶模塊UserService 爲例,如下爲優化前的日誌輸出代碼:

public String login(String userName, String userPwd) {
    logger.info("UserService 接收到參數-->" + userName + "," + userPwd);
    /**
     * 登陸邏輯省略
     */
    return "login";
}

日誌級別設置到debug,在開發階段方便查看後端接收到的參數信息。仔細分析這裏的日誌代碼,能夠看到當日志級別設置爲info 時 debug 日誌不該該執行輸出操做,同時這裏調用debug 方法時,對於傳入的字符串參數須要做對應的拼接操做,纔會傳入過來。當訪問的狀況在商城項目作活動狀況下 這裏的狀況有可能會變得很糟糕:全部的debug 信息所有輸出 同時會有大量字符串拼接操做,會影響整個應用程序的執行性能。

日誌輸出場景這裏以用戶模塊UserService 爲例,日誌輸出代碼優化

  • 輸出日誌前判斷日誌輸出級別
  • 藉助Lambda延遲日誌內容輸出

    /**

    • 添加info方法
    • 判斷日誌打印級別
    • 當條件成立時 輸出日誌信息
    • @param logger
    • @param message
*/
public   void info(Log logger, Supplier<String> message){
    if(logger.isInfoEnabled()){
        logger.info(message.get());
    }
}

public String login(String userName, String userPwd) {
    //logger.info("UserService 接收到參數-->" + userName + "," + userPwd);
    // 延遲Lambda 表達式執行  只有肯定
    info(logger,()->"UserService 接收到參數-->" + userName + "," + userPwd);
    return "login";
}

Lambda優點與使用場景

Lambda表達式的引入取代了匿名內部類,使得代碼變得簡潔、緊湊,同時Lambda的惰性特色,在開發時可以提升應用程序的執行性能。

對於Lambda的應用場景,從代碼結構來講一般是結合函數式接口來使用,使得開發是面向函數來進行編程,也是Java8引入的一種新的思想-函數式編程(後續會介紹)。同時還會結合前面講到的接口默認方法提現到應用開發中。

樂字節原創,轉載請註明出處和做者。更多教程和資料請上 騰訊課堂:樂字節

相關文章
相關標籤/搜索