Java8 lambda表達式概念以及使用lambda操作list集合


一、Lambda 表達式基本概況

(1)Lambda 表達式,也可稱為閉包,它是推動Java 8發布的最重要新特性;Lambda 允許把函數作為一個方法的參數(函數作為參數傳遞進方法中);使用 Lambda 表達式可以使代碼變的更加簡潔緊湊。

語法如下:

(parameters) -> expression
或
(parameters) ->{ statements; }

(2)以下是lambda表達式的重要特征:

  • 可選類型聲明:不需要聲明參數類型,編譯器可以統一識別參數值。
  • 可選的參數圓括號:一個參數無需定義圓括號,但多個參數需要定義圓括號。
  • 可選的大括號:如果主體包含了一個語句,就不需要使用大括號。
  • 可選的返回關鍵字:如果主體只有一個表達式返回值則編譯器會自動返回值,大括號需要指明表達式返回了一個數值。

(3)使用 Lambda 表達式需要了解以下幾點:

  • Lambda 表達式主要用來定義行內執行的方法類型接口,例如,一個簡單方法接口。
  • Lambda 表達式免去了使用匿名方法的麻煩,並且給予Java簡單但是強大的函數化的編程能力。
  • 將匿名內部類簡寫成lambda表達式的依據:
    • 能夠使用Lambda的依據是必須有相應的函數接口(函數接口,是指內部有抽象方法的接口)。實際上Lambda的類型就是對應函數接口的類型。
    • Lambda表達式另一個依據是類型推斷機制,在上下文信息足夠的情況下,編譯器可以推斷出參數表的類型,而不需要顯式指明。注意,Java是強類型語言,每個變量和對象都必須有明確的類型。
  • 如果使用了匿名內部類,編譯過后將會產生一個以$符號結尾的class字節碼文件,而使用lambda則不會產生這樣的class字節碼文件,只會產生一個class文件,這是因為jvm將Lambda表達式封裝成了主類的一個私有方法,並通過invokedynamic指令進行調用。

(4)變量作用域

  • lambda 表達式只能引用標記了 final的外層局部變量,這就是說不能在lambda內部修改定義在域外的局部變量,否則會報編譯錯誤。
  • lambda 表達式內的局部變量可以不用聲明為final,但是必須不可被后面的代碼修改(即隱性的具有final 的語義)。
  • 在Lambda 表達式當中不允許聲明一個與局部變量同名的參數或者局部變量。

二、Lambda and Collections

我們先從最熟悉的Java集合框架(Java Collections Framework, JCF)開始說起。

為引入Lambda表達式,Java8新增了java.util.funcion包,里面包含常用的函數接口,這是Lambda表達式的基礎,Java集合框架也新增部分接口,以便與Lambda表達式對接。

首先回顧一下Java集合框架的接口繼承結構:

上圖中綠色標注的接口類,表示在Java8中加入了新的接口方法,當然由於繼承關系,他們相應的子類也都會繼承這些新方法。下表詳細列舉了這些方法。

接口名 Java8新加入的方法
Collection removeIf()、spliterator()、 stream()、 parallelStream() 、forEach()
List replaceAll() 、sort()
Map getOrDefault()、 forEach()、 replaceAll()、 putIfAbsent() 、remove()、 replace() 、computeIfAbsent() 、computeIfPresent()、 compute()、 merge()

這些新加入的方法大部分要用到java.util.function包下的接口,這意味着這些方法大部分都跟Lambda表達式相關。

Collection中的新方法

(1)forEach()方法 以list為例說明forEach()方法。

該方法的簽名為void forEach(Consumer<? super E> action)作用是對容器中的每個元素執行action指定的動作,其中Consumer是個函數接口,里面只有一個待實現方法void accept(T t)

需求:假設有一個字符串列表,需要打印出其中所有長度大於3的字符串。

List<String> list = new ArrayList<>(Arrays.asList("i", "love", "liangliang"));
// 使用增強for循環實現
for (String s : list) {
    if (s.length() > 3) {
        System.out.println("使用增強for循環實現:" + s);
    }
}

// 使用匿名內部類實現Comsumer接口
list.forEach(new Consumer<String>() {
    @Override
    public void accept(String s) {
        if (s.length() > 3) {
            System.out.println("使用Consumer內部類實現:" + s);
        }
    }
});
// 上述代碼調用forEach()方法,並使用匿名內部類實現Comsumer接口。

// lambda寫法
list.forEach(s -> {
    if (s.length() > 3) {
        System.out.println("使用lambda表達式實現:" + s);
    }
});
// 上述代碼給forEach()方法傳入一個Lambda表達式,我們不需要知道accept()方法,也不需要知道Consumer接口,類型推導幫我們做了一切。

提示:在idea中,如果使用了匿名內部類實現,idea會提示將它轉換成lambda表達式實現,因此如果暫時忘記了如何寫lambda表達式可以利用這一特性快速轉成lambda表達式。


(2)removeIf() 以list為例說明removeIf()方法。

該方法簽名為boolean removeIf(Predicate<? super E> filter),作用是刪除容器中所有滿足filter指定條件的元素,其中Predicate是一個函數接口,里面只有一個待實現方法boolean test(T t)

需求:假設有一個字符串列表,需要刪除其中所有長度大於3的字符串。

List<String> list = new ArrayList<>(Arrays.asList("i", "love", "liangliang"));

// 我們知道如果需要在迭代過程中對容器進行刪除操作必須使用迭代器,否則會拋出ConcurrentModificationException,所以迭代器實現如下
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    if (iterator.next().length() > 3) {
        iterator.remove();
    }
}
// 遍歷出來,使用lambda表達式
list.forEach(System.out::println);

// 使用removeIf()方法,並使用匿名內部類實現Precicate接口
list.removeIf(new Predicate<String>() {
    @Override
    public boolean test(String s) {
        return s.length() > 3;
    }
});
list.forEach(System.out::println);

// 使用lambda表達式實現 刪除長度大於3的元素
list.removeIf(str -> str.length()>3);
list.forEach(System.out::println);

不得不說idea真的太智能了,提示檢測功能簡直了,不用說了今后對於idea的提示建議90%我都接受,太智能了。


(3)replaceAll() 以list為例說明replaceAll()方法。

該方法簽名為void replaceAll(UnaryOperator<E> operator)作用是對每個元素執行operator指定的操作,並用操作結果來替換原來的元素。其中UnaryOperator是一個函數接口,里面只有一個待實現函數T apply(T t)

需求:假設有一個字符串列表,將其中所有長度大於3的元素轉換成大寫,其余元素不變。

List<String> list = new ArrayList<>(Arrays.asList("i", "love", "liangliang"));

// Java7及之前的實現方式,采用for循環實現
for (int i = 0; i < list.size(); i++) {
    String str = list.get(i);
    if (str.length() > 3) {
        list.set(i, str.toUpperCase());
    }
}
list.forEach(System.out::println);

// 調用replaceAll()方法,並使用匿名內部類實現UnaryOperator接口
list.replaceAll(new UnaryOperator<String>() {
    @Override
    public String apply(String s) {
        if (s.length() > 3) {
            return s.toUpperCase();
        }
        return s;
    }
});
list.forEach(System.out::println);

// 使用Lambda表達式實現
list.replaceAll(s -> {
    if (s.length() > 3) {
        return s.toUpperCase();
    } else {
        return s;
    }
});
list.forEach(System.out::println);

(4)sort() 以list為例說明sort()方法。

該方法定義在List接口中,方法簽名為void sort(Comparator<? super E> c)該方法根據c指定的比較規則對容器元素進行排序Comparator接口我們並不陌生,其中有一個方法int compare(T o1, T o2)需要實現,顯然該接口是個函數接口。

需求:假設有一個字符串列表,按照字符串長度增序對元素排序。

List<String> list = new ArrayList<>(Arrays.asList("love", "liangliang", "i"));

// 按元素長度進行排序
// 由於Java7以及之前sort()方法在Collections工具類中,所以代碼要這樣寫:
list.sort(new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
        // 默認是升序排序
        return o1.length() - o2.length();
    }
});
// 輸出排序后的結果
list.forEach(System.out::println);

// 現在可以直接使用List.sort()方法,結合Lambda表達式實現對list元素排序
list.sort((str1, str2) -> str1.length() - str2.length());
// 還可以進一步簡化成如下進行排序
list.sort(Comparator.comparingInt(String::length));
// 輸出排序后的結果
list.forEach(System.out::println);

這里的::表示引用,因為如果只是按照默認的或者參數只有一個的情況下idea會提示你進行優化,用::引用來進行替換就行了。

參考博文:
(1)https://objcoding.com/2019/03/04/lambda/ (非常詳細,值得仔細閱讀)
(2)https://www.runoob.com/java/java8-lambda-expressions.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM