<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          巧用Stream優(yōu)化老代碼,太清爽了!

          共 6786字,需瀏覽 14分鐘

           ·

          2022-01-23 01:33

          點(diǎn)擊關(guān)注公眾號(hào),Java干貨及時(shí)送達(dá)

          作者:何甜甜在嗎

          來(lái)源:https://juejin.cn/post/6844903945005957127


          Java8的新特性主要是Lambda表達(dá)式和流,當(dāng)流和Lambda表達(dá)式結(jié)合起來(lái)一起使用時(shí),因?yàn)榱魃昝魇教幚頂?shù)據(jù)集合的特點(diǎn),可以讓代碼變得簡(jiǎn)潔易讀



          放大招,流如何簡(jiǎn)化代碼




          果有一個(gè)需求,需要對(duì)數(shù)據(jù)庫(kù)查詢(xún)到的菜肴進(jìn)行一個(gè)處理:

          • 篩選出卡路里小于400的菜肴

          • 對(duì)篩選出的菜肴進(jìn)行一個(gè)排序

          • 獲取排序后菜肴的名字

          菜肴:Dish.java

          public class Dish {    private String name;    private boolean vegetarian;    private int calories;    private Type type;
          // getter and setter}
          Java8以前的實(shí)現(xiàn)方式
          private List beforeJava7(List dishList) {        List lowCaloricDishes = new ArrayList<>();
          //1.篩選出卡路里小于400的菜肴 for (Dish dish : dishList) { if (dish.getCalories() < 400) { lowCaloricDishes.add(dish); }????????} //2.對(duì)篩選出的菜肴進(jìn)行排序 Collections.sort(lowCaloricDishes, new Comparator() { @Override public int compare(Dish o1, Dish o2) { return Integer.compare(o1.getCalories(), o2.getCalories()); } });
          //3.獲取排序后菜肴的名字 List lowCaloricDishesName = new ArrayList<>(); for (Dish d : lowCaloricDishes) { lowCaloricDishesName.add(d.getName());????????} return lowCaloricDishesName; }
          Java8之后的實(shí)現(xiàn)方式
           private List afterJava8(List dishList) {        return dishList.stream()                .filter(d -> d.getCalories() < 400)  //篩選出卡路里小于400的菜肴                .sorted(comparing(Dish::getCalories))  //根據(jù)卡路里進(jìn)行排序                .map(Dish::getName)  //提取菜肴名稱(chēng)                .collect(Collectors.toList()); //轉(zhuǎn)換為L(zhǎng)ist    }
          不拖泥帶水,一氣呵成,原來(lái)需要寫(xiě)24代碼實(shí)現(xiàn)的功能現(xiàn)在只需5行就可以完成了

          高高興興寫(xiě)完需求這時(shí)候又有新需求了,新需求如下:

          對(duì)數(shù)據(jù)庫(kù)查詢(xún)到的菜肴根據(jù)菜肴種類(lèi)進(jìn)行分類(lèi),返回一個(gè)Map>的結(jié)果

          這要是放在jdk8之前肯定會(huì)頭皮發(fā)麻。

          Java8以前的實(shí)現(xiàn)方式

          private static Map> beforeJdk8(List dishList) {    Map> result = new HashMap<>();
          for (Dish dish : dishList) { //不存在則初始化 if (result.get(dish.getType())==null) { List dishes = new ArrayList<>(); dishes.add(dish); result.put(dish.getType(), dishes); } else { //存在則追加 result.get(dish.getType()).add(dish); }????} return result;}

          還好jdk8有Stream,再也不用擔(dān)心復(fù)雜集合處理需求。

          Java8以后的實(shí)現(xiàn)方式

          private static MapList> afterJdk8(List dishList) {    return dishList.stream().collect(groupingBy(Dish::getType));}

          又是一行代碼解決了需求,忍不住大喊Stream API牛批 看到流的強(qiáng)大功能了吧,接下來(lái)將詳細(xì)介紹流。

          什么是流




          流是從支持?jǐn)?shù)據(jù)處理操作的源生成的元素序列,源可以是數(shù)組、文件、集合、函數(shù)。流不是集合元素,它不是數(shù)據(jù)結(jié)構(gòu)并不保存數(shù)據(jù),它的主要目的在于計(jì)算。

          如何生成流




          生成流的方式主要有五種。

          1.通過(guò)集合生成,應(yīng)用中最常用的一種

          List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);Stream<Integer> stream = integerList.stream();

          通過(guò)集合的stream方法生成流。

          2.通過(guò)數(shù)組生成

          int[] intArr = new int[]{1, 2, 3, 4, 5};IntStream stream = Arrays.stream(intArr);

          通過(guò)Arrays.stream方法生成流,并且該方法生成的流是數(shù)值流【即IntStream】而不是Stream。補(bǔ)充一點(diǎn)使用數(shù)值流可以避免計(jì)算過(guò)程中拆箱裝箱,提高性能。

          Stream API提供了mapToInt、mapToDouble、mapToLong三種方式將對(duì)象流【即Stream】轉(zhuǎn)換成對(duì)應(yīng)的數(shù)值流,同時(shí)提供了boxed方法將數(shù)值流轉(zhuǎn)換為對(duì)象流。

          3.通過(guò)值生成

          Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);

          通過(guò)Stream的of方法生成流,通過(guò)Stream的empty方法可以生成一個(gè)空流

          4.通過(guò)文件生成

          Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())

          通過(guò)Files.line方法得到一個(gè)流,并且得到的每個(gè)流是給定文件中的一行

          5.通過(guò)函數(shù)生成 提供了iterate和generate兩個(gè)靜態(tài)方法從函數(shù)中生成流

          iterator

          Stream stream = Stream.iterate(0, n -> n + 2).limit(5);

          iterate方法接受兩個(gè)參數(shù),第一個(gè)為初始化值,第二個(gè)為進(jìn)行的函數(shù)操作,因?yàn)閕terator生成的流為無(wú)限流,通過(guò)limit方法對(duì)流進(jìn)行了截?cái)?,只生?個(gè)偶數(shù)

          generator

          Stream stream = Stream.generate(Math::random).limit(5);

          generate方法接受一個(gè)參數(shù),方法參數(shù)類(lèi)型為Supplier,由它為流提供值。generate生成的流也是無(wú)限流,因此通過(guò)limit對(duì)流進(jìn)行了截?cái)唷?/span>

          流的操作類(lèi)型




          流的操作類(lèi)型主要分為兩種。

          1.中間操作

          一個(gè)流可以后面跟隨零個(gè)或多個(gè)中間操作。其目的主要是打開(kāi)流,做出某種程度的數(shù)據(jù)映射/過(guò)濾,然后返回一個(gè)新的流,交給下一個(gè)操作使用。這類(lèi)操作都是惰性化的,僅僅調(diào)用到這類(lèi)方法,并沒(méi)有真正開(kāi)始流的遍歷,真正的遍歷需等到終端操作時(shí),常見(jiàn)的中間操作有下面即將介紹的filter、map等。

          2.終端操作

          一個(gè)流有且只能有一個(gè)終端操作,當(dāng)這個(gè)操作執(zhí)行后,流就被關(guān)閉了,無(wú)法再被操作,因此一個(gè)流只能被遍歷一次,若想在遍歷需要通過(guò)源數(shù)據(jù)在生成流。終端操作的執(zhí)行,才會(huì)真正開(kāi)始流的遍歷。如下面即將介紹的count、collect等。

          流的使用




          流的使用將分為終端操作和中間操作進(jìn)行介紹。

          中間操作

          filter篩選

           List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5); Stream<Integer> stream = integerList.stream().filter(i -> i > 3);

          通過(guò)使用filter方法進(jìn)行條件篩選,filter的方法參數(shù)為一個(gè)條件

          distinct去除重復(fù)元素

          List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5);Stream<Integer> stream = integerList.stream().distinct();

          通過(guò)distinct方法快速去除重復(fù)的元素

          limit返回指定流個(gè)數(shù)

           List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5); Stream<Integer> stream = integerList.stream().limit(3);

          通過(guò)limit方法指定返回流的個(gè)數(shù),limit的參數(shù)值必須>=0,否則將會(huì)拋出異常

          skip跳過(guò)流中的元素

           List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5); Stream<Integer> stream = integerList.stream().skip(2);

          通過(guò)skip方法跳過(guò)流中的元素,上述例子跳過(guò)前兩個(gè)元素,所以打印結(jié)果為2,3,4,5,skip的參數(shù)值必須>=0,否則將會(huì)拋出異常

          map流映射

          所謂流映射就是將接受的元素映射成另外一個(gè)元素。

          List<String> stringList = Arrays.asList("Java 8", "Lambdas",  "In", "Action");Stream stream = stringList.stream().map(String::length);

          通過(guò)map方法可以完成映射,該例子完成中String -> Integer的映射,之前上面的例子通過(guò)map方法完成了Dish->String的映射。

          flatMap流轉(zhuǎn)換

          將一個(gè)流中的每個(gè)值都轉(zhuǎn)換為另一個(gè)流。

          List wordList = Arrays.asList("Hello", "World");List strList = wordList.stream()        .map(w -> w.split(" "))        .flatMap(Arrays::stream)        .distinct()        .collect(Collectors.toList());

          map(w -> w.split(" "))的返回值為Stream,我們想獲取Stream,可以通過(guò)flatMap方法完成Stream?->Stream的轉(zhuǎn)換

          元素匹配

          提供了三種匹配方式。

          1.allMatch匹配所有

          List integerList = Arrays.asList(1, 2, 3, 4, 5);if (integerList.stream().allMatch(i -> i > 3)) {    System.out.println("值都大于3");}

          通過(guò)allMatch方法實(shí)現(xiàn)。

          2.anyMatch匹配其中一個(gè)

          List integerList = Arrays.asList(1, 2, 3, 4, 5);if (integerList.stream().anyMatch(i -> i > 3)) {    System.out.println("存在大于3的值");}

          等同于:

          for (Integer i : integerList) {    if (i > 3) {        System.out.println("存在大于3的值");        break;    }}

          存在大于3的值則打印,java8中通過(guò)anyMatch方法實(shí)現(xiàn)這個(gè)功能。

          3.noneMatch全部不匹配

          List integerList = Arrays.asList(1, 2, 3, 4, 5);if (integerList.stream().noneMatch(i -> i > 3)) {    System.out.println("值都小于3");}

          通過(guò)noneMatch方法實(shí)現(xiàn)。

          終端操作

          統(tǒng)計(jì)流中元素個(gè)數(shù)

          1.通過(guò)count

          List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);Long result = integerList.stream().count();

          通過(guò)使用count方法統(tǒng)計(jì)出流中元素個(gè)數(shù)。

          2.通過(guò)counting

          List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);Long result = integerList.stream().collect(counting());

          最后一種統(tǒng)計(jì)元素個(gè)數(shù)的方法在與collect聯(lián)合使用的時(shí)候特別有用。

          查找

          提供了兩種查找方式。

          1.findFirst查找第一個(gè)

          List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);Optional<Integer> result = integerList.stream().filter(i -> i > 3).findFirst();

          通過(guò)findFirst方法查找到第一個(gè)大于三的元素并打印。

          2.findAny隨機(jī)查找一個(gè)

          List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);Optional<Integer> result = integerList.stream().filter(i -> i > 3).findAny();

          通過(guò)findAny方法查找到其中一個(gè)大于三的元素并打印,因?yàn)閮?nèi)部進(jìn)行優(yōu)化的原因,當(dāng)找到第一個(gè)滿(mǎn)足大于三的元素時(shí)就結(jié)束,該方法結(jié)果和findFirst方法結(jié)果一樣。提供findAny方法是為了更好的利用并行流,findFirst方法在并行上限制更多【本篇文章將不介紹并行流】。

          reduce將流中的元素組合起來(lái)

          假設(shè)我們對(duì)一個(gè)集合中的值進(jìn)行求和

          jdk8之前

          int sum = 0;for (int i : integerList) {sum += i;}

          jdk8之后通過(guò)reduce進(jìn)行處理

          int sum = integerList.stream().reduce(0, (a, b) -> (a + b));

          一行就可以完成,還可以使用方法引用簡(jiǎn)寫(xiě)成:

          int sum = integerList.stream().reduce(0, Integer::sum);

          reduce接受兩個(gè)參數(shù),一個(gè)初始值這里是0,一個(gè)BinaryOperator accumulator來(lái)將兩個(gè)元素結(jié)合起來(lái)產(chǎn)生一個(gè)新值,

          另外reduce方法還有一個(gè)沒(méi)有初始化值的重載方法。

          獲取流中最小最大值

          通過(guò)min/max獲取最小最大值

          Optional<Integer> min = menu.stream().map(Dish::getCalories).min(Integer::compareTo);Optional<Integer> max = menu.stream().map(Dish::getCalories).max(Integer::compareTo);

          也可以寫(xiě)成:

          OptionalInt min = menu.stream().mapToInt(Dish::getCalories).min();OptionalInt max = menu.stream().mapToInt(Dish::getCalories).max();

          min獲取流中最小值,max獲取流中最大值,方法參數(shù)為Comparator comparator。

          通過(guò)minBy/maxBy獲取最小最大值

          Optional min = menu.stream().map(Dish::getCalories).collect(minBy(Integer::compareTo));Optional max = menu.stream().map(Dish::getCalories).collect(maxBy(Integer::compareTo));

          minBy獲取流中最小值,maxBy獲取流中最大值,方法參數(shù)為Comparator comparator。

          通過(guò)reduce獲取最小最大值

          Optional<Integer> min = menu.stream().map(Dish::getCalories).reduce(Integer::min);Optional<Integer> max = menu.stream().map(Dish::getCalories).reduce(Integer::max);

          求和

          通過(guò)summingInt

          int sum = menu.stream().collect(summingInt(Dish::getCalories));

          如果數(shù)據(jù)類(lèi)型為double、long,則通過(guò)summingDouble、summingLong方法進(jìn)行求和。

          通過(guò)reduce

          int sum = menu.stream().map(Dish::getCalories).reduce(0, Integer::sum);
          通過(guò)sum
          int sum = menu.stream().mapToInt(Dish::getCalories).sum();
          在上面求和、求最大值、最小值的時(shí)候,對(duì)于相同操作有不同的方法可以選擇執(zhí)行。可以選擇collect、reduce、min/max/sum方法,推薦使用min、max、sum方法。因?yàn)樗詈?jiǎn)潔易讀,同時(shí)通過(guò)mapToInt將對(duì)象流轉(zhuǎn)換為數(shù)值流,避免了裝箱和拆箱操作。

          通過(guò)averagingInt求平均值

          double average = menu.stream().collect(averagingInt(Dish::getCalories));

          如果數(shù)據(jù)類(lèi)型為double、long,則通過(guò)averagingDouble、averagingLong方法進(jìn)行求平均。

          通過(guò)summarizingInt同時(shí)求總和、平均值、最大值、最小值

          IntSummaryStatistics intSummaryStatistics = menu.stream().collect(summarizingInt(Dish::getCalories));double average = intSummaryStatistics.getAverage();  //獲取平均值int min = intSummaryStatistics.getMin();  //獲取最小值int max = intSummaryStatistics.getMax();  //獲取最大值long sum = intSummaryStatistics.getSum();  //獲取總和

          如果數(shù)據(jù)類(lèi)型為double、long,則通過(guò)summarizingDouble、summarizingLong方法。

          通過(guò)foreach進(jìn)行元素遍歷

          List integerList = Arrays.asList(1, 2, 3, 4, 5);integerList.stream().forEach(System.out::println);
          而在jdk8之前實(shí)現(xiàn)遍歷:
          for (int i : integerList) {    System.out.println(i);}
          jdk8之后遍歷元素來(lái)的更為方便,原來(lái)的for-each直接通過(guò)foreach方法就能實(shí)現(xiàn)了

          返回集合

          List<String> strings = menu.stream().map(Dish::getName).collect(toList());Set<String> sets = menu.stream().map(Dish::getName).collect(toSet());

          只舉例了一部分,還有很多其他方法 jdk8之前

           List<String> stringList = new ArrayList<>();    Set<String> stringSet = new HashSet<>();    for (Dish dish : menu) {        stringList.add(dish.getName());        stringSet.add(dish.getName());}

          通過(guò)遍歷和返回集合的使用發(fā)現(xiàn)流只是把原來(lái)的外部迭代放到了內(nèi)部進(jìn)行,這也是流的主要特點(diǎn)之一。內(nèi)部迭代可以減少好多代碼量

          通過(guò)joining拼接流中的元素

          String result = menu.stream().map(Dish::getName).collect(Collectors.joining(", "));

          默認(rèn)如果不通過(guò)map方法進(jìn)行映射處理拼接的toString方法返回的字符串,joining的方法參數(shù)為元素的分界符,如果不指定生成的字符串將是一串的,可讀性不強(qiáng)。

          進(jìn)階通過(guò)groupingBy進(jìn)行分組

          Map>> result = dishList.stream().collect(groupingBy(Dish::getType));

          在collect方法中傳入groupingBy進(jìn)行分組,其中g(shù)roupingBy的方法參數(shù)為分類(lèi)函數(shù)。還可以通過(guò)嵌套使用groupingBy進(jìn)行多級(jí)分類(lèi)。

          Map> result = menu.stream().collect(groupingBy(Dish::getType,        groupingBy(dish -> {            if (dish.getCalories() <= 400) return CaloricLevel.DIET;                else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;                else return CaloricLevel.FAT;        })));
          進(jìn)階通過(guò)partitioningBy進(jìn)行分區(qū)

          分區(qū)是特殊的分組,它分類(lèi)依據(jù)是true和false,所以返回的結(jié)果最多可以分為兩組

          Map<Boolean, List> result = menu.stream().collect(partitioningBy(Dish :: isVegetarian))

          等同于

          Map<Boolean, List> result = menu.stream().collect(groupingBy(Dish :: isVegetarian))

          這個(gè)例子可能并不能看出分區(qū)和分類(lèi)的區(qū)別,甚至覺(jué)得分區(qū)根本沒(méi)有必要,換個(gè)明顯一點(diǎn)的例子:

          List integerList = Arrays.asList(1, 2, 3, 4, 5);Map<Boolean, List> result = integerList.stream().collect(partitioningBy(i -> i < 3));

          返回值的鍵仍然是布爾類(lèi)型,但是它的分類(lèi)是根據(jù)范圍進(jìn)行分類(lèi)的,分區(qū)比較適合處理根據(jù)范圍進(jìn)行分類(lèi)。

          總 結(jié)




          通過(guò)使用Stream API可以簡(jiǎn)化代碼,同時(shí)提高了代碼可讀性,趕緊在項(xiàng)目里用起來(lái)。講道理在沒(méi)學(xué)Stream API之前,誰(shuí)要是給我在應(yīng)用里寫(xiě)很多Lambda,Stream API,飛起就想給他一腳。

          我想,我現(xiàn)在可能愛(ài)上他了【嘻嘻】。同時(shí)使用的時(shí)候注意不要將聲明式和命令式編程混合使用,前幾天刷segment刷到一條:

          imango老哥說(shuō)的很對(duì),別用聲明式編程的語(yǔ)法干命令式編程的勾當(dāng)


          1、刪除知名開(kāi)源庫(kù)跑路,神秘Bug影響超2萬(wàn)個(gè)項(xiàng)目

          2、Windows11竟然推送1968年的驅(qū)動(dòng)?

          3、全中國(guó)一共有多少 IP 地址?

          4、Windows重要功能被閹割,全球用戶(hù)怒噴數(shù)月后微軟終于悔改

          5、牛逼!國(guó)產(chǎn)開(kāi)源的遠(yuǎn)程桌面火了,只有9MB 支持自建中繼器!

          6、摔到老三的 Java,未來(lái)在哪?

          7、真香!用 IDEA 神器看源碼,效率真高!

          點(diǎn)分享

          點(diǎn)收藏

          點(diǎn)點(diǎn)贊

          點(diǎn)在看

          瀏覽 32
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  五月丁香俺也去国产 | 少妇久久精品 | 免费一级A片奶好大 | 午夜福利无码电影 | 9l视频自拍九色9l视频成人 |