Skip to content

Instantly share code, notes, and snippets.

@asufana
Last active April 10, 2020 05:44
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save asufana/df8cefbbb8d492fc8baf to your computer and use it in GitHub Desktop.
Save asufana/df8cefbbb8d492fc8baf to your computer and use it in GitHub Desktop.
Java8 StreamAPI

Java8 StreamAPI

filter:抽出する

引数:Predicate(引数を1つ取り、戻り値がboolean)

//DX04で文字列が終わる値を抽出する
Arrays.asList("EPS-DX04", "EPS-SX04", "EPS-AP-DX04", "D3-M-DX04-C")
    .stream()
    .filter(name -> name.matches("^.*DX04$")
    .forEach(System.out::println); // => EPS-DX04 EPS-AP-DX04

//ラムダ式を変数化すると
Predicate<String> regex = name -> name.matches("^.*DX04$");
Arrays.asList("EPS-DX04", "EPS-SX04", "EPS-AP-DX04", "D3-M-DX04-C")
    .stream()
    .filter(regex)
    .forEach(System.out::println);
  • Predicateのデフォルトメソッド:and, or, negate, isEqualなど(参考

forEach:処理する

引数:Consumer(引数を1つ取り、戻り値がない)

//リストの値を出力する
Arrays.asList("EPS-DX04", "EPS-DX04-A", "EPS-DX03")
    .stream()
    .filter(name -> name.matches("^.*DX04$"))
    .forEach(System.out::println); // => EPS-DX04 EPS-AP-DX04

//ラムダ式を変数化すると
final Consumer print = n -> System.out.println(n);
Arrays.asList("EPS-DX04", "EPS-DX04-A", "EPS-DX03")
    .stream()
    .filter(name -> name.matches("^.*DX04$"))
    .forEach(print);
}

戻り値がない void ということは、forEachは何か変更を加える、つまり副作用が発生するということ。コードに出てきたら要注意。

逆に副作用がある場合には、forEachを使うと決めることで、問い合わせと変更を明示的に分離できる。

DDD「副作用をなくす設計」と同じアプローチ(voidを返す関数は副作用を持つ)

  • Consumerのデフォルトメソッド:andThenなど(参考

map:変換する

引数:Function(引数を1つ取り、戻り値が任意の型)

  • Function<? super T, ? extends R> function
//リストの値を文字長に変換する
Arrays.asList("EPS-DX04", "EPS-DX04-A", "EPS-DX03")
    .stream()
    .map(n -> n.length())
    .forEach(System.out::println); // => 8 10 8    

//ラムダ式を変数化すると
final Function<String, Integer> length = n -> n.length();
Arrays.asList("EPS-DX04", "EPS-DX04-A", "EPS-DX03")
    .stream()
    .map(length)
    .forEach(System.out::println);
}

mapは1つの関数を繰り返しリストに適用するための関数。forEachとよく似ているが、mapには戻り値があり、forEachにはない。

戻り値は任意の型なので、型変換をするような処理が可能。

  • Functionのデフォルトメソッド:andThen, composeなど(参考

flatMap:変換する

引数:Function(引数を1つ取り、戻り値が任意のStream型)

  • Function<? super T, ? extends Stream<? extends R>> function
//リストの値をハイフンで区切って、利用されている単語を抽出する
Arrays.asList("EPS-DX04", "EPS-DX04-A", "EPS-DX03")
    .stream()
    .filter(name -> name.matches("^.*DX04$"))
    .flatMap(name -> Arrays.stream(name.split("-"))) //splitで新しいリストを生成
    .distinct() //重複排除
    .forEach(System.out::println); // => EPS DX04

//ラムダ式を変数化すると
final Function<String, Stream<String>> words = n -> Arrays.stream(n.split("-"));
Arrays.asList("EPS-DX04", "EPS-DX04-A", "EPS-DX03")
    .stream()
    .filter(name -> name.matches("^.*DX04$"))
    .flatMap(words)
    .distinct()
    .forEach(System.out::println);

flatMapに渡す関数Functionがリストを返却するものだとすると、Stream<List<T>> だったものが、Stream<List<List<T>>> となってしまう。

それを Stream<List<T>> と維持したまま返却するのが flatMap。

この例でもしmapを使うと、

Arrays.asList("EPS-DX04", "EPS-DX04-A", "EPS-DX03")
    .stream()
    .filter(name -> name.matches("^.*DX04$"))
    .map(name -> Arrays.stream(name.split("-")))
    .collect(Collectors.toList); // => List<Stream<String>> が戻る

よくMapReduceやHadoopの例題にあるwordCountをflatMapで処理する例

FileSystems.getDefault().getPath("input.txt");

Map<String, Long> wordCountMap = Files.lines(path)
    .flatMap(line -> Arrays.stream(line.split(" "))) //空白を単語の区切りと見なす
    .collect(Collectors.groupingBy(s -> s, Collectors.counting())); //同じ単語の出現数をカウントする

reduce:まとめる

引数:BinaryOperator(同じ型の引数を2つ取り、戻り値が引数と同じ型)

System.out.println(Arrays.asList(1, 2, 3, 4)
                         .stream()
                         .reduce((i, j) -> i + j) // Optionalが返る
                         .get()); // => 10

//ラムダ式を変数化すると
final BinaryOperator<Integer> sum = (i, j) -> i + j;
System.out.println(Arrays.asList(1, 2, 3, 4)
                         .stream()
                         .reduce(sum)
                         .get());

2つの値に対して同時に関数を適用し、単一の値にする。後述するCollectorを使うのが一般的。

  • BinaryOperatorのデフォルトメソッド:minBy, maxByなど(参考

Sort

Java8まで

//これはつらい
Collections.sort(list, new Comparator<Product>() {
	@Override
	public int compare(Product o1, Product o2) {
		return o1.price().compareTo(o2.price());
	}
});

Commonsでやると

//ただしBeans形式(getPrice()が必要)であること、使いにくい
Collections.sort(products, new BeanComparator("price"));

Lambdajでやると

sort(products, on(Product.class).price());

Java8から

products.stream()
    .sorted((o1, o2) -> o1.price.compareTo(o2.price))
    .forEach(System.out::println);

//メソッド参照がいい
products.stream()
    .sorted(Comparator.comparing(Product::getPrice))
    .forEach(System.out::println);

降順

products.stream()
    .sorted(Comparator.comparing(Product::getPrice).reversed())
    .forEach(System.out::println);

複合ソート

products.stream()
    .sorted(Comparator.comparing(Product::getPrice).thenComparing(Product::getName))
    .forEach(System.out::println);

Limit, Skip

Arrays.asList("a", "b", "c", "d", "e")
        .stream()
        .limit(3)
        .skip(1)
        .forEach(System.out::println); // b c

コレクター

toXXX

//toSet
final Set<String> set =
    Arrays.asList("EPS-DX04",
                  "EPS-DX04",
                  "EPS-DX03",
                  "EPS-DX03")
          .stream()
          .collect(Collectors.toSet()); // => [EPS-DX03, EPS-DX04]

//toList
final List<String> list =
    Arrays.asList("EPS-DX04",
                  "EPS-DX04",
                  "EPS-DX03",
                  "EPS-DX03")
          .stream()
          .collect(Collectors.toList()); // => [EPS-DX04, EPS-DX04, EPS-DX03, EPS-DX03]

//toMap
final List<String, Integer> map =
    Arrays.asList("EPS-DX04",
                  "EPS-DX04",
                  "EPS-DX03",
                  "EPS-DX03")
          .stream()
          //重複除外しておかないとエラーになるよ
          .distinct()
          //toMapの第一引数がKey、第二引数がValueに当たる
          .collect(Collectors.toMap(v -> v, v -> v.length())); // => {EPS-DX03=8, EPS-DX04=8}

groupingBy:Listコレクション を Mapコレクション に変換する

引数:Function(引数を1つ取り、戻り値が任意の型)

final Map<Integer, List<String>> map =
    Arrays.asList("EPS-DX04", "EPS-DX04-A", "EPS-DX03")
        .stream()
        .collect(Collectors.groupingBy(name -> name.length())); // => {8=[EPS-DX04, EPS-DX03], 10=[EPS-DX04-A]}

//ラムダ式を変数化すると
final Function<String, Integer> length = n -> n.length();
final Map<Integer, List<String>> map =
    Arrays.asList("EPS-DX04", "EPS-DX04-A", "EPS-DX03")
        .stream()
        .collect(Collectors.groupingBy(length));
    public Map<Field, List<Annotation>> getFieldAnnotationsMap() {
        return Arrays.asList(getClass().getDeclaredFields())
        		.stream()
                        .collect(Collectors.toMap(field -> field,
                                                    field -> Arrays.asList(field.getAnnotations())));
    }

キーを条件式として記述することもできる

final Map<String, List<String>> map =
    Arrays.asList("EPS-DX04", "EPS-DX04-A", "EPS-DX03")
        .stream()
        .collect(Collectors.groupingBy(name -> {
            if(name.indexOf("DX04") != -1) return "DX04";
            if(name.indexOf("DX03") != -1) return "DX03";
            return "UNKNOWN";
        })); // => {DX03=[EPS-DX03], DX04=[EPS-DX04, EPS-DX04-A]}

//ラムダ式を変数化すると
final Function<String, String> type = name -> {
    if (name.indexOf("DX04") != -1) { return "DX04"; }
    if (name.indexOf("DX03") != -1) { return "DX03"; }
    return "UNKNOWN";
};
final Map<String, List<String>> map =
    Arrays.asList("EPS-DX04", "EPS-DX04-A", "EPS-DX03")
        .stream()
        .collect(Collectors.groupingBy(type));

groupingByの第二引数にCollector処理を渡すと、任意のkeyだけでなく、任意のvalueのMapコレクションが生成できる

groupingBy(Function, Collector)シグネチャ

//カウント
final Map<Integer, Long> map = 
    Arrays.asList("EPS-DX04", "EPS-DX04-A", "EPS-DX03")
        .stream()
        .collect(
            Collectors.groupingBy(name -> name.length(),
            Collectors.counting()
        )); // => {8=2, 10=1}

//平均値
final Map<String, List<String>> map =
    Arrays.asList("EPS-DX04", "EPS-DX04-A", "EPS-DX03")
        .stream()
        .collect(
            Collectors.groupingBy(name -> {
                if(name.indexOf("DX04") != -1) return "DX04";
                if(name.indexOf("DX03") != -1) return "DX03";
                return "UNKNOWN";
            },
            Collectors.averageingInt(name -> name.length())
        )); // => {DX03=8.0, DX04=9.0}

//valueリストの変換
final Map<Integer, List<String>> map =
    Arrays.asList("EPS-DX04", "EPS-DX04-A", "EPS-DX03")
          .stream()
          .collect(
              Collectors.groupingBy(name -> name.length(),
              Collectors.mapping(name -> name.substring(0, 1), Collectors.toList())
          )); // => {8=[E, E], 10=[E]}

collectingAndThen:仕上げ処理を追加する

Collectors#toListで抽出したリストをイミュータブルにする

final List<String> immutableList =
    Arrays.asList("EPS-DX04", "EPS-DX04-A", "EPS-DX03")
            .stream()
            .collect(
                Collectors.collectingAndThen(Collectors.toList(), list -> Collections.unmodifiableList(list))
            );
immutableList.remove(0); // => UnsupportedOperationException

mapコレクションのstream

mapコレクションからのストリーム処理は、map.entrySet().stream()

final List<Integer> keys = map.entrySet()
    .stream()
    .map(Entry::getKey) //mapのキー要素だけを抽出(entry -> entry.getKey() のメソッド参照)
    .collect(Collectors.toList());

key, valueへのアクセス

map.entrySet()
    .stream()
    .forEach((k, v) -> System.out.println(k + ":" + v));

Map<Key, List> の Valueリストをネストラムダで処理する

final Map<String, List<String>> map =
    Arrays.asList("EPS-DX04", "EPS-DX04-A", "EPS-DX03", "EPS-DX03-A")
        .stream()
        .collect(Collectors.groupingBy(name -> {
            if(name.indexOf("DX04") != -1) return "DX04";
            if(name.indexOf("DX03") != -1) return "DX03";
            return "UNKNOWN";
        })); // => {DX03=[EPS-DX03, EPS-DX03-A], DX04=[EPS-DX04, EPS-DX04-A]}

final Map<String, Long> map2 =
    map.entrySet()
        .stream()
        .collect(Collectors.toMap(
            Entry::getKey,
            entry -> entry.getValue()
                .stream()
                .filter(name -> name.matches("^.*-A$"))
                .count())); // => {DX03=1, DX04=1}

メソッド参照(補筆)

Consumerインスタンスとして保持できる

final Consumer<String> print = System.out::println;
print.accept("hoge");

Mapインターフェースの拡張

forEachメソッド:mapのループ処理が簡単に

final Map<Integer, String> map = new HashMap<Integer, String>() {
    { put(1, "fooVal"); }
    { put(2, "barVal"); }
};

map.forEach((k, v) -> {
    //型推論もしてくれる
    System.out.println(String.format("%s:%s", k, v));
});

getOrDefaultメソッド:nullチェック不要に

final Map<Integer, String> map = new HashMap<Integer, String>() {
    { put(1, "fooVal"); }
    { put(2, "barVal"); }
};

final String string = map.getOrDefault(3, "").toUpperCase();

merge

list1.stream().collect(
        () -> new HashMap<Integer, Integer>(),
        (map, item) -> map.merge(item.getKey(), item.getValue(), (x, y) -> x + y),
        (left, right) -> left.putAll(right))
    .forEach((k, v) -> System.out.println(k + ":" + v));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment