LoginSignup
9

More than 3 years have passed since last update.

[Java11] Stream 使い方まとめ -基本編-

Last updated at Posted at 2019-12-10

はじめに

Javaを書いて2年ほど経ちまして、最近ではJava11で実装をしています。Java8から9,10を経てStreamOptionalにもいくつかのメソッドが追加されています。
現在の開発ではOptionalを本格的に取り入れたりと、Stream,Optionalをより有効に活用できる環境になりました。

それとは別に「これってStreamで書けますか?」「ここStreamで書きたいんですけど...」と聞かれることも多かったり...
他の人のコードを見て、Streamだったらもっと綺麗に書けるのにと思ったり...

いずれにしろもっと積極的にStreamで書いて欲しい!(なぜStreamで書いて欲しいのかはこちら

ちなみに公式リファレンスを抵抗なく読める人は、今回の記事は役に立たないかもしれません。

注意書き

  • 本記事はJava11の仕様に基づいて書いています
  • 全てのメソッドを網羅しているわけではありません
  • わかりやすいように1操作ごとに変数に置いていますが、実際は1文で書くことが好ましいです
  • また、中間操作では各操作後のStreamの中身の変化をコメントで表していますが、実際は終端操作をするまでは中間操作は行われないことに留意してください

本編

特によく使うものには「⭐️」を付けています。


Widgetクラスの想定
class Widget {
    private Color color;

    private int weight;

    private String text;

    private List<Widget> childWeights = new ArrayList<>();

    public Widget(Color color, int weight) {
        this.color = color;
        this.weight = weight;
    }

    public Widget(Color color, int weight, String text) {
        this(color, weight);
        this.text = text;
    }

    public Color getColor() {
        return color;
    }

    public int getWeight() {
        return weight;
    }

    public Optional<String> getText() {
        return Optional.ofNullable(text);
    }

    public List<Widget> getChildWeights() {
        return childWeights;
    }

    public void setChildWeights(List<Widget> childWeights) {
       this.childWeights = childWeights;
    }

    public boolean isColorRed() {
        return color == RED;
    }

    public boolean isColorBlue() {
        return color == BLUE;
    }

    @Override
    public String toString() {
        return "Widget {color: " + color + ", weigth: " + weight + "}";
    }

    @Override
    public boolean equals(Object o) {
        Widget w = (Widget) o;
        return this.color == w.color && this.weight == w.weight;
    }
}


Streamの生成

⭐️ List・Set・Mapから

1番使う方法

Widget a = new Widget(RED, 10);
Widget b = new Widget(RED, 15);
Widget c = new Widget(BLUE, 20);

// Listから
List<Widget> list = List.of(a, b, c); // [a, b, c]
Stream<Widget> listToStream = list.stream(); // <a, b, c>
// Setから
Set<Widget> set = Set.of(a, b, c); // {a, b, c}
Stream<Widget> setToStream = set.stream(); // <a, b, c>
// Mapから
Map<Long, Widget> map = Map.of(1L, a, 2L, b, 3L, c); // {1L : a, 2L : b, 3L : c}
Stream<Long> mapKeyToStream = map.keySet().stream(); // <1L, 2L, 3L>
Stream<Widget> mapValueToStream = map.values().stream(); // <a, b, c>
Stream<Entry<Long, Widget>> mapToStream = map.entrySet().stream(); // <{1L : a}, {2L : b}, {3L : c}>

最初から並列にしたいとき

Widget a = new Widget(RED, 10);
Widget b = new Widget(RED, 15);
Widget c = new Widget(BLUE, 20);

// Listから
List<Widget> list = List.of(a, b, c); // [a, b, c]
Stream<Widget> listToStream = list.parallelStream(); // <a, b, c>
// Setから
Set<Widget> set = Set.of(a, b, c); // {a, b, c}
Stream<Widget> setToStream = set.parallelStream(); // <a, b, c>
// Mapから
Map<Long, Widget> map = Map.of(1L, a, 2L, b, 3L, c); // {1L : a, 2L : b, 3L : c}
Stream<Long> mapKeyToStream = map.keySet().parallelStream(); // <1L, 2L, 3L>
Stream<Widget> mapValueToStream = map.values().parallelStream(); // <a, b, c>
Stream<Entry<Long, Widget>> mapToStream = map.entrySet().parallelStream(); // <{1L : a}, {2L : b}, {3L : c}>

配列から

Widget[] arr = {a, b, c};
Stream<Widget> arrayToStream = Arrays.stream(arr); // <a, b, c>

いくつかの値から

1個

Stream<Long> stream1 = Stream.of(100L); // <100L>

// × エラー
Stream<Long> stream2 = Stream.of(null); // <null> ?

2..N個

Stream<Long> stream1 = Stream.of(100L, 200L); // <100L, 200L>
Stream<String> stream2 = Stream.of("A", "B", "C", null); // <"A", "B", "C", null>

Stream<Long> stream3 = Stream.of(null, null); // <null, null>

0..1個

Stream<Long> stream1 = Stream.ofNullable(100L); // <100L>

Stream<Long> stream2 = Stream.ofNullable(null); // <>
Stream<Long> stream3 = Stream.empty(); // <>

複数のStreamから

⭐️ 2個のStreamから

Stream<Widget> stream1 = Stream.of(a, b, c); // <a, b, c>
Stream<Widget> stream2 = Stream.of(x, y, z); // <x, y, z>
Stream<Widget> stream3 = Stream.concat(stream1, stream2); // <a, b, c, x, y, z>

3..N個のStreamから

使ったことはないですが、いざと言うときはこのように書けます

Stream<Widget> stream1 = Stream.of(a, b); // <a, b>
Stream<Widget> stream2 = Stream.of(c, d); // <c, d>
Stream<Widget> stream3 = Stream.of(e, f); // <e, f>
Stream<Widget> stream4 = Stream.of(g, h); // <g, h>
Stream<Stream<Widget>> stream5_1 = Stream.of(stream1, stream2, stream3, stream4); // <<a, b>, <c, d>, <e, f>, <g, h>>
Stream<Widget> stream5_2 = stream5_1.flatMap(UnaryOperator.identity()); // <a, b, c, d, e, f, g, h>

整数の特定の範囲から(IntStream)

IntStream stream1To4 = IntStream.range(1, 5); // IntStream<1, 2, 3, 4>
IntStream stream1To5 = IntStream.rangeClosed(1, 5); // IntStream<1, 2, 3, 4, 5>

中間操作

⭐️ マッピング

1 : 1 変換

Widget a = new Widget(RED, 10);
Widget b = new Widget(RED, 15);
Widget c = new Widget(BLUE, 20);

// Widget -> Color
Stream<Widget> stream1_1 = Stream.of(a, b, c); // <a, b, c>
Stream<Color> stream1_2 = stream1_1.map(Widget::getColor); // <RED, RED, BLUE>

// Widget -> 重さ(int)
Stream<Widget> stream2_1 = Stream.of(a, b, c); // <a, b, c>
Stream<Integer> stream2_2 = stream2_1.map(Widget::getWeight); // <10, 15, 20>

// number -> number * 2
IntStream stream3_1 = IntStream.range(1, 5); // IntStream<1, 2, 3, 4>
IntStream stream3_2 = stream3_1.map(i -> i * 2); // IntStream<2, 4, 6, 8>

1 : 0..N 変換

Widget a = new Widget(RED, 10);
Widget a_1 = new Widget(RED, 1);
Widget a_2 = new Widget(RED, 2);
Widget a_3 = new Widget(RED, 4);
a.setChildWeights(List.of(a_1, a_2, a_3));
Widget b = new Widget(RED, 15);
Widget b_1 = new Widget(RED, 8);
b.setChildWeights(List.of(b_1));
Widget c = new Widget(BLUE, 20);

// Widget -> それぞれの子Widget
Stream<Widget> stream1 = Stream.of(a, b, c); // <a, b, c>
Stream<List<Widget>> stream2 = stream1.map(Widget::getChildWeights); // <[a_1, a_2, a_3], [b_1], []>
Stream<Widget> stream3 = stream2.flatMap(Collection::stream); // <a_1, a_2, a_3, b_1>

1 : 0..1 変換

Widget a = new Widget(RED, 10, "A");
Widget b = new Widget(RED, 15, "B");
Widget c = new Widget(BLUE, 20);

// Widget -> text(String)
Stream<Widget> stream1 = Stream.of(a, b, c); // <a, b, c>
Stream<Optional<String>> stream2 = stream1.map(Widget::getText); // <<"A">, <"B">, <>>
Stream<String> stream3 = stream2.flatMap(Optional::stream); // <"A", "B">

Stream<T>からIntStreamなど

Stream<Integer> stream1_1 = Stream.of(1, 2, 3, 4); // <1, 2, 3, 4>
IntStream stream1_2 = stream1_1.mapToInt(i -> i); // IntStream<1, 2, 3, 4>

IntStreamなどからStream<Integer>など

IntStream stream1_1 = IntStream.range(1, 5); // IntStream<1, 2, 3, 4>
Stream<Integer> stream1_2 = stream1_1.boxed(); // <1, 2, 3, 4>

IntStream stream2_1 = IntStream.range(1, 5); // IntStream<1, 2, 3, 4>
Stream<String> stream2_2 = stream2_1.mapToObj(i -> String.valueOf(i)); // <"1", "2", "3", "4">

⭐️ フィルター

Widget a = new Widget(RED, 10);
Widget b = new Widget(RED, 15);
Widget c = new Widget(BLUE, 20);
Widget d = new Widget(GREEN, 25);

// 赤色のものを抽出
Stream<Widget> stream1_1 = Stream.of(a, b, c, d); // <a, b, c, d>
Stream<Widget> stream1_2 = stream1_1.filter(Widget::isColorRed); // <a, b>
// 重さが10より大きいものを抽出
Stream<Widget> stream1_3 = Stream.of(a, b, c, d); // <a, b, c, d>
Stream<Widget> stream1_4 = stream1_3.filter(w -> w.getWeight() > 10); // <b, c, d>

// 赤色でないものを抽出(1)
Stream<Widget> stream2_1 = Stream.of(a, b, c, d); // <a, b, c, d>
Stream<Widget> stream2_2 = stream2_1.filter(w -> !w.isColorRed()); // <c, d>
// 赤色でないものを抽出(2)
Stream<Widget> stream2_3 = Stream.of(a, b, c, d); // <a, b, c, d>
Stream<Widget> stream2_4 = stream2_2.filter(Predicate.not(Widget::isColorRed)); // <c, d>

// 等価のものを抽出(1)
Stream<Widget> stream3_1 = Stream.of(a, b, c, d, null); // <a, b, c, d, null>
Stream<Widget> stream3_2= stream3_1.filter(w -> Objects.equals(w, new Widget(RED, 15))); // <b>
// 等価のものを抽出(2)
Stream<Widget> stream3_3 = Stream.of(a, b, c, d, null); // <a, b, c, d, null>
Stream<Widget> stream3_4= stream3_3.filter(Predicate.isEqual(new Widget(RED, 15))); // <b>

// 赤色か青色のものを抽出(1)
Stream<Widget> stream4_1 = Stream.of(a, b, c, d); // <a, b, c, d>
Stream<Widget> stream4_2 = stream4_1.filter(w -> w.isColorRed() || w.isColorBlue()); // <a, b, c>
// 赤色か青色のものを抽出(2)
Stream<Widget> stream4_3 = Stream.of(a, b, c, d); // <a, b, c, d>
Predicate<Widget> isColorRed = Widget::isColorRed; 
// Widget::isColorRedがPredicateである保証がないため
// filter((Widget::isColorRed).or(Widget::isColorBlue))とは書けない
Stream<Widget> stream4_4 = stream4_2.filter(isColorRed.or(Widget::isColorBlue)); // <a, b, c>

重複除去(distinct)

Widget a = new Widget(RED, 10);
Widget b = new Widget(RED, 15);

Stream<Widget> stream1_1 = Stream.of(a, a, b, b, null, null); // <a, a, b, b, null, null>
Stream<Widget> stream1_2 = stream1_1.distinct(); // <a, b, null>

⭐️ ソート


Widget a = new Widget(RED, 10, "A");
Widget b = new Widget(RED, 15, "B");
Widget c = new Widget(BLUE, 20);
Widget d = new Widget(GREEN, 25;

// 重さでソート
Stream<Widget> stream1_1 = Stream.of(b, c, a); // <b, c, a>
Stream<Widget> stream1_2 = stream1_1.sorted(Comparator.comparing(Widget::getWeight)); // <a, b, c>

// 重さでソート(降順)
Stream<Widget> stream2_1 = Stream.of(b, c, a); // <b, c, a>
Stream<Widget> stream2_2 = stream2_1.sorted(Comparator.comparing(Widget::getWeight, Comparator.reverseOrder())); // <c, b, a>

// テキストでソート(nullを最初にする)
Stream<Widget> stream3_1 = Stream.of(b, c, a); // <b, c, a>
Stream<Widget> stream3_2 = stream3_1.sorted(Comparator.comparing(w -> w.getText().orElse(null), Comparator.nullsFirst(Comparator.naturalOrder()))); <c, a, b>

// 第一ソート: 赤色か, 第二ソート: 重さ
Stream<Widget> stream4_1 = Stream.of(b, c, a, d); // <b, c, a, d>
Stream<Widget> stream4_2 = stream4_1.sorted(Comparator.comparing(Widget::isColorRed).thenComparing(Widget::getWeight)); // <c, d, a, b>

最初のN個

Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5); // <1, 2, 3, 4, 5>
Stream<Integer> stream2 = stream1.limit(3); // <1, 2, 3>

最初のN個以外

Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5); // <1, 2, 3, 4, 5>
Stream<Integer> stream2 = stream1.skip(2); // <3, 4, 5>

終端操作

⭐️ Listを作成

Widget a = new Widget(RED, 10);
Widget b = new Widget(RED, 15);
Widget c = new Widget(BLUE, 20);

Stream<Widget> stream = Stream.of(a, b, c); // <a, b, c>
List<Widget> list = stream.collect(Collectors.toList()); // [a, b, c]

Setを作成

Widget a = new Widget(RED, 10);
Widget b = new Widget(RED, 15);
Widget c = new Widget(BLUE, 20);

Stream<Widget> stream = Stream.of(a, b, c); // <a, b, c>
Set<Widget> set = stream.collect(Collectors.toSet()); // {a, b, c}

⭐️ Mapを作成

Widget a = new Widget(RED, 10);
Widget b = new Widget(RED, 15);
Widget c = new Widget(BLUE, 20);

Stream<Widget> stream1 = Stream.of(a, b, c); // <a, b, c>
Map<Integer, Color> map1 = stream.collect(Collectors.toMap(Widget::getWeight, Widget::getColor)); // {10: RED, 15: RED, 20: BLUE}

Stream<Widget> stream2_1 = Stream.of(a, b, c); // <a, b, c>
Map<Color, Widget> map2_1 = stream.collect(Collectors.toMap(Widget::getColor, UnaryOperator.identity(), (w1, w2) -> w1)); // {RED: a, BLUE: c}
Stream<Widget> stream2_2 = Stream.of(a, b, c); // <a, b, c>
Map<Color, Widget> map2_2 = stream.collect(Collectors.toMap(Widget::getColor, UnaryOperator.identity(), (w1, w2) -> w2)); // {RED: b, BLUE: c}
// × エラー (キーが衝突する場合は先の方と後の方のどちらを優先するかを指定する必要がある)
Stream<Widget> stream2_3 = Stream.of(a, b, c); // <a, b, c>
Map<Color, Widget> map2_3 = stream.collect(Collectors.toMap(Widget::getColor, UnaryOperator.identity())); 

Stream<Widget> stream3 = Stream.of(a, b, c); // <a, b, c>
Map<Color, List<Widget>> map3 = stream3.collect(Collectors.groupingBy(Widget::getColor)); // {RED: [a, b], BLUE: [c]}

⭐️ 繰り返し処理(forEach)

Stream<Integer> stream = Stream.of(1, 2, 3); // <1, 2, 3>
stream.forEach(System.out::println);
// 標準出力:
// 1
// 2
// 3

要素数を取得

Stream<Integer> stream = Stream.of(1, 2, 3); // <1, 2, 3>
long cnt = stream.count(); // 3L

⭐️ 文字列を結合

Stream<Widget> stream1 = Stream.of("A", "B", "C"); // <"A", "B", "C">
String str1 = stream1.collect(Collectors.joining()); // "ABC"

Stream<Widget> stream2 = Stream.of("A", "B", "C"); // <"A", "B", "C">
String str2 = stream2.collect(Collectors.joining(", ")); // "A, B, C"

Stream<Widget> stream3 = Stream.of("A", "B", "C"); // <"A", "B", "C">
String str3 = stream3.collect(Collectors.joining(", ", "[", "]")); // "[A, B, C]"

数値の合計を取得

Widget a = new Widget(RED, 10);
Widget b = new Widget(RED, 15);
Widget c = new Widget(BLUE, 20);

Stream<Widget> stream1_1 = Stream.of(a, b, c); // <a, b, c>
IntStream stream1_2 = stream1_1.mapToInt(Widget::getWeight); // <10, 15, 20>
int sum1 = stream1_2.sum(); // 45

Stream<Widget> stream2_1 = Stream.of(a, b, c); // <a, b, c>
int sum2 = stream2.collect(Collectors.summingInt(Widget::getWeight)); // 45

⭐️ 最大値を取得

Widget a = new Widget(RED, 10);
Widget b = new Widget(RED, 15);
Widget c = new Widget(BLUE, 20);

Stream<Widget> stream1 = Stream.of(a, b, c); // <a, b, c>
Optional<Widget> widget = stream1.max(Comparator.comparing(Widget::getWeight)); // <c>

Stream<Widget> stream2_1 = Stream.of(a, b, c); // <a, b, c>
IntStream stream2_2 = stream2_1.mapToInt(Widget::getWeight); // <10, 15, 20>
OptionalInt max = stream2_2.max(); // <20>

⭐️ 最小値を取得

Widget a = new Widget(RED, 10);
Widget b = new Widget(RED, 15);
Widget c = new Widget(BLUE, 20);

Stream<Widget> stream1 = Stream.of(a, b, c); // <a, b, c>
Optional<Widget> widget = stream1.min(Comparator.comparing(Widget::getWeight)); // <a>

Stream<Widget> stream2_1 = Stream.of(a, b, c); // <a, b, c>
IntStream stream2_2 = stream2_1.mapToInt(Widget::getWeight); // <10, 15, 20>
OptionalInt min = stream2_2.min(); // <10>

⭐️ 最初の要素を取得

Stream<Integer> stream = Stream.of(1, 2, 3); // <1, 2, 3>
Optional<Integer> n = stream.findFirst() // <1>

⭐️ 任意の要素を取得

Stream<Integer> stream = Stream.of(1, 2, 3); // <1, 2, 3>
Optional<Integer> n = stream.findAny() // <1>

⭐️ 真偽値を返す

任意の1つ以上の要素が条件に合う

Widget a = new Widget(RED, 10);
Widget b = new Widget(RED, 15);
Widget c = new Widget(BLUE, 20);

Stream<Widget> stream1 = Stream.of(a, b, c); // <a, b, c>
boolean bool1 = stream1.anyMatch(Widget::isColorRed); // true

Stream<Widget> stream2 = Stream.of(c); // <c>
boolean bool2 = stream2.anyMatch(Widget::isColorRed); // false

Stream<Widget> stream3 = Stream.of(); // <>
boolean bool3 = stream3.anyMatch(Widget::isColorRed); // false

全ての要素が条件に合う

Streamが空の場合の挙動に注意

Stream<Widget> stream1 = Stream.of(a, b); // <a, b>
boolean bool1 = stream1.allMatch(Widget::isColorRed); // true

Stream<Widget> stream2 = Stream.of(b, c); // <b, c>
boolean bool2 = stream2.allMatch(Widget::isColorRed); // false

Stream<Widget> stream3 = Stream.of(); // <>
boolean bool3 = stream3.allMatch(Widget::isColorRed); // true

全ての要素が条件に合わない

Streamが空の場合の挙動に注意

Stream<Widget> stream1 = Stream.of(c); // <c>
boolean bool1 = stream1.noneMatch(Widget::isColorRed); // true

Stream<Widget> stream2 = Stream.of(b, c); // <a, b, c>
boolean bool2 = stream2.noneMatch(Widget::isColorRed); // false

Stream<Widget> stream3 = Stream.of(); // <>
boolean bool3 = stream3.noneMatch(Widget::isColorRed); // true

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9