Java8新特性第3章(Stream API)

Stream做爲Java8的新特性之一,他與Java IO包中的InputStream和OutputStream徹底不是一個概念。Java8中的Stream是對集合功能的一種加強,主要用於對集合對象進行各類很是便利高效的聚合和大批量數據的操做。結合Lambda表達式能夠極大的提升開發效率和代碼可讀性。java

假設咱們須要把一個集合中的全部形狀設置成紅色,那麼咱們能夠這樣寫git

for (Shape shape : shapes){
    shape.setColor(RED)
}

若是使用Java8擴展後的集合框架則能夠這樣寫:github

shapes.foreach(s -> s.setColor(RED));

第一種寫法咱們叫外部迭代,for-each調用shapesiterator()依次遍歷集合中的元素。這種外部迭代有一些問題:markdown

  • for循環是串行的,並且必須按照集合中元素的順序依次進行;
  • 集合框架沒法對控制流進行優化,例如經過排序、並行、短路求值以及惰性求值改善性能。

    上面這兩個問題咱們會在後面的文章中逐步解答。數據結構


第二種寫法咱們叫內部迭代,兩段代碼雖然看起來只是語法上的區別,但實際上他們內部的區別其實很是大。用戶把對操做的控制權交還給類庫,從而容許類庫進行各類各樣的優化(例如亂序執行、惰性求值和並行等等)。總的來講,內部迭代使得外部迭代中不可能實現的優化成爲可能。 外部迭代同時承擔了作什麼(把形狀設爲紅色)和怎麼作(獲得Iterator實例而後依次遍歷),而內部迭代只負責作什麼,而把怎麼作留給類庫。這樣代碼會變得更加清晰,而集合類庫則能夠在內部進行各類優化。

1、什麼是Stream

Stream不是集合元素,它也不是數據結構、不能保存數據,它更像一個更高級的`Interator`。Stream提供了強大的數據集合操做功能,並被深刻整合到現有的集合類和其它的JDK類型中。流的操做能夠被組合成流水線(Pipeline)。拿前面的例子來講,若是我只想把藍色改爲紅色:
shapes.stream()
      .filter(s -> s.getColor() == BLUE)
      .forEach(s -> s.setColor(RED));
在`Collection`上調用`stream()`會生成該集合元素的流,接下來`filter()`操做會產生只包含藍色形狀的流,最後,這些藍色形狀會被`forEach`操做設爲紅色。 若是咱們想把藍色的形狀提取到新的List裏,則能夠:
List<Shape> blue = shapes.stream()
                          .filter(s -> s.getColor() == BLUE)
                          .collect(Collectors.toList());
`collect()`操做會把其接收的元素彙集到一塊兒(這裏是List),`collect()`方法的參數則被用來指定如何進行彙集操做。在這裏咱們使用`toList()`以把元素輸出到List中。 若是每一個形狀都被保存在`Box`裏,而後咱們想知道哪一個盒子至少包含一個藍色形狀,咱們能夠這麼寫:
Set<Box> hasBlueShape = shapes.stream()
                               .filter(s -> s.getColor() == BLUE)
                              .map(s -> s.getContainingBox())
                              .collect(Collectors.toSet());
`map()`操做經過映射函數(這裏的映射函數接收一個形狀,而後返回包含它的盒子)對輸入流裏面的元素進行依次轉換,而後產生新流。 若是咱們須要獲得藍色物體的總重量,咱們能夠這樣表達:
int sum = shapes.stream()
                .filter(s -> s.getColor() == BLUE)
                .mapToInt(s -> s.getWeight())
                .sum();

2、Stream vs Collection

流(Stream)和集合(Collection)的區別:
  • Collection主要用來對元素進行管理和訪問;
  • Stream並不支持對其元素進行直接操做和直接訪問,而只支持經過聲明式操做在其之上進行運算後獲得結果;
  • Stream不存儲值
  • 對Stream的操做會產生一個結果,可是Stream並不會改變數據源;
  • 大多數Stream的操做(filter,map,sort等)都是以惰性的方式實現的。這使得咱們可使用一次遍歷完成整個流水線操做,並能夠用短路操做提供更高效的實現。

3、惰性求值 vs 急性求值

filter()map()這樣的操做既能夠被急性求值(以filter()爲例,急性求值須要在方法返回前完成對全部元素的過濾),也能夠被惰性求值(用Stream表明過濾結果,當且僅當須要時才進行過濾操做)在實際中進行惰性運算能夠帶來不少好處。好比說,若是咱們進行惰性過濾,咱們就能夠把過濾和流水線裏的其它操做混合在一塊兒,從而不須要對數據進行多遍遍歷。相相似的,若是咱們在一個大型集合裏搜索第一個知足某個條件的元素,咱們能夠在找到後直接中止,而不是繼續處理整個集合。(這一點對無限數據源是很重要,惰性求值對於有限數據源起到的是優化做用,但對無限數據源起到的是決定做用,沒有惰性求值,對無限數據源的操做將沒法終止)框架

對於filter()map()這樣的操做,咱們很天然的會把它當成是惰性求值操做,不過它們是否真的是惰性取決於它們的具體實現。另外,像sum()這樣生成值的操做和forEach()這樣產生反作用的操做都是自然急性求值,由於它們必需要產生具體的結果。ide

咱們拿下面這段代碼舉例:函數

int sum = shapes.stream()
                .filter(s -> s.getColor() == BLUE)
                .mapToInt(s -> s.getWeight())
                .sum();

這裏的filter()map()都是惰性的,這就意味着在調用sum()以前不會從數據源中提取任何元素。在sum()操做以後纔會把filter()map()sum()放在對數據源一次遍歷中。這樣能夠大大減小維持中間結果所帶來的開銷。性能

4、舉個栗子��

前面長篇大論的介紹概念實在太枯燥,爲了方便你們理解咱們用Streams API來實現一個具體的業務場景。優化

假設咱們有一個房源庫項目,這個房源庫中有一系列的小區,每一個小區都有小區名和房源列表,每套房子又有價格、面積等屬性。如今咱們須要篩選出含有100平米以上房源的小區,並按照小區名排序。

咱們先來看看不用Streams API如何實現:

List<Community> result = new ArrayList<>();
for (Community community : communities) {
        for (House house : community.houses) {
            if (house.area > 100) {
                result.add(community);
                break;
            }
        }
    }
    Collections.sort(result, new Comparator<Community>() {
        @Override
        public int compare(Community c1, Community c2) {
            return c1.name.compareTo(c2.name);
        }
    });
    return result;

若是使用Streams API:

return communities.stream()
          .filter(c -> c.houses.stream().anyMatch(h -> h.area>100))
          .sorted(Comparator.comparing(c -> c.name))
          .collect(Collectors.toList());

若是你喜歡個人文章,就關注下個人知乎專欄或者在 GitHub 上添個 Star 吧!

相關文章
相關標籤/搜索