魔術師見習いのノート

プロフィール

魔術師見習い
Author魔術師見習い-_-.
Twitter魔術師見習い

コンピュータ関係のメモを主に書きます.

MENU

Stream メモ

投稿日:
タグ:

JavaのStreamに関するメモ。

Stream API

概要

Java8から導入されたStream APIは、簡単に言ってデータ集合を扱う機能である。 Stream APIを使うとループ文やイテレータでは、冗長な記載しかできなかった文が簡潔に書けることが多い。

ただし、プロジェクト内でうまくルールを決めないと、同じような実装をやるために、さまざまな書き方で書かれることになる。

また、性能的な観点でいると、簡単なデータ集合を扱うだけならば、わざわざStream型を使用するよりも、基本(プリミティブ)型を使ったほうが速い。

ループ文
java Hoge 0.30s user 0.02s system 99% cpu 0.318 total
String[] ary = {"hoge", "fuga", "piyo"};
for (int i=0;i<3;i++){
   System.out.println(ary[i]);
}
Stream API
java Hoge 0.46s user 0.06s system 113% cpu 0.460 total
String[] ary = {"hoge", "fuga", "piyo"};
Stream.of(ary).forEach(System.out::println);

基本

データ集合にはそれに対応するストリームの種類がある。

  • オブジェクト型
    • Stream
  • 基本(プリミティブ)型
    ストリーム対応するプリミティブ型
    IntStreamint
    LongStreamlong
    DoubleStreamdouble

ストリームは上記のStreamやIntStreamクラスなどのstaticメソッドや、配列やコレクション型などのデータ集合をストリームに型変換することで、記載する。

ストリームに0回以上の中間操作というメソッド呼び出しを行い、最後に終端操作と呼ばれるメソッドを呼ぶのがストリームの基本的な考え方である。

以下、0〜20の間の3の倍数の奇数を出力するサンプル。

IntStream.rangeClosed(0, 20)            // ストリームを取得: 0〜20
         .filter((v) -> v%3==0)         // 中間操作: 3の倍数
         .filter((v) -> v%2==1)         // 中間操作: 奇数
         .forEach(System.out::println); // 終端操作:各要素を引数にメソッド呼び出し
メソッドは以下のように記載する。
  • クラス::メソッド
  • オブジェクト::メソッド
当然this::メソッドもある。

レシピ集

要素に対する条件
allMatch(条件)
全て一致
int [] ary = {1, 2, 3, 4, 5};
if (IntStream.of(ary).allMatch((i) -> i > 0)){
  System.out.println("OK");
}
noneMatchというものもある
noneMatch(条件)
int[] v = {0, 0, 0, 0};
if (IntStream.of(v).noneMatch((x) -> x > 0)){
  System.out.println("OK");
}
anyMatch(条件)
いずれかが一致
int [] ary = {1, 2, 3, 4, 5};
if (IntStream.of(ary).anyMatch((i) -> i > 3)){
  System.out.println("OK");
}
多次元配列から1次元配列へ
flatMap(expression)のexpressionにはStreamを返すメソッドを指定する(flatMapToInt()の場合、IntStream)。
オブジェクト型
String[][] ary = {{"a", "b"}, {"c", "d", "e"}};
String[] s = Stream.of(ary).flatMap(Stream::of)
                           .toArray(String[]::new);
String[][][] ary = {{{"a", "b"}, {"c", "d", "e"}},
                    {{"f", "g"},{"h", "i"}}};
String[] s = Stream.of(ary).flatMap(Stream::of)
                           .flatMap(Stream::of)
                           .toArray(String[]::new);
プリミティブ型
int[][] ary = {{123, 456}, {789, 101}};
int[] iary = Stream.of(ary).flatMapToInt(IntStream::of)
	                   .toArray();
int[][][][] ary = {{{{123, 456}, {789, 101}}, {{121, 131}, {415, 161}}}, {{{718, 192}, {212, 132}}}};
int[] iary = Stream.of(ary).flatMap(Stream::of)
	                   .flatMap(Stream::of)
	                   .flatMapToInt(IntStream::of)
	                   .toArray();
型変換
int[]からList<Integer>への型変換
int [] ary = {1, 2, 3, 4, 5};
List<Integer> list = IntStream.of(ary).mapToObj(Integer::new)
                                      .collect(Collectors.toList());
List<Integer>からint[]への型変換
int[] ary = list.stream().mapToInt((v) -> v).toArray();
String[][]からint[]への型変換
String[][] ary = {{"123", "456"}, {"789", "101"}};
int[] iary = Stream.of(ary)
	   .map((x) -> Stream.of(x).mapToInt((y) -> Integer.parseInt(y)).toArray())
	   .flatMapToInt((v) -> IntStream.of(v))
	   .toArray();
int[]からint
int[] ary = {1, 2, 3, 4};
IntStream.of(ary).reduce((b, i) -> b + i).ifPresent(System.out::println);
ListからMapへ
List.of("a", "b", "c").stream().collect(Function.identity(), e -> String.fomat("{%s}", e));
配列・コレクション型の初期化
List<Integer>の作成
List<Integer> ary = IntStream.rangeClosed(1, 5)
                             .mapToObj(Integer::new)
			     .collect(Collectors.toList());
連続する数字のストリーム。range()の範囲は「開始番号 ≦ n < 終了番号」 rangeClosed()の範囲は「開始番号 ≦ n≦ 終了番号
Set<Integer>
List<Integer> ary = IntStream.rangeClosed(1, 5)
                             .mapToObj(Integer::new)
			     .collect(Collectors.toSet());
ArrayList<String>[]
@SuppressWarnings("unchecked")
public static void main(String[] args){
  ArrayList<String>[] ary =
  Stream.generate(ArrayList::new).limit(5).toArray(ArrayList[]::new);
}
数列
乱数配列
double[] ary = DoubleStream.generate(() -> Math.random()).limit(3).toArray();
generate()は無限に続くストリーム。遅延評価されるため、実際に使用する際は基本的にlimit(n)のように使用する分だけを指定する。
  • 0.39132915962622816
  • 0.7227708950486765
  • 0.47467761699532696
配列に変換(1〜4)
int[] a = IntStream.range(1, 5).toArray()
終了値を含める場合(1〜5)
int[] a = IntStream.rangeClosed(1, 5).toArray()
1, 4, 9, 16, 25,..
IntStream.rangeClosed(1, 5).map((x) -> x * x);
2, 4, 6, 8, 10,..
Stream.iterate(2, x -> x + 2).limit(8)
iterate()はlimit()がないと無制限に続く
Stream.iterate(初期値, 増加式)
その他

文字列結合

Java8からString.join()やStringJoinerというクラスが導入された。 これらを使うと文字列結合が非常に便利になる。

また、Streamと組み合わせると更に記載がシンプルになる。

hoge,fuga,piyo,foo
String.join(",", ary);
レコード:{hoge,fuga,piyo,foo}
Stream.of(ary).collect(Collectors.joining(",", "レコード:{", "}"));

Appendix

ストリーム取得
  • Arrays.stream(配列)
  • Stream.of(配列)
  • Stream.of(要素, ...)
  • コレクション型.stream()
中間操作
Stream.filter(expression)
条件に合致するもののみを抽出する
Stream.distinct()
Streamから要素の重複を排除したものを取得する。
Stream.sorted()
Stream.sorted(expression)
ソートしたストリームを取得する
Stream.map(expression)
Stream.mapToInt(expression)
Stream.mapToLong(expression)
Stream.mapToDouble(expression)
IntStream.mapToObj(expression)
LongStream.mapToObj(expression)
DoubleStream.mapToObj(expression)
各要素を別の型に変換する。map()やmapToObj()はStream型を返し、それ以外は対応するストリームを返す(例:mapToInt()であればIntStream)。
Stream.flatMap(expression)
Stream.flatMapToInt(expression)
Stream.flatMapToLong(expression)
Stream.flatMapToDouble(expression)
各要素をストリームとして、それらを結合したものを取得する expressionには対応するストリームを返すメソッドを指定する。
Stream.parallel()
IntStream.parallel()
LongStream.parallel()
DoubleStream.parallel()
並列実行用のストリームを取得する
終端操作
forEachOrdered(expression)
forEach(expression)
メソッド呼び出し。返り値なし。
Stream.count()
Streamの要素数をカウント。返り値は要素数。
IntStream.sum()
LongStream.sum()
Streamの要素を合計する。返り値は和。

一覧