Java8のStreamとDateTime APIで日付のストリームを作る
今回は小ネタです。前回の続編と言えなくもないかもです。
Java8のStreamとDateTime APIを使って、Stream的に日付の順列を処理する方法です。
インターフェイス
fromDate
からtoDate
までの日付の順列をStream
で返すようにします。
以降、Stream<LocalDate>
のことを、ここでは「日付ストリーム」と呼びます。
// import java.time.*; // import java.util.*; // import java.util.function.*; // import java.util.stream.*; static Stream<LocalDate> dateStream(LocalDate fromDate, LocalDate toDate) { // TODO not impl yet }
あとは、この日付ストリームを使って、forEach
するなりflatMap
するなりします。
実装1: 有限数列から日付ストリームを得る
有限の数列を作ってから、map
操作によりそれぞれの数を日付に変換します。
- 日数を割り出す
- 日数分の数列を生成する (0, 1, 2, .... 30)
- 数列を
fromDate
に+n日したLocalDate
に変換
// 日数 int days = fromDate.until(toDate).getDays() + 1; // fromDateに加算する値の数列 IntStream numbers = IntStream.range(0, days); // 数列→fromDateに日数を加算した日付ストリーム に変換 Stream<LocalDate> dateStream = numbers.mapToObj(x -> fromDate.plusDays(x)); return dateStream;
最後のコード部分3行は、通常は1行で書くところですが、意味をはっきりさせるために分割しています。
実装2: 無限の日付ストリーム+limit
fromDate
から+1日を繰り返す無限の日付ストリームを生成し、日数で制限(limit
)します。
- 日数を割り出す
fromDate
から+1日を繰り返す無限の日付ストリームを生成limit
で日数と同じだけ取り出す(日数で打ち切る)
追記(2017-02-20): コメントで誤りをご指摘いただきました。これは実に酷いミスですね...
// 日数 long days = ChronoUnit.DAYS.between(fromDate, toDate); // int days = fromDate.until(toDate).getDays() + 1; // => 訂正 2017-02-20 // 誤り! fromDate.until(toDate) は java.time.Periodを返し、 // getDays() は Period の日数しか返さない // +1日を繰り返す無限日付シーケンスを生成 Stream<LocalDate> generator = Stream.iterate(fromDate, x -> x.plusDays(1)); // 日数で打ち切る Stream<LocalDate> dateStream = generator.limit(days); return dateStream;
(架空の)実装3: 無限の日付ストリーム+takeWhile
HaskellやScalaなどのtakeWhile
のように、Predicate<T>
を取るメソッドがあれば、日数を取得する必要も無かったんですが...残念。
追記(2017-02-20): 訂正ついでに。Java9ではtakeWhile
が実装されるようですね。使い方は、未確認ですが、たぶん下記のコードをそのまま使えると思います。
// コンパイルエラー: そんなメソッド(takeWhile)はありません! return Stream.iterate(fromDate, x -> x.plusDays(1)).takeWhile(x -> !x.isAfter(toDate)); //
それっぽいことを実現するには、Spliterators.spliteratorUnknownSize
などを使ってできないこともないですが。
おしまい。