Java8新特性之Stream

本來以為Java8的新特性需要3篇文章才能寫完的,但是發(fā)現(xiàn)其實(shí)Stream的接口并不想象的那么多洞辣,今天就可以完結(jié)啦!

流之初體驗(yàn)

首先先定義一個(gè)菜品類:

  • Dish
public class Dish {
    private final String name;
    private final boolean vegetarian;
    private final int calories;
    private final Type type;
    
    public boolean isVegetarian() {
        return vegetarian;
    }
    
    //省略set,get琅束,toString方法
}

然后創(chuàng)建一個(gè)靜態(tài)方法招盲,并且設(shè)置成類的成員變量低缩,以供測(cè)試。

public static List<Dish> getDishes() {
    return Arrays.asList(
        new Dish("pork", false, 800, Dish.Type.MEAT),
        new Dish("beef", false, 700, Dish.Type.MEAT),
        new Dish("chicken", false, 400, Dish.Type.MEAT),
        new Dish("french fries", true, 530, Dish.Type.OTHER),
        new Dish("rice", true, 350, Dish.Type.OTHER),
        new Dish("pizza", true, 550, Dish.Type.OTHER),
        new Dish("prawns", false, 300, Dish.Type.FISH),
        new Dish("salmon", false, 450, Dish.Type.FISH)
    );
}
XNBqZ

好了,現(xiàn)在有個(gè)需求咆繁,找出菜品中所有小于400卡路里的食物讳推,并且按照卡路里的大小進(jìn)行排序。

java8之前玩般,甚至有些人在java8之后银觅,都會(huì)想著借助一個(gè)中間變量保符合要求的菜品,然后排序坏为。

public static List<String> beforeJava8() {
    List<Dish> lowCaloricDishes = new ArrayList<>();
    for (Dish dish : dishes) {
        if (dish.getCalories() < 400) {
            lowCaloricDishes.add(dish);
        }
    }

    lowCaloricDishes.sort(Comparator.comparingInt(Dish::getCalories));
//    lowCaloricDishes.sort((d1, d2) -> Integer.compare(d1.getCalories(), d2.getCalories()));
    List<String> res = new ArrayList<>();

    for (Dish dish : lowCaloricDishes) {
        res.add(dish.getName());
    }
    return res;
}

由于前一篇文章講過了方法引用究驴,所以這里就直接用,不過下面一行也有普通的Lambda表達(dá)式的書寫匀伏。

上述寫法有什么問題嗎洒忧,可以發(fā)現(xiàn)lowCaloricDishes 只使用了一次,真就一個(gè)臨時(shí)變量够颠。那能不能跳過創(chuàng)建變量的過程熙侍,你直接把數(shù)據(jù)給我,我經(jīng)過過濾排序后得到想要的呢履磨,就和流水線一樣蛉抓。

public static List<String> afterJava8() {
    return dishes.stream()
        .filter(dish -> dish.getCalories() < 400)
        .sorted(Comparator.comparing(Dish::getCalories))
        .map(Dish::getName)
        .collect(Collectors.toList());
}
img

這就是本篇要講的流(Stream)了

流的定義

從支持?jǐn)?shù)據(jù)處理操作的源生成的元素序列

流和集合有點(diǎn)類似,集合是數(shù)據(jù)結(jié)構(gòu)蹬耘,主要的目的是存儲(chǔ)和訪問元素芝雪,而流的主要目的是為了對(duì)元素進(jìn)行一系列的操作。

通俗入門地來講综苔,集合就相當(dāng)于你一部電影下載惩系,流就相當(dāng)于在線觀看。其實(shí)只需要把流想成高級(jí)的集合即可如筛。流有兩個(gè)重要的特點(diǎn):

  • 流水線: 很多流本身會(huì)返回一個(gè)流堡牡,這樣多個(gè)流就能鏈接起來和流水線一般。
  • 內(nèi)部迭代: 內(nèi)部迭代也就是把迭代封裝起來杨刨,如collect(Collectors.toList) 晤柄,與之相對(duì)應(yīng)的外部迭代則是for-each

值得注意的是妖胀,和迭代器類似芥颈,流只能遍歷一次 ,遍歷完就可以說這個(gè)流消費(fèi)掉了赚抡。

流的構(gòu)建

流的構(gòu)建

流常用的構(gòu)建方式有4種爬坑,其實(shí)要么是借助Stream 類的靜態(tài)方法,要么是借助別人的類的靜態(tài)方法涂臣。

  • 由值創(chuàng)建流
  • 由數(shù)組創(chuàng)建流
  • 由文件生成流
  • 由函數(shù)生成流
public static void buildStream() throws IOException {
    Stream<String> byValue = Stream.of("java8", "c++", "go");

    Stream<Object> empty = Stream.empty();

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

    Stream<String> byFiles = Files.lines(Paths.get(""));

    Stream<Integer> byFunction1 = Stream.iterate(0, n -> n * 2);
    Stream<Double> byFunction2 = Stream.generate(Math::random);

    Stream<String> java = Stream.of("java");
}

流的操作

可以連接起來的流操作稱為中間操作盾计,關(guān)閉流的操作稱為終端操作

通俗地講,返回結(jié)果是流的操作稱為中間操作,放回的不是流的操作稱為終端操作署辉。

image-20210414155605342

通過查找java8接口可以得知到哪些接口是中間操作族铆,哪些接口時(shí)終端操作。由于那些接口描述得太過官方哭尝,估計(jì)我貼了也沒啥人會(huì)仔細(xì)看哥攘,所以想看的直接去官方查閱即可。

流的使用

就按照官網(wǎng)上的java API順序來講述刚夺,小插一句献丑,之前我一直沒有學(xué)流是主要是因?yàn)楦杏X接口會(huì)很多,怎么可能記得了這么多侠姑,其實(shí)這幾天看才發(fā)現(xiàn)真的很少,基本上不用記箩做。

img

首先構(gòu)建好一個(gè)數(shù)字列表:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 5, 5, 5, 6, 7);

中間操作

中間操作有去重莽红、過濾、截?cái)喟畎睢⒉榭窗灿酢⑻^、排序 燃辖,這些相信大家都能夠明白是什么意思鬼店。

public static void midOperation() {
    numbers.stream()
        .distinct()
        .forEach(System.out::println);

    List<Integer> filter = numbers.stream()
        .filter(n -> n % 2 == 0)
        .collect(Collectors.toList());

    numbers.stream()
        .limit(3)
        .forEach(System.out::println);

    numbers.stream()
        .peek(integer -> System.out.println("consume operation:" + integer))
        .forEach(System.out::println);

    List<Integer> skip = numbers.stream()
        .skip(2)
        .collect(Collectors.toList());

    numbers.stream()
        .sorted()
        .forEach(System.out::println);
}

中間操作之映射(map)

需要單獨(dú)拎出來說的是映射(map)扁平化映射(flatMap) ,注意黔龟,這里的map并不是hashmap的那個(gè)map妇智,而是說把什么映射或者說轉(zhuǎn)化成了什么。

public static void midOperation() {
    List<String> map = numbers.stream()
                .map(Object::toString)          //這里就是把int映射成了string
                .collect(Collectors.toList());
}

而對(duì)于扁平化映射氏身,現(xiàn)在又有一個(gè)需求巍棱,現(xiàn)在有個(gè)單詞列表如{"hello", "world"},返回里面各不相同的字符蛋欣,也就是要求返回List<String> 航徙。

這還不簡(jiǎn)單,把單詞映射成一個(gè)個(gè)字母陷虎,再去重就好了到踏。

public static void flatMapDemoNoral() {
    List<String> words = Arrays.asList("hello", "world");
    List<String[]> normal = words.stream()
        .map(str -> str.split(""))
        .distinct()
        .collect(Collectors.toList());
}
img

雖然確實(shí)也能達(dá)到效果,但是注意映射所用的函數(shù)是split() 尚猿,返回的是String[] 窝稿,因此整個(gè)返回的是List<String[]>

那我映射完后再把每個(gè)String[] 數(shù)組映射成流

public static void flatMapDemoMap() {
    List<String> words = Arrays.asList("hello", "world");
    List<Stream<String>> usingMap = words.stream()
                .map(str -> str.split(""))
                .map(Arrays::stream)
                .distinct()
                .collect(Collectors.toList());
}

雖然摘掉了數(shù)組的帽子,但是返回的卻是List<Stream<String>> 谊路。

flatMap 正是為了解決這種情況的

public static void flatMapDemoFlatMap() {
    List<String> words = Arrays.asList("hello", "world");
    List<String> usingFlatMap = words.stream()
                .map(str -> str.split(""))
                .flatMap(Arrays::stream)
                .distinct()
                .collect(Collectors.toList());
}

可以簡(jiǎn)單的理解讹躯,map是把每個(gè)元素映射成了獨(dú)立的流,而扁平化map是把元素保存了下來,最后映射成了一個(gè)流 潮梯。

查找與匹配

終端操作除了上述寫例子的時(shí)候常用的collect()forEach() 還有查找和規(guī)約兩種大的方向骗灶。

因?yàn)闆]啥好說的,直接上代碼就完了:

public static void endOperationFindAndMatch() {
    if (dishes.stream().noneMatch(Dish::isVegetarian)) {
        System.out.println("所有的菜品都是非素食");
    }
    if (dishes.stream().allMatch(Dish::isVegetarian)) {
        System.out.println("所有的菜品都是素食");
    }
    if (dishes.stream().anyMatch(Dish::isVegetarian)) {
        System.out.println("菜品中至少有一道菜是素食");
    }

    Optional<Dish> any = dishes.stream()
        .filter(meal -> meal.getCalories() <= 1000)
        .findAny();
    Optional<Dish> first = dishes.stream()
        .filter(meal -> meal.getCalories() <= 1000)
        .findFirst();
}

歸約(計(jì)算)

對(duì)流的規(guī)約操作的話秉馏,一般有普通操作也就是能直接調(diào)用接口的耙旦,還有一種就是借助reduce()

對(duì)于普通操作來說萝究,像求和免都,最大值,最小值這些都是有接口對(duì)應(yīng)的帆竹。

public static void endOperationCalculate() {
    long count = dishes.stream()
        .filter(meal -> meal.getCalories() <= 1000)
        .count();
    Optional<Dish> max = dishes.stream()
        .max(Comparator.comparingInt(Dish::getCalories));
    Optional<Dish> min = dishes.stream()
        .min(Comparator.comparing(Dish::getName));

}

但是如果說要求對(duì)元素求和绕娘,就要使用reduce()

image-20210417114209123

一般使用的是可以接受2個(gè)參數(shù),一個(gè)是初始值栽连,一個(gè)是BinaryOprator<T> 來將兩個(gè)元素結(jié)合起來產(chǎn)生的新值险领。

public static void reduceDemo() {
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 5, 5, 5, 6, 7);
    Integer sum = numbers.stream().reduce(0, Integer::sum);
    
    //所有元素相乘也是比較簡(jiǎn)單
    Integer multi = numbers.stream().reduce(0, (a, b) -> a * b);
    
    //還有求最大值
    Optional<Integer> max = numbers.stream().reduce(Integer::max);
}

Optional類

上面一直出現(xiàn)有返回值是Optional<T> ,它是一個(gè)容器類秒紧,代表一個(gè)值存在或者不存在绢陌,比如一開始的findAny() ,可能找不到符合條件的菜品熔恢。Java8引入的目的主要是/為了不要返回容易出現(xiàn)問題的null了脐湾。

就說幾個(gè)比較常用的api就好了至于其它的可以上網(wǎng)看下官方API,今天說的API已經(jīng)夠多了

  • isPresent() 將在Optional 包含值的時(shí)候返回true叙淌,否則返回false
  • ifPresent(Consumer<T> block) 存在值的時(shí)候會(huì)執(zhí)行給定的代碼塊
  • get() 存在值就返回值秤掌,否則拋出NoSuchElement異常
  • orElse() 存在值就返回,否則就返回一個(gè)默認(rèn)值
public static void optionalDemo() {
    //ifPresent
    dishes.stream()
        .filter(Dish::isVegetarian)
        .findAny()
        .ifPresent(dish -> System.out.println(dish.getName()));

    //isPresent
    boolean isLowCalories= dishes.stream()
        .filter(dish -> dish.getCalories() <= 1000)
        .findAny()
        .isPresent();
    
    //get
    Optional<Dish> optional = dishes.stream()
        .filter(Dish::isVegetarian)
        .findAny();
    if (optional.isPresent()) {
        Dish dish = optional.get();
    }

    //orElse
    Dish dishNormal = dishes.stream()
        .filter(Dish::isVegetarian)
        .findAny()
        .orElse(new Dish("java", false, 10000, Dish.Type.OTHER));
}

總結(jié)

一樣的凿菩,還是有幾個(gè)小點(diǎn)沒講机杜,比如并行流的部分,收集器部分衅谷,但是這些相對(duì)來說算是比較深入的椒拗。再經(jīng)過兩篇文章后,已經(jīng)盡可能地全面地了解了Java8新特性获黔。消化完后就可以去看《java8實(shí)戰(zhàn)》蚀苛,看書才是學(xué)習(xí)新知識(shí)的重點(diǎn),最后就是應(yīng)用了玷氏。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末堵未,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子盏触,更是在濱河造成了極大的恐慌渗蟹,老刑警劉巖块饺,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異雌芽,居然都是意外死亡授艰,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門世落,熙熙樓的掌柜王于貴愁眉苦臉地迎上來淮腾,“玉大人,你說我怎么就攤上這事屉佳」瘸” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵武花,是天一觀的道長(zhǎng)圆凰。 經(jīng)常有香客問我,道長(zhǎng)体箕,這世上最難降的妖魔是什么送朱? 我笑而不...
    開封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮干旁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘炮沐。我一直安慰自己争群,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開白布大年。 她就那樣靜靜地躺著换薄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪翔试。 梳的紋絲不亂的頭發(fā)上轻要,一...
    開封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音垦缅,去河邊找鬼冲泥。 笑死,一個(gè)胖子當(dāng)著我的面吹牛壁涎,可吹牛的內(nèi)容都是我干的凡恍。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼怔球,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼嚼酝!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起竟坛,我...
    開封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤闽巩,失蹤者是張志新(化名)和其女友劉穎钧舌,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涎跨,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡洼冻,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了六敬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碘赖。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖外构,靈堂內(nèi)的尸體忽然破棺而出普泡,到底是詐尸還是另有隱情,我是刑警寧澤审编,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布撼班,位于F島的核電站,受9級(jí)特大地震影響垒酬,放射性物質(zhì)發(fā)生泄漏砰嘁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一勘究、第九天 我趴在偏房一處隱蔽的房頂上張望矮湘。 院中可真熱鬧,春花似錦口糕、人聲如沸缅阳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽十办。三九已至,卻和暖如春超棺,著一層夾襖步出監(jiān)牢的瞬間向族,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工棠绘, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留件相,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓弄唧,卻偏偏與公主長(zhǎng)得像适肠,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子候引,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 今天感恩節(jié)哎侯养,感謝一直在我身邊的親朋好友。感恩相遇澄干!感恩不離不棄逛揩。 中午開了第一次的黨會(huì)柠傍,身份的轉(zhuǎn)變要...
    迷月閃星情閱讀 10,564評(píng)論 0 11
  • 彩排完,天已黑
    劉凱書法閱讀 4,217評(píng)論 1 3
  • 表情是什么辩稽,我認(rèn)為表情就是表現(xiàn)出來的情緒惧笛。表情可以傳達(dá)很多信息。高興了當(dāng)然就笑了逞泄,難過就哭了患整。兩者是相互影響密不可...
    Persistenc_6aea閱讀 125,019評(píng)論 2 7