【Java】Stream API - ストリーム中間処理

#ストリーム中間処理
* ストリームを流れる値を加工、フィルターする
* 実行されるのは**終端処理が呼ばれたタイミング**

##指定条件で値をフィルタ
* **filterメソッド**

```java
import java.util.stream.Stream;
public class Main {
  public static void main(String[] args) {
    Stream.of(
      "https://qiita.com/",
      "Neko",
      "CAT",
      "https://www.amazon.co.jp/",
      "https://ja.wikipedia.org/wiki/Neko_(%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2)"
    )
      .filter(s -> s.startsWith("https://"))
      .forEach(System.out::println);
/*
https://qiita.com/
https://www.amazon.co.jp/
https://ja.wikipedia.org/wiki/Neko_(%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2)
*/
  }
}
```

##与えられた値を加工
* **mapメソッド**
* 文字列リストを文字列長リストに変換して出力
* 生成された直後は`String<String>`で、mapメソッドを経たら`String<Integer>`になる

```java
import java.util.stream.Stream;
public class Main {
  public static void main(String[] args) {

    Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
    .map(s -> s.length())
    .forEach(System.out::println); //8 7 7 13 4
  }
}
```
* 基本型ストリームを生成する**mapToInt/mapToLong/mapToDoubleメソッド**
* `String<Integer>`ではなく`IntStream`が生成される
* ボクシングの負担減少

```java
import java.util.stream.Stream;
public class Main {
  public static void main(String[] args) {
    Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
    .mapToInt(s -> s.length())
    .forEach(System.out::println); //8 7 7 13 4
  }
}
```
##与えられた要素を加工
* **flatMap/flatMapToXXメソッド**
* 変換結果を**Stream型で返す**のがmapとの違い
* 個々の要素で返したStreamが最終的に結合されて返る

```java
//二次元配列listを一次元配列に変換
import java.util.Arrays;
public class Main {
  public static void main(String[] args) {

    var list = new String[][] {
      { "neko", "nekko", "nekkko" },
      { "inu", "innu" },
      { "tori", "torri" }
    };
    //flatMapで入れ子の配列を渡し、Arrays.streamメソッドでストリーム化
    //flatMapで連結することで一次元配列にフラット化
    Arrays.stream(list)
      .flatMap(v -> Arrays.stream(v))
      .forEach(System.out::println);
     // neko nekko nekkko inu innu tori torri
    }
}
```
* ストリーム加工の際に**入れ子で値を加工することも可能**

```java
//二次元配列をフラット化する際に頭文字を取り出す
import java.util.Arrays;
public class Main {
  public static void main(String[] args) {

    var list = new String[][] {
      { "neko", "nekko", "nekkko" },
      { "inu", "innu" },
      { "tori", "torri" }
    };

    Arrays.stream(list)
    .flatMap(v -> Arrays.stream(v).map(str -> str.substring(0, 1)))
    .forEach(System.out::println);
    //n n n i i t t
    }
}
```

##要素を並び替える
* **sortedメソッド**
* 自然順序によるソート

```java
import java.util.stream.Stream;
public class Main {
  public static void main(String[] args) {

    Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
    .sorted()
    .forEach(System.out::println);
// Munchkin
// Persian
// Scottish Fold
// Siamese
// Tama
  }
}
```

* 規則を設定したい場合、**ラムダ式で設定**

```java
import java.util.stream.Stream;
import java.util.Comparator;

public class Main {
  public static void main(String[] args) {

    Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
    .sorted((str1, str2) -> str1.length() - str2.length())
    .forEach(System.out::println);
    // Tama
    // Siamese
    // Persian
    // Munchkin
    // Scottish Fold

    /*Comparatorインターフェースで辞書逆順
     Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
     .sorted(Comparator.reverseOrder())
     .forEach(System.out::println);
    */
  }
}
```

##m~n番目の要素を取り出す
* **skip/limitメソッド**
* **skipメソッド**：m番目までの要素を切り捨てる
* **limitメソッド**：n+1番目以降の要素を切り捨てる

```java
//skipで4要素をスキップし、limitでそこから10個分の要素を取り出す
import java.util.stream.IntStream;
public class StreamLimit {
  public static void main(String[] args) {
    IntStream.range(1, 20)
      .skip(4)
      .limit(10)
      .forEach(System.out::println); //5,6,7,8,9,10,11,12,13,14
  }
}
```

##先頭から条件をみたす間の値を除去
* **dropWhileメソッド**
* 条件式に合致してる値をスキップ
    * ストリームの先頭から負数を除去
    * あくまでも**先頭から連続する値なので途中の負数は除去しない**
    * →途中の場合はfilterで除去

```java
import java.util.stream.IntStream;
public class StreamDrop {
  public static void main(String[] args) {
    IntStream.of(-2, -5, 0, 3, -1, 2)
      .dropWhile(i -> i < 0)
      .forEach(System.out::println); //0,3,-1,2
  }
}
```


##先頭から条件を満たす間の値のみ取り出す
* **takeWhileメソッド**
* ストリームの先頭から負数であるものだけを処理する
    * あくまでも**処理するのは先頭から連続する値**

```java
import java.util.stream.IntStream;
public class StreamTake {
  public static void main(String[] args) {
    IntStream.of(-2, -5, 0, 3, -1, 2)
      .takeWhile(i -> i < 0)
      .forEach(System.out::println); //-2,-5
  }
}
```

##ストリームの途中状態を確認する
* **peekメソッド**
* 途中で任意の処理を挟むことができる
* peekメソッドは**ストリームに影響を及ぼさない**

```java
//ソート前後でストリーム内容を出力
import java.util.stream.Stream;
public class StreamPeek {
  public static void main(String[] args) {
    Stream.of("さかな", "あか", "こだま", "きんもくせい")
      .peek(System.out::println)
      .sorted()
      .forEach(System.out::println);
  }
}
```

##値の重複を除去
* **distinctメソッド**
* ストリームに含まれる値の重複を除去 
* 任意のクラスの特定フィールドをキーに重複除去できない
* →filterメソッドを使う

```java
import java.util.stream.Stream;
public class Main {
  public static void main(String[] args) {
    Stream.of("ねこ", "猫", "ねっこ", "ねこ", "ねこねこ")
      .distinct()
      .forEach(System.out::println);
    // ねこ
    // 猫
    // ねっこ
    // ねこねこ
  }
}
```

* Personクラスでnameフィールドをキーに重複をチェック

```java
import java.util.HashSet;
import java.util.stream.Stream;

public class StreamDistinctObj {

  public static void main(String[] args) {
    var set = new HashSet<String>();
    Stream.of(
        new Person("山田", 40),
        new Person("高野", 30),
        new Person("大川", 35),
        new Person("山田", 45)
      )
      //filterメソッドの中で値をHashSetに追加
      //HashSet.addメソッドは値を追加できなかった（重複）場合falseを返す

      .filter(p -> set.add(p.name))
      .forEach(System.out::println);
  }
}
```
```java
public class Person {
  public String name;
  public int age;

  public Person(String name, int age) {
    this.name = name;
    this.age = age;
  }
  @Override
  public String toString() {
    return String.format("%s（%d歳）", this.name, this.age);
  }
}
```

##基本型ストリームの変換
* **asLongStream/asDoubleStream/LongStream**で型変換

```java
//IntStreamからDoubleStreamへの変換
import java.util.stream.IntStream;
public class Main {
  public static void main(String[] args) {
    IntStream.range(1, 5)
      .asDoubleStream()
      .forEach(System.out::println);
      //1.0 2.0 3.0 4.0
  }
}
```

##基本型、参照型ストリームを相互変換
* 基本型から参照型：**boxedメソッド**
* 参照型から基本型：**mapToObjメソッド**

```java
//IntStreamからStream<Integer>に変換
import java.util.stream.IntStream;
public class Main {
  public static void main(String[] args) {
    IntStream.range(1, 5)
      .boxed()
      .forEach(System.out::println);
      //1 2 3 4
      
    IntStream.range(1, 5)
      .mapToObj(Integer::valueOf)
      .forEach(System.out::println);
      //1 2 3 4
  }
}
```

```java
//Stream<Integer>からIntStreamに変換
import java.util.stream.Stream;
public class StreamUnboxed {
  public static void main(String[] args) {
    Stream.of(1, 2, 3, 4)
      .mapToInt(i -> i) //アンボクシングでInteger→int
      .forEach(System.out::println); //1 2 3 4
    }
}
```