Java8 Stream API


Stream是Java8中,操作集合的一個重要特性。

  • 從iteration到Stream操作

當你操作一個集合的時候,你通常的做法是迭代每一個元素,然后處理你想要的事情。舉個例子:

String contents = new String(Files.readAllBytes(
    Paths.get("alice.txt")), StandardCharsets.UTF_8);  // 讀取文件到字符串中
List<String> words = Arrays.asList(contents.split("[\\P{L}]+"));  // 截取words

現在我們來迭代操作它:

int count=0;
for (String w : words) {
    if (w.length() > 12) count++;
}

這段代碼有什么問題嗎?除了並行處理不是很好以為我想是沒有。在Java8中,相同的操作是這么處理的:

long count = words.stream().filter(w -> w.length() > 12).count();

so cool!從代碼中,我們就能非常容易的看出它要表達的意思,filter是針對words的過濾。

有人會問,這樣的操作的確讓人很是興奮,但是剛才說到的並行處理它能解決嗎?答案是不可以,但是Java8給我們提供了非常好的API,並行處理如下:

long count = words.parallelStream().filter(w -> w.length() > 12).count();

通過將stream()修改為parallelStream(),這樣就可以並行的進行過濾和統計了。

 從表面上,stream看起來和集合很像,你可以自由的操作它。但是有以下幾點不同:

  1. stream不存儲集合元素
  2. stream操作不修改源數據,他們是返回一個新的streams來承載結果
  3. stream操作都會盡可能的進行延遲加載。這意味着當需要使用結果的時候它才會才運行。

當你使用stream的時候,需要關注一下3個階段:

  1. 創建一個stream
  2. 指定的中間操作將初始stream轉化為其他stream
  3. 最終的操作會產生一個結果,在調用最終操作前都會延遲執行的。在這之后,stream不會再被使用。

在上面的例子中,stream()和parallelStream()創造了stream,filter方法轉化了它,count方法是最終的操作。


注意:stream操作不是按照引用的順序來執行的。在例子中,知道count調用才執行。當count方法請求第一個元素,filter方法開始請求元素,知道找到一個長度大於12的元素。 


 

  • Stream創建

Java8中,你就可以將集合的操作都利用stream來處理,如果你有一個數組,使用靜態的Stream.of方法來實現:

// 分隔后返回String[]
Stream<String> words = Stream.of(content.split("[\\P{L}]+"));

of方法定義:

public static<T> Stream<T> of(T t);
public static<T> Stream<T> of(T... values);

所以你可以構造很多參數的stream。

使用Arrays.stream(array, from, to),可以得到數組的一部分。

想要創建一個空的stream,可以使用Stream.empty方法:

Stream<String> silence = Stream.empty();

stream接口有2個靜態方法來構造無限的streams。generate方法是通過無參函數的。比如:

Stream<String> echos = Stream.generate(() -> "Echo");
or
Stream<Double> randoms = Stream.generate(Math::random);

如果想要產生一個無限的序列,例如:0 1 2 3 ...,可以使用iterate方法來實現:

Stream<BigInteger> integers = Stream.iterate(BigInteger.ZERO, n -> n.add(BigInteger.ONE));
  • filter.map和flatMap方法

map方法也可以達到上面例子的效果,例如:

Stream<String> lowercaseWords = words.map(String::toLowerCase);

使用Lambda表達式如下:

Stream<Character> firstChars = words.map(s -> s.charAt(0));

上面這段代碼是將words中每個詞的第一個字符取出來。

在Java8之前,如果你想將一段字符串分隔出來,代碼如下:

public static Stream<Character> characterStream(String s) {
    List<Character> result = new ArrayList<>();
    for (char c : s.toCharArray()) result.add(c);
    return result.stream();
}

調用characterStream("boat"), 將返回['b','o','a','t']

如果用map來操作的話,如下:

Stream<Stream<Character>> result = words.map(w -> characterStream(w));

這里也可以使用flatMap來替代map:

Stream<Character> letters = words.flatMap(w -> characterStream(w));
  • 提取子Stream和合並Stream

stream.limit(n)方法和SQL中的limit很像,就是取前n個符合條件的數據,例如:

List<Person> persons = Arrays.asList(
        new Person("安紅", "女", 12),
        new Person("索隆", "男", 23),
        new Person("路飛", "男", 22)
);

Stream<Person> partialPersonStream = persons.stream().limit(2);
// 將Stream轉換為List
List<Person> partialPerson = partialPersonStream.collect(Collectors.toList());
Assert.assertTrue(2 == partialPerson.size());
Assert.assertTrue("安紅".equals(partialPerson.get(0).getName()));
Assert.assertTrue("索隆".equals(partialPerson.get(1).getName()));

stream.skip(n)為跳過前n個數據,例如:

List<Person> persons = Arrays.asList(
        new Person("安紅", "女", 12),
        new Person("索隆", "男", 23),
        new Person("路飛", "男", 22)
);

// 跳過前2個
Stream<Person> skipPersonStream = persons.stream().skip(2);
List<Person> skipPersons = skipPersonStream.collect(Collectors.toList());
Assert.assertTrue(1 == skipPersons.size());
Assert.assertTrue("路飛".equals(skipPersons.get(0).getName()));

stream.concat()方法可以對stream進行追加操作,例如:

Stream<String> combined = Stream.concat(characterStream("Hello"), characterStream("world"));

這里stream的結果為['h','e','l','l','o','w','o','r','l','d']

也許大家會覺得這樣的操作,一旦debug起來會比較困難,但是大家不用着急,peek方法就會很好的解決這個問題了:

Object[] powers = Stream.iterate(1.0, p->p*2).peek(e->System.out.println(e)).limit(20).toArray();

這樣就可以將每次得到的結果打印出來了,怎么樣,Java8更干、更爽、更貼心吧?

  • 狀態變換

distinct方法用來獲取唯一性數據,清除重復數據

// distinct - 清除重復數據
Stream<String> uniqueWordStream = Stream.of("a", "b", "b", "c", "a", "d").distinct();
List<String> uniqueWords = uniqueWordStream.collect(Collectors.toList());
Assert.assertTrue(4 == uniqueWords.size());  // a, b, c, d
  • Optional

max和min分別返回最大值和最小值,例如:

Optional<String> largest = words.max(String::compareToIgnoreCase);
if (largest.isPresent()) {
    System.out.println("largest: " + largest.get());
}
// max - 獲取最大值
Optional<Person> maxAgePerson = persons.stream().max((p1, p2) -> Integer.compare(p1.getAge(), p2.getAge()));
if (maxAgePerson.isPresent()) {
    Person person = maxAgePerson.get();
    Assert.assertTrue("索隆".equals(person.getName()));
}

findFirst會返回第一個數據:

// 返回Q開頭的第一個數據
Optional<String> startWithQ = words.filter(s->s.startWith("Q")).findFirst();

如果想匹配所有符合要求的,可以使用如下:

// 匹配所有Q開頭的字符串
Optional<String> startsWithQ = words.parallel().filter(s->s.startWith("Q")).findAny();

如果想要查找是否存在匹配,可以如下:

boolean isWordStartWithQ = words.parallel().anyMatch(s->s.startWith("Q"));

 Optional對象是可以是一個T的包裝類,也可以不是對象。它主要是為了替換引用類型T對象或者null。正確使用的話會更安全。

  • 結果的處理

結果處理使用collect來處理,例子如下:

 // stream的結果收集和處理
Stream<Person> stream = persons.stream().filter(p->p.getAge() > 20);
Set<Person> setResult = stream.collect(Collectors.toSet());
// 獲取list中name一列的數據
Stream<String> nameStream = persons.stream().map(p->p.getName());
//Stream<String> nameStream = stream.map(p->p.getName());  // Error: 不要這么操作,否則會有問題,無法找到p
List<String> names = nameStream.collect(Collectors.toList());
Assert.assertTrue(3 == names.size());
// 將所有名字連接起來,逗號分隔
String nameStr = nameStream.collect(Collectors.joining(","));  // Error: 執行到這里的時候就會報錯了,因為nameStream已經關閉了
Assert.assertTrue("安紅,索隆,路飛".equals(nameStr));
  • Collecting into Maps

toMap用來處理將數據轉化為map結構,例子如下:

// Colloecting into Maps
Map<String, Integer> peoples = persons.stream().collect(Collectors.toMap(Person::getName, Person::getAge));

Stream<Locale> locales = Stream.of(Locale.getAvailableLocales());
Map<String, String> languageNames = locales.collect(
    Collectors.toMap(
        l -> l.getDisplayLanguage(),
        l -> l.getDisplayLanguage(l),
        (existingValue, newValue) -> existingValue));
  • 分組

分組使用函數groupingBy,例子如下:

Stream<Locale> locales1 = Stream.of(Locale.getAvailableLocales());
Map<String, List<Locale>> countryToLocales = locales1.collect(Collectors.groupingBy(Locale::getCountry));
List<Locale> swissLocales = countryToLocales.get("CH");
  • 並行Stream

例子如下:

// parallel stream
ConcurrentMap<String, List<Person>> result = persons.stream().parallel().collect(Collectors.groupingByConcurrent(Person::getName));

 


免責聲明!

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



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