3

How can we use the Java Streams approach to collecting objects generated in a for loop?

For example, here we generate one LocalDate object for each day in a month represented by YearMonth by repeatedly calling YearMonth::atDay.

YearMonth ym = YearMonth.of( 2017 , Month.AUGUST ) ;
List<LocalDate> dates = new ArrayList<>( ym.lengthOfMonth() );
for ( int i = 1 ; i <= ym.lengthOfMonth () ; i ++ ) {
    LocalDate localDate = ym.atDay ( i );
    dates.add( localDate );
}

Can this be rewritten using streams?

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154

3 Answers3

8

It can be rewritten starting with an IntStream:

YearMonth ym = YearMonth.of(2017, Month.AUGUST);
List<LocalDate> dates =
        IntStream.rangeClosed(1, ym.lengthOfMonth())
        .mapToObj(ym::atDay)
        .collect(Collectors.toList());

Each integer value from the IntStream is mapped to the desired date and then the dates are collected in a list.

Markus Benko
  • 1,507
  • 8
  • 8
  • What controls the instantiation of the `List`? What will the concrete object behind `List` be? In the [Answer by Górkiewicz](http://stackoverflow.com/a/42285720/642706), I see we explicitly define an `ArrayList`. How is that task accomplished here? I see the doc for `toList` says “ no guarantees on the type, mutability, serializability, or thread-safety of the List returned”. Does that mean the backing concrete class used is none of my business/concern? That would be understandable and acceptable; I'm just curious. – Basil Bourque Feb 16 '17 at 22:24
  • 2
    You wanted `List` and you get exactly that. If you want to explicitly define which List implementation should be used you can do also: `Collectors.toCollection(ArrayList::new)` – Markus Benko Feb 16 '17 at 22:29
  • `Collectors.toCollection(ArrayList::new)` is good, but adds verbosity. – Grzegorz Górkiewicz Feb 16 '17 at 22:34
  • 3
    I understand now. [A] If I care about the backing concrete implementation of `List`, then I must provide a [`Supplier`](https://docs.oracle.com/javase/8/docs/api/java/util/function/Supplier.html) passed in a call to [`Collectors.toCollection`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html#toCollection-java.util.function.Supplier-). In this case the Supplier takes the form of the method reference `ArrayList::new`. [B] If I do *not* care about the concrete class implementing the `List` interface, then use the shorter simpler code seen in your Answer. – Basil Bourque Feb 16 '17 at 22:38
2

Replace your for loop with an IntStream:

YearMonth ym = YearMonth.of(2017, Month.AUGUST);
List<LocalDate> dates = new ArrayList<>(ym.lengthOfMonth());
IntStream.rangeClosed(1, ym.lengthOfMonth())
         .forEach(i -> dates.add(ym.atDay(i)));
Grzegorz Górkiewicz
  • 4,496
  • 4
  • 22
  • 38
1

In Java 9 a special method datesUntil is added to LocalDate which can generate a stream of dates:

LocalDate start = LocalDate.of(2017, Month.AUGUST, 1);
List<LocalDate> dates = start.datesUntil(start.plusMonths(1))
        .collect(Collectors.toList());
Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334