天天看點

相信我,使用 Stream 真的可以讓代碼更優雅

作者:架構屆的郭德綱

前言

雖然 stream在 Java8 中就已經被引入,但是大多數人卻沒有去使用這個十分有用的特性,本文就通過介紹幾個通過使用stream讓代碼更簡潔、可讀,來讓你了解stream的友善之處。

技巧

數組轉集合

相信經常刷LeetCode的小夥伴,偶爾會遇到需要将List與基本類型數組進行互轉的情況,然後就需要寫像下面這樣的代碼:

// 将 List 元素存儲到數組中
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
int[] arr = new int[list.size()];
Integer[] temp = list.toArray(new Integer[0]);
for (int i = 0; i < temp.length; i++) {
 arr[i] = temp[i];
}

// 将數組元素 存儲到 List 中
int[] arr = {1, 2, 3, 4, 5};
List<Integer> list = new ArrayList<>();
for (int val : arr) {
 list.add(val);
}
           

以上兩個轉換雖然寫着還不算麻煩,但是每次都需要寫一個循環,尤其在數組轉List的時候還需要使用一個臨時數組,都會讓人看着很不舒服,但是如果使用了stream就會大不一樣,用stream實作了相同功能的代碼如下:

// 将 List 元素存儲到數組中
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
int[] arr = list.stream().mapToInt(Integer::intValue).toArray();

// 将數組元素 存儲到 List 中
int[] arr = {1, 2, 3, 4, 5};
List<Integer> list = IntStream.of(arr).boxed().collect(Collectors.toList());
           

可以發現通過使用stream,我們能夠在寫代碼的時候更加連貫,代碼也更加可靠易維護,注意力也可以放在業務功能上,相信各位就算對lambda文法并不是太熟悉,在閱讀上面代碼的時候,也很容易能夠看懂。

統計數組元素中的個數

假設我們現在需要統計并輸出一個有重複元素的數組中每個元素及對應元素出現的個數,相信各位都能夠想到,我們使用一個Map就很容易解決這個問題,代碼如下:

String[] arr = {"a", "c", "a", "b", "d", "c"};
Map<String, Integer> map = new HashMap<>();
for (String s : arr) {
    if (map.containsKey(s)) {
        map.put(s, map.get(s) + 1);
    } else {
        map.put(s, 1);
    }
}
map.forEach((key, value) -> System.out.println(key + " : " + value));
           

如果對Map中的API更加熟悉的小夥伴,可能會寫出下面這個更加簡潔的代碼:

String[] arr = {"a", "c", "a", "b", "d", "c"};
Map<String, Integer> map = new HashMap<>();
for (String s : arr) {
    map.put(s, map.getOrDefault(s, 0) + 1);
}
map.forEach((key, value) -> System.out.println(key + " : " + value));
           

但是,如果使用stream,我們還能寫出更加簡潔的代碼,同樣不需要寫煩人的循環了,而且隻需兩行代碼即可(為了提高可讀性,進行了換行):

String[] arr = {"a", "c", "a", "b", "d", "c"};
Stream.of(arr)
      .collect(Collectors.toMap(k -> k, k -> 1, Integer::sum))
      .forEach((k, v) -> System.out.println(k + " : " + v));
           

注意

在上面的代碼中,Collectors.toMap(k -> k, k -> 1, Integer::sum)這一部分可能不好了解,對于這裡面的三個參數,第一個參數代表将arr中的每一個元素作為Map中的key,第二個參數代表每一個key所對應的value,在這裡每一個元素都對應個數1,第三個參數代表,如果存在相同的key,該如何進行合并,這裡通過使用Integer::sum,代表将具有相同key的元素進行合并時,其value進行相加,這樣便實作了每個元素個數的統計。

基本資料類型的數組自定義排序

有時我們會遇到對基本資料類型的數組進行自定義排序的情況,不同于包裝類型的數組和集合可以直接使用比較器,我們隻能通過将基本數組類型的數組轉為包裝類型或者存儲在集合中,在排序完成後再轉為基本類型的數組,再者,我們隻能通過手寫排序算法,修改排序算法中的比較進行實作。

不管是哪種方法,我們都沒辦法将精力放在邏輯功能上,必須寫一些額外的代碼,甚至是修改底層邏輯,就像下面的代碼一樣(實作數組逆序):

int[] arr = {1, 5, 9, 7, 2, 3, 7, -1, 0, 3};
// 将數組轉為包裝類型再進行自定義排序
Integer[] temp = new Integer[arr.length];
for (int i = 0; i < arr.length; i++) {
    temp[i] = arr[i];
}
Arrays.sort(temp, Comparator.reverseOrder());
for (int i = 0; i < temp.length; i++) {
    arr[i] = temp[i];
}

// 将數組轉為集合類型再進行自定義排序
List<Integer> list = new ArrayList<>();
for (int val : arr) {
    list.add(val);
}
list.sort(Collections.reverseOrder());
for (int i = 0; i < list.size(); i++) {
    arr[i] = list.get(i);
}

// 通過手寫排序算法修改比較規則實作
// 為了讓代碼更加簡潔,使用了最暴力且沒有優化的冒泡排序
int[] arr = {1, 5, 9, 7, 2, 3, 7, -1, 0, 3};
for (int i = 0; i < arr.length; i++) {
    for (int j = 0; j < arr.length - i - 1; j++) {
        if (arr[j] < arr[j + 1]) {
            int temp = arr[j];
            arr[j] = arr[j + 1];
            arr[j + 1] = temp;
        }
    }
}
           

可以發現以上幾種方法,我們都需要寫很多代碼,無法将注意力集中在設計自定義排序這個問題上,但是通過使用stream,我們就可以寫出下面這樣簡潔的代碼(如果願意的話,你也可以把一系列的鍊式操作寫在一行上,但為了代碼的可讀性,不建議那麼做):

int[] arr = {1, 5, 9, 7, 2, 3, 7, -1, 0, 3};
arr = IntStream.of(arr)
               .boxed()
               .sorted(Comparator.reverseOrder())
               .mapToInt(Integer::intValue)
               .toArray();
           

注意

在這裡其實為了實作數組的逆序,我們隻需要調用Arrays的sort方法,然後再進行數組元素的反轉即可,不過因為是為了講解自定義排序,大多數情況下不會是數組逆序這麼簡單,是以我就寫了更加通用一些的代碼。

統計數組中前 k 個個高頻元素

在最後,我們通過一道題來進行實戰以便更好地體驗stream的強大之處,當然我們在練習該題的時候,更需要從算法的角度去考慮該題的解法,不過在本文,我們主要為了講解stream的使用,是以就不去考慮算法的東西了,而如果使用stream,我們就可以寫出下面這樣簡單易懂的代碼:

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        return Arrays.stream(nums)
                     .boxed()
                     .collect(Collectors.toMap(e -> e, e -> 1, Integer::sum))
                     .entrySet()
                     .stream()
                     .sorted((m1, m2) -> m2.getValue() - m1.getValue())
                     .limit(k)
                     .mapToInt(Map.Entry::getKey)
                     .toArray();
    }
}
           

總結

本文介紹了幾個簡單、實用的stream使用技巧,當然stream的應用遠不止此,希望通過本文,能夠激發起你學習stream的興趣,本文若有錯誤之處,也歡迎你的指正。

來源:blog.csdn.net/qq_41698074/article/details/108502976