引数: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など(参考)
引数: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など(参考)
引数: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など(参考)
引数: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())); //同じ単語の出現数をカウントする
引数: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など(参考)
//これはつらい
Collections.sort(list, new Comparator<Product>() {
@Override
public int compare(Product o1, Product o2) {
return o1.price().compareTo(o2.price());
}
});
//ただしBeans形式(getPrice()が必要)であること、使いにくい
Collections.sort(products, new BeanComparator("price"));
sort(products, on(Product.class).price());
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);
Arrays.asList("a", "b", "c", "d", "e")
.stream()
.limit(3)
.skip(1)
.forEach(System.out::println); // b c
//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}
引数: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]}
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コレクションからのストリーム処理は、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");
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));
});
final Map<Integer, String> map = new HashMap<Integer, String>() {
{ put(1, "fooVal"); }
{ put(2, "barVal"); }
};
final String string = map.getOrDefault(3, "").toUpperCase();
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));