Parallel Streams – Streams

16.9 Parallel Streams

The Stream API makes it possible to execute a sequential stream in parallel without rewriting the code. The primary reason for using parallel streams is to improve performance, but at the same time ensuring that the results obtained are the same, or at least compatible, regardless of the mode of execution. Although the API goes a long way to achieve its aim, it is important to understand the pitfalls to avoid when executing stream pipelines in parallel.

Building Parallel Streams

The execution mode of an existing stream can be set to parallel by calling the parallel() method on the stream (p. 933). The parallelStream() method of the Collection interface can be used to create a parallel stream with a collection as the data source (p. 897). No other code is necessary for parallel execution, as the data partitioning and thread management for a parallel stream are handled by the API and the JVM. As with any stream, the stream is not executed until a terminal operation is invoked on it.

The isParallel() method of the stream interfaces can be used to determine whether the execution mode of a stream is parallel (p. 933).

Parallel Stream Execution

The Stream API allows a stream to be executed either sequentially or in parallel— meaning that all stream operations can execute either sequentially or in parallel. A sequential stream is executed in a single thread running on one CPU core. The elements in the stream are processed sequentially in a single pass by the stream operations that are executed in the same thread (p. 891).

A parallel stream is executed by different threads, running on multiple CPU cores in a computer. The stream elements are split into substreams that are processed by multiple instances of the stream pipeline being executed in multiple threads. The partial results from processing of each substream are merged (or combined) into a final result (p. 891).

Parallel streams utilize the Fork/Join Framework (§23.3, p. 1447) under the hood for executing parallel tasks. This framework provides support for the thread management necessary to execute the substreams in parallel. The number of threads employed during parallel stream execution is dependent on the CPU cores in the computer.

Figure 16.12, p. 963, illustrates parallel functional reduction using the three-argument reduce(identity, accumulator, combiner) terminal operation (p. 962).

Figure 16.14, p. 967, illustrates parallel mutable reduction using the three-argument collect(supplier, accumulator, combiner) terminal operation (p. 966).

Accessing Time Units in a Duration – Date and Time

Accessing Time Units in a Duration

The Duration class provides the getUNIT() methods to read the individual values of its time units. The class also has methods to check if the period has a negative value or if its value is zero.

Click here to view code image

Duration dx = Duration.ofSeconds(12L*60*60, 500_000_000L); // PT12H0.5S
out.println(dx.getNano());                                 // 500000000
out.println(dx.getSeconds());                              // 43200 (i.e. 12 hrs.)

Reading the individual values of time units of a Duration object can also be done using the get(unit) method, where only the NANOS and SECONDS units are allowed. A list of temporal units that are accepted by the get(unit) method can be obtained by calling the getUnits() of the Duration class.

Click here to view code image

out.println(dx.get(ChronoUnit.NANOS));       // 500000000
out.println(dx.get(ChronoUnit.SECONDS));     // 43200
out.println(dx.get(ChronoUnit.MINUTES));     // UnsupportedTemporalTypeException
out.println(dx.getUnits());                  // [Seconds, Nanos]

The class Duration provides the method toUNIT() to derive the total length of the duration in the unit designated by the method name. The seconds and the nanoseconds are converted to this unit, if necessary.

Click here to view code image

out.println(“Days:    ” + dx.toDays());      // Days:    0
out.println(“Hours:   ” + dx.toHours());     // Hours:   12
out.println(“Minutes: ” + dx.toMinutes());   // Minutes: 720
out.println(“Millis:  ” + dx.toMillis());    // Millis:  43200500
out.println(“Nanos:   ” + dx.toNanos());     // Nanos:   43200500000000

int getNano()
long getSeconds()

Return the number of nanoseconds and seconds in this duration, respectively—not the total length of the duration. Note that the first method name is getNano, without the s.

long get(TemporalUnit unit)

Returns the value of the specified unit in this Duration—not the total length of the duration. The only supported ChronoUnit constants are NANOS and SECONDS (p. 1044). Other units result in an UnsupportedTemporalTypeException.

List<TemporalUnit> getUnits()

Returns the list of time units supported by this duration: NANOS and SECONDS (p. 1044). These time units can be used with the get(unit) method.

long toDays()
long toHours()
long toMinutes()
long toMillis()
long toNanos()

Return the total length of this duration, converted to the unit designated by the method, if necessary. Note that there is no toSeconds() method. Also, the method name is toNanos—note the s at the end.

The methods toMillis() and toNanos() throw an ArithmeticException in case of number overflow.

boolean isNegative()

Determines whether the total length of this duration is negative.

boolean isZero()

Determines whether the total length of this duration is zero.

The LocalDateTime Class – Date and Time

The LocalDateTime Class

The class LocalDateTime allows the date and the time to be combined into one entity, which is useful for representing such concepts as appointments that require both a time and a date. The of() methods in the LocalDateTime class are combinations of the of() methods from the LocalTime and LocalDate classes, taking values of both time and date fields as arguments. The toString() method of this class will format the temporal fields according to the ISO standard (§18.6, p. 1134):

uuuu-MM-ddTHH:mm:ss.SSSSSSSSS

The letter T separates the values of the date fields from those of the time fields.

Click here to view code image

// 2021-04-28T12:15
LocalDateTime dt1 = LocalDateTime.of(2021, 4, 28, 12, 15);
// 2021-08-19T14:00
LocalDateTime dt2 = LocalDateTime.of(2021, Month.AUGUST, 19, 14, 0);

The LocalDateTime class also provides an of() method that combines a LocalDate object and a LocalTime object. The first declaration in the next code snippet combines a date and a time. The static field LocalTime.NOON defines the time at noon. In addition, the LocalTime class provides the instance method atDate(), which takes a date as an argument and returns a LocalDateTime object. The second declaration combines the time at noon with the date referred to by the reference date1. Conversely, the LocalDate class provides the overloaded instance method atTime() to combine a date with a specified time. In the last two declarations, the atTime() method is passed a LocalTime object and values for specific time fields, respectively.

Click here to view code image

// LocalDate date1 is 1969-07-20.
LocalDateTime dt3 = LocalDateTime.of(date1, LocalTime.NOON); // 1969-07-20T12:00
LocalDateTime dt4 = LocalTime.of(12, 0).atDate(date1);       // 1969-07-20T12:00
LocalDateTime dt5 = date1.atTime(LocalTime.NOON);            // 1969-07-20T12:00
LocalDateTime dt6 = date1.atTime(12, 0);                     // 1969-07-20T12:00

As a convenience, each temporal class provides a static method now() that reads the system clock and returns the values for the relevant temporal fields in an instance of the target class.

Click here to view code image

LocalTime currentTime = LocalTime.now();
LocalDate currentDate = LocalDate.now();
LocalDateTime currentDateTime = LocalDateTime.now();

Example 17.1 includes the different ways to create temporal objects that we have discussed so far.

Click here to view code image

// LocalTime
LocalDateTime atDate(LocalDate date)

Returns a LocalDateTime that combines this time with the specified date.

Click here to view code image

// LocalDate
LocalDateTime atTime(LocalTime time)
LocalDateTime atTime(int hour, int minute)
LocalDateTime atTime(int hour, int minute, int second)
LocalDateTime atTime(int hour, int minute, int second, int nanoOfSecond)
LocalDateTime atStartOfDay()

Return a LocalDateTime that combines this date with the specified values for time fields. The second and nanosecond fields are set to zero, if their values are not specified. In the last method, this date is combined with the time at midnight.

Click here to view code image

// LocalDateTime
ZonedDateTime atZone(ZoneId zone)

Returns a ZonedDateTime by combining this date-time with the specified time zone (p. 1072).

Click here to view code image

// LocalTime, LocalDate, LocalDateTime, respectively.
static LocalTime now()
static LocalDate now()
static LocalDateTime now()

Each temporal class has this static factory method, which returns either the current time, date, or date-time from the system clock.

Example 17.1 Creating Local Dates and Local Times

Click here to view code image

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;
public class CreatingTemporals {
  public static void main(String[] args) {
    // Creating a specific time from time-based values:
    LocalTime time1 = LocalTime.of(8, 15, 35, 900);// 08:15:35.000000900
    LocalTime time2 = LocalTime.of(16, 45);        // 16:45
//  LocalTime time3 = LocalTime.of(25, 13, 30);    // DateTimeException
    System.out.println(“Surveillance start time: ” + time1);
    System.out.println(“Closing time: ” + time2);
    // Creating a specific date from date-based values:
    LocalDate date1 = LocalDate.of(1969, 7, 20);            // 1969-07-20
    LocalDate date2 = LocalDate.of(-3113, Month.AUGUST, 11);// -3113-08-11
//  LocalDate date3 = LocalDate.of(2021, 13, 11);           // DateTimeException
//  LocalDate date4 = LocalDate.of(2021, 2, 29);            // DateTimeException
    System.out.println(“Date of lunar landing:        ” + date1);
    System.out.println(“Start Date of Mayan Calendar: ” + date2);
    // Creating a specific date-time from date- and time-based values.
    // 2021-04-28T12:15
    LocalDateTime dt1 = LocalDateTime.of(2021, 4, 28, 12, 15);
    // 2021-08-17T14:00
    LocalDateTime dt2 = LocalDateTime.of(2021, Month.AUGUST, 17, 14, 0);
    System.out.println(“Car service appointment: ” + dt1);
    System.out.println(“Hospital appointment:    ” + dt2);
    // Combining date and time objects.
    // 1969-07-20T12:00
    LocalDateTime dt3 = LocalDateTime.of(date1, LocalTime.NOON);
    LocalDateTime dt4 = LocalTime.of(12, 0).atDate(date1);
    LocalDateTime dt5 = date1.atTime(LocalTime.NOON);
    LocalDateTime dt6 = date1.atTime(12, 0);
    System.out.println(“Factory date-time combo: ” + dt3);
    System.out.println(“Time with date combo:    ” + dt4);
    System.out.println(“Date with time combo:    ” + dt5);
    System.out.println(“Date with explicit time combo: ” + dt6);
    // Current time:
    LocalTime currentTime = LocalTime.now();
    System.out.println(“Current time:      ” + currentTime);
    // Current date:
    LocalDate currentDate = LocalDate.now();
    System.out.println(“Current date:      ” + currentDate);
    // Current date and time:
    LocalDateTime currentDateTime = LocalDateTime.now();
    System.out.println(“Current date-time: ” + currentDateTime);
  }
}

Possible output from the program:

Click here to view code image Surveillance start time: 08:15:35.000000900
Closing time: 16:45
Date of lunar landing:        1969-07-20
Start Date of Mayan Calendar: -3113-08-11
Car service appointment: 2021-04-28T12:15
Hospital appointment:    2021-08-17T14:00
Factory date-time combo: 1969-07-20T12:00
Time with date combo:    1969-07-20T12:00
Date with time combo:    1969-07-20T12:00
Date with explicit time combo: 1969-07-20T12:00
Current time:      10:55:41.296744
Current date:      2021-03-05
Current date-time: 2021-03-05T10:55:41.299318

Reducing – Streams

Reducing

Collectors that perform common statistical operations, such as counting, averaging, and so on, are special cases of functional reduction that can be implemented using the Collectors.reducing() method.

Click here to view code image

static <T> Collector<T,?,Optional<T>> reducing(BinaryOperator<T> bop)

Returns a collector that performs functional reduction, producing an Optional with the cumulative result of applying the binary operator bop on the input elements: e1 bop e2 bop e3 …, where each ei is an input element. If there are no input elements, an empty Optional<T> is returned.

Note that the collector reduces input elements of type T to a result that is an Optional of type T.

Click here to view code image

static <T> Collector<T,?,T> reducing(T identity, BinaryOperator<T> bop)

Returns a collector that performs functional reduction, producing the cumulative result of applying the binary operator bop on the input elements: identity bop e1 bop e2 …, where each ei is an input element. The identity value is the initial value to accumulate. If there are no input elements, the identity value is returned.

Note that the collector reduces input elements of type T to a result of type T.

Click here to view code image

static <T,U> Collector<T,?,U> reducing(
       U                               identity,
       Function<? super T,? extends U> mapper,
       BinaryOperator<U>               bop)

Returns a collector that performs a map-reduce operation. It maps each input element of type T to a mapped value of type U by applying the mapper function, and performs functional reduction on the mapped values of type U by applying the binary operator bop. The identity value of type U is used as the initial value to accumulate. If the stream is empty, the identity value is returned.

Note that the collector reduces input elements of type T to a result of type U.

Collectors returned by the Collectors.reducing() methods effectively perform equivalent functional reductions as the reduce() methods of the stream interfaces. However, the three-argument method Collectors.reducing(identity, mapper, bop) performs a map-reduce operation using a mapper function and a binary operator bop, whereas the Stream.reduce(identity, accumulator, combiner) performs a reduction using an accumulator and a combiner (p. 955). The accumulator is a BiFunction<U,T,U> that accumulates a partially accumulated result of type U with an element of type T, whereas the bop is a BinaryOperator<U> that accumulates a partially accumulated result of type U with an element of type U.

The following comparators are used in the examples below:

Click here to view code image

// Comparator to compare CDs by title.
Comparator<CD> cmpByTitle = Comparator.comparing(CD::title);        // (1)
// Comparator to compare strings by their length.
Comparator<String> byLength = Comparator.comparing(String::length); // (2)

The collector returned by the Collectors.reducing() method is used as a standalone collector at (3) to find the CD with the longest title. The result of the operation is an Optional<String> as there might not be any input elements. This operation is equivalent to using the Stream.reduce() terminal operation at (4).

Click here to view code image

Optional<String> longestTitle1 = CD.cdList.stream()
    .map(CD::title)
    .collect(Collectors.reducing(
        BinaryOperator.maxBy(byLength)));            // (3) Standalone collector
System.out.println(longestTitle1.orElse(“No title”));// Keep on Erasing
Optional<String> longestTitle2 = CD.cdList.stream()  // Stream<CD>
    .map(CD::title)                                  // Stream<String>
    .reduce(BinaryOperator.maxBy(byLength));         // (4) Stream.reduce(bop)

The collector returned by the one-argument Collectors.reducing() method is used as a downstream collector at (5) to find the CD with the longest title in each group classified by the year a CD was released. The collector at (5) is equivalent to the collector returned by the Collectors.maxBy(cmpByTitle) method.

Click here to view code image

Map<Year, Optional<CD>> cdWithMaxTitleByYear = CD.cdList.stream()
    .collect(Collectors.groupingBy(
         CD::year,
         Collectors.reducing(                        // (5) Downstream collector
             BinaryOperator.maxBy(cmpByTitle))
         ));
System.out.println(cdWithMaxTitleByYear);
// {2017=Optional[<Jaav, “Java Jive”, 8, 2017, POP>],
//  2018=Optional[<Funkies, “Lambda Dancing”, 10, 2018, POP>]}
System.out.println(cdWithMaxTitleByYear.get(Year.of(2018))
                       .map(CD::title).orElse(“No title”)); // Lambda Dancing

The collector returned by the three-argument Collectors.reducing() method is used as a downstream collector at (6) to find the longest title in each group classified by the year a CD was released. Note that the collector maps a CD to its title. The longest title is associated with the map value for each group classified by the year a CD was released. The collector will return an empty string (i.e., the identity value “”) if there are no CDs in the stream. In comparison, the collector Collectors.mapping() at (7) also maps a CD to its title, and uses the downstream collector Collectors.maxBy(byLength) at (7) to find the longest title (p. 993). The result in this case is an Optional<String>, as there might not be any input elements.

Click here to view code image

Map<Year, String> longestTitleByYear = CD.cdList.stream()
    .collect(Collectors.groupingBy(
         CD::year,
         Collectors.reducing(“”, CD::title,          // (6) Downstream collector
             BinaryOperator.maxBy(byLength))
         ));
System.out.println(longestTitleByYear);  // {2017=Java Jive, 2018=Keep on Erasing}
System.out.println(longestTitleByYear.get(Year.of(2018)));      // Keep on Erasing
Map<Year, Optional<String>> longestTitleByYear2 = CD.cdList.stream()
    .collect(Collectors.groupingBy(
         CD::year,
         Collectors.mapping(CD::title,               // (7) Downstream collector
             Collectors.maxBy(byLength))
         ));
System.out.println(longestTitleByYear2);
// {2017=Optional[Java Jive], 2018=Optional[Keep on Erasing]}
System.out.println(longestTitleByYear2.get(Year.of(2018))
                       .orElse(“No title.”));        // Keep on Erasing

The pipeline below groups CDs according to the year they were released. For each group, the collector returned by the three-argument Collectors.reducing() method performs a map-reduce operation at (8) to map each CD to its number of tracks and accumulate the tracks in each group. This map-reduce operation is equivalent to the collector returned by the Collectors.summingInt() method at (9).

Click here to view code image

Map<Year, Integer> noOfTracksByYear = CD.cdList.stream()
    .collect(Collectors.groupingBy(
         CD::year,
         Collectors.reducing(                        // (8) Downstream collector
             0, CD::noOfTracks, Integer::sum)));
System.out.println(noOfTracksByYear);                   // {2017=14, 2018=28}
System.out.println(noOfTracksByYear.get(Year.of(2018)));// 28
Map<Year, Integer> noOfTracksByYear2 = CD.cdList.stream()
    .collect(Collectors.groupingBy(
         CD::year,
         Collectors.summingInt(CD::noOfTracks)));    // (9) Special case collector

Factors Affecting Performance – Streams

Factors Affecting Performance

There are no guarantees that executing a stream in parallel will improve the performance. In this subsection we look at some factors that can affect performance.

Benchmarking

In general, increasing the number of CPU cores and thereby the number of threads that can execute in parallel only scales performance up to a threshold for a given size of data, as some threads might become idle if there is no data left for them to process. The number of CPU cores boosts performance to a certain extent, but it is not the only factor that should be considered when deciding to execute a stream in parallel.

Inherent in the total cost of parallel processing is the start-up cost of setting up the parallel execution. At the onset, if this cost is already comparable to the cost of sequential execution, not much can be gained by resorting to parallel execution.

A combination of the following three factors can be crucial in deciding whether a stream should be executed in parallel:

  • Sufficiently large data size

The size of the stream must be large enough to warrant parallel processing; otherwise, sequential processing is preferable. The start-up cost can be too prohibitive for parallel execution if the stream size is too small.

  • Computation-intensive stream operations

If the stream operations are small computations, then the stream size should be proportionately large as to warrant parallel execution. If the stream operations are computation-intensive, the stream size is less significant, and parallel execution can boost performance.

  • Easily splittable stream

If the cost of splitting the stream into substreams is higher than processing the substreams, employing parallel execution can be futile. Collections like Array-Lists, HashMaps, and simple arrays are efficiently splittable, whereas LinkedLists and IO-based data sources are less efficient in this regard.

Benchmarking—that is, measuring performance—is strongly recommended to decide whether parallel execution will be beneficial. Example 16.14 illustrates a simple scheme where reading the system clock before and after a stream is executed can be used to get a sense of how well a stream performs.

The class StreamBenchmarks in Example 16.14 defines five methods, at (1) through (5), that compute the sum of values from 1 to n. These methods compute the sum in various ways. Each method is executed with four different values of n; that is, the stream size is the number of values for summation. The program prints the benchmarks for each method for the different values of n, which of course can vary, as many factors can influence the results—the most significant one being the number of CPU cores on the computer.

  • The methods seqSumRangeClosed() at (1) and parSumRangeClosed() at (2) perform the computation on a sequential and a parallel stream, respectively, that are created with the closeRange() method.

Click here to view code image

return LongStream.rangeClosed(1L, n).sum();                // Sequential stream

return LongStream.rangeClosed(1L, n).parallel().sum();     // Parallel stream

Benchmarks from Example 16.14:

  Size   Sequential Parallel
   1000   0.05681   0.11031
  10000   0.06698   0.13979
 100000   0.71274   0.52627
1000000   7.02237   4.37249

The terminal operation sum() is not computation-intensive. The parallel stream starts to show better performance when the number of values approaches 100000. The stream size is then significantly large for the parallel stream to show better performance. Note that the range of values defined by the arguments of the rangeClosed() method can be efficiently split into substreams, as its start and end values are provided.

  • The methods seqSumIterate() at (3) and parSumIterate() at (4) return a sequential and a parallel sequential stream, respectively, that is created with the iterate() method.

Click here to view code image

return LongStream.iterate(1L, i -> i + 1).limit(n).sum();            // Sequential

return LongStream.iterate(1L, i -> i + 1).limit(n).parallel().sum(); // Parallel

Benchmarks from Example 16.14:

  Size   Sequential Parallel
   1000   0.08645   0.34696
  10000   0.35687   1.27861
 100000   3.24083  11.38709
1000000  29.92285 117.87909

The method iterate() creates an infinite stream, and the limit() intermediate operation truncates the stream according to the value of n. The performance of both streams degrades fast when the number of values increases. However, the parallel stream performs worse than the sequential stream in all cases. The values generated by the iterate() method are not known before the stream is executed, and the limit() operation is also stateful, making the process of splitting the values into substreams inefficient in the case of the parallel stream.

  • The method iterSumLoop() at (5) uses a for(;;) loop to compute the sum.

Benchmarks from Example 16.14:

  Size   Iterative
   1000   0.00586
  10000   0.02330
 100000   0.22352
1000000   2.49677

Using a for(;;) loop to calculate the sum performs best for all values of n compared to the streams, showing that significant overhead is involved in using streams for summing a sequence of numerical values.

In Example 16.14, the methods measurePerf() at (6) and xqtFunctions() at (13) create the benchmarks for functions passed as parameters. In the measurePerf() method, the system clock is read at (8) and the function parameter func is applied at (9). The system clock is read again at (10) after the function application at (9) has completed. The execution time calculated at (10) reflects the time for executing the function. Applying the function func evaluates the lambda expression or the method reference implementing the LongFunction interface. In Example 16.14, the function parameter func is implemented by method references that call methods, at (1) through (5), in the StreamBenchmarks class whose execution time we want to measure.

Click here to view code image

public static <R> double measurePerf(LongFunction<R> func, long n) { // (6)
  // …
  double start = System.nanoTime();                                // (8)
  result = func.apply(n);                                          // (9)
  double duration = (System.nanoTime() – start)/1_000_000;         // (10) ms.
  // …
}

Example 16.14 Benchmarking

Click here to view code image

import java.util.function.LongFunction;
import java.util.stream.LongStream;
/*
 * Benchmark the execution time to sum numbers from 1 to n values
 * using streams.
 */
public final class StreamBenchmarks {
  public static long seqSumRangeClosed(long n) {                       // (1)
    return LongStream.rangeClosed(1L, n).sum();
  }
  public static long paraSumRangeClosed(long n) {                      // (2)
    return LongStream.rangeClosed(1L, n).parallel().sum();
  }
  public static long seqSumIterate(long n) {                           // (3)
    return LongStream.iterate(1L, i -> i + 1).limit(n).sum();
  }
  public static long paraSumIterate(long n) {                          // (5)
    return LongStream.iterate(1L, i -> i + 1).limit(n).parallel().sum();
  }
  public static long iterSumLoop(long n) {                             // (5)
    long result = 0;
    for (long i = 1L; i <= n; i++) {
      result += i;
    }
    return result;
  }
  /*
   * Applies the function parameter func, passing n as parameter.
   * Returns the average time (ms.) to execute the function 100 times.
   */
  public static <R> double measurePerf(LongFunction<R> func, long n) { // (6)
    int numOfExecutions = 100;
    double totTime = 0.0;
    R result = null;
    for (int i = 0; i < numOfExecutions; i++) {                        // (7)
      double start = System.nanoTime();                                // (8)
      result = func.apply(n);                                          // (9)
      double duration = (System.nanoTime() – start)/1_000_000;         // (10)
      totTime += duration;                                             // (11)
    }
    double avgTime = totTime/numOfExecutions;                          // (12)
    return avgTime;
  }
  /*
   * Executes the functions in the varargs parameter funcs
   * for different stream sizes.
   */
  public static <R> void xqtFunctions(LongFunction<R>… funcs) {      // (13)
    long[] sizes = {1_000L, 10_000L, 100_000L, 1_000_000L};            // (14)
    // For each stream size …
    for (int i = 0; i < sizes.length; ++i) {                           // (15)
      System.out.printf(“%7d”, sizes[i]);
      // … execute the functions passed in the varargs parameter funcs.
      for (int j = 0; j < funcs.length; ++j) {                         // (16)
        System.out.printf(“%10.5f”, measurePerf(funcs[j], sizes[i]));
      }
      System.out.println();
    }
  }
  public static void main(String[] args) {                             // (17)
    System.out.println(“Streams created with the rangeClosed() method:”);// (18)
    System.out.println(”  Size   Sequential Parallel”);
    xqtFunctions(StreamBenchmarks::seqSumRangeClosed,
                 StreamBenchmarks::paraSumRangeClosed);
    System.out.println(“Streams created with the iterate() method:”);  // (19)
    System.out.println(”  Size   Sequential Parallel”);
    xqtFunctions(StreamBenchmarks::seqSumIterate,
                 StreamBenchmarks::paraSumIterate);
    System.out.println(“Iterative solution with an explicit loop:”);   // (20)
    System.out.println(”  Size   Iterative”);
    xqtFunctions(StreamBenchmarks::iterSumLoop);
  }
}

Possible output from the program:

Click here to view code image

Streams created with the rangeClosed() method:
  Size   Sequential Parallel
   1000   0.05681   0.11031
  10000   0.06698   0.13979
 100000   0.71274   0.52627
1000000   7.02237   4.37249
Streams created with the iterate() method:
  Size   Sequential Parallel
   1000   0.08645   0.34696
  10000   0.35687   1.27861
 100000   3.24083  11.38709
1000000  29.92285 117.87909
Iterative solution with an explicit loop:
  Size   Iterative
   1000   0.00586
  10000   0.02330
 100000   0.22352
1000000   2.49677

Accessing Fields in Dates and Times – Date and Time

Accessing Fields in Dates and Times

A temporal object provides get methods that are tailored to access the values of specific temporal fields that constitute its state. The LocalTime and LocalDate classes provide get methods to access the values of time and date fields, respectively. Not surprisingly, the LocalDateTime class provides get methods for accessing the values of both time and date fields.

Click here to view code image

// LocalTime, LocalDateTime
int getHour()
int getMinute()
int getSecond()
int getNano()

Return the value of the appropriate time field from the current LocalTime or LocalDateTime object.

// LocalDate, LocalDateTime
int       getDayOfMonth()
DayOfWeek getDayOfWeek()
int       getDayOfYear()
Month     getMonth()
int       getMonthValue()
int       getYear()

Return the value of the appropriate date field from the current LocalDate or LocalDateTime object. The enum type DayOfWeek allows days of the week to be referred to by name; for example, DayOfWeek.MONDAY is day 1 of the week. The enum type Month allows months of the year to be referred to by name—for example, Month.JANUARY. The month value is from 1 (Month.JANUARY) to 12 (Month.DECEMBER).

Click here to view code image

// LocalTime, LocalDate, LocalDateTime
int get(TemporalField field)
long getLong(TemporalField field)
boolean isSupported(TemporalField field)

The first two methods return the value of the specified TemporalField (p. 1046) from this temporal object as an int value or as a long value, respectively. To specify fields whose value does not fit into an int, the getLong() method must be used.

The third method checks if the specified field is supported by this temporal object. It avoids an exception being thrown if it has been determined that the field is supported.

Using an invalid field in a get method will result in any one of these exceptions: DateTimeException (field value cannot be obtained), UnsupportedTemporalType-Exception (field is not supported), or ArithmeticException (numeric overflow occurred).

Here are some examples of using the get methods; more examples can be found in Example 17.2. Given that time and date refer to a LocalTime (08:15) and a LocalDate (1945-08-06), respectively, the code below shows how we access the values of the temporal fields using specifically named get methods and using specific temporal fields.

Click here to view code image

int minuteOfHour1 = time.getMinute();                     // 15
int minuteOfHour2 = time.get(ChronoField.MINUTE_OF_HOUR); // 15
int monthVal1 = date.getMonthValue();                     // 8
int monthVal2 = date.get(ChronoField.MONTH_OF_YEAR);      // 8

The temporal class LocalDateTime also provides two methods to obtain the date and the time as temporal objects, in contrast to accessing the values of individual date and time fields.

Click here to view code image

LocalDateTime doomsday = LocalDateTime.of(1945, 8, 6, 8, 15);
LocalDate date = doomsday.toLocalDate();                  // 1945-08-06
LocalTime time = doomsday.toLocalTime();                  // 08:15

// LocalDateTime
LocalDate toLocalDate()
LocalTime toLocalTime()

These methods can be used to get the LocalDate and the LocalTime components of this date-time object, respectively.

The following two methods return the number of days in the month and in the year represented by a LocalDate object.

Click here to view code image

LocalDate foolsday = LocalDate.of(2022, 4, 1);
int daysInMonth = foolsday.lengthOfMonth();    // 30
int daysInYear = foolsday.lengthOfYear();     // 365 (2022 is not a leap year.)

// LocalDate
int lengthOfMonth()
int lengthOfYear()

These two methods return the number of days in the month and in the year represented by this date, respectively.

Comparing Dates and Times – Date and Time

Comparing Dates and Times

It is also possible to check whether a temporal object represents a point in time before or after another temporal object of the same type. In addition, the LocalDate and LocalDateTime classes provide an isEqual() method that determines whether a temporal object is equal to another temporal object of the same type. In contrast, the equals() method allows equality comparison with an arbitrary object.

Click here to view code image

LocalDate d1 = LocalDate.of(1948, 2, 28);                  // 1948-02-28
LocalDate d2 = LocalDate.of(1949, 3, 1);                   // 1949-03-01
boolean result1 = d1.isBefore(d2);                         // true
boolean result2 = d2.isAfter(d1);                          // true
boolean result3 = d1.isAfter(d1);                          // false
boolean result4 = d1.isEqual(d2);                          // false
boolean result5 = d1.isEqual(d1);                          // true
boolean result6 = d1.isLeapYear();                         // true

The temporal classes implement the Comparable<E> interface, providing the compareTo() method so that temporal objects can be compared in a meaningful way. The temporal classes also override the equals() and the hashCode() methods of the Object class. These methods make it possible to both search for and sort temporal objects.

Click here to view code image

// LocalTime
boolean isBefore(LocalTime other)
boolean isAfter(LocalTime other)

Determine whether this LocalTime represents a point on the timeline before or after the other time, respectively.

Click here to view code image

// LocalDate
boolean isBefore(ChronoLocalDate other)
boolean isAfter(ChronoLocalDate other)
boolean isEqual(ChronoLocalDate other)
boolean isLeapYear()

The first two methods determine whether this LocalDate represents a point on the timeline before or after the other date, respectively. The LocalDate class implements the ChronoLocalDate interface.

The third method determines whether this date is equal to the specified date.

The last method checks for a leap year according to the ISO proleptic calendar system rules.

Click here to view code image

// LocalDateTime
boolean isBefore(ChronoLocalDateTime<?> other)
boolean isAfter(ChronoLocalDateTime<?> other)
boolean isEqual(ChronoLocalDateTime<?> other)

The first two methods determine whether this LocalDateTime represents a point on the timeline before or after the specified date-time, respectively. The Local-DateTime class implements the ChronoLocalDateTime<LocalDateTime> interface.

The third method determines whether this date-time object represents the same point on the timeline as the other date-time.

Click here to view code image

int compareTo(LocalTime other)                // LocalTime
int compareTo(ChronoLocalDate other)          // LocalDate
int compareTo(ChronoLocalDateTime<?> other)   // LocalDateTime

Compare this temporal object to another temporal object. The three temporal classes implement the Comparable<E> functional interface. The compareTo() method returns 0 if the two temporal objects are equal, a negative value if this temporal object is less than the other temporal object, and a positive value if this temporal object is greater than the other temporal object.

Summary of Static Factory Methods in the Collectors Class – Streams

Summary of Static Factory Methods in the Collectors Class

The static factory methods of the Collectors class that create collectors are summarized in Table 16.7. All methods are static generic methods, except for the overloaded joining() methods that are not generic. The keyword static is omitted, as are the type parameters of a generic method, since these type parameters are evident from the declaration of the formal parameters to the method. The type parameter declarations have also been simplified, where any bound <? super T> or <? extends T> has been replaced by <T>, without impacting the intent of a method. A reference is also provided for each method in the first column.

The last column in Table 16.7 indicates the function type of the corresponding parameter in the previous column. It is instructive to note how the functional interface parameters provide the parameterized behavior to build the collector returned by a method. For example, the method averagingDouble() returns a collector that computes the average of the stream elements. The parameter function mapper with the functional interface type ToDoubleFunction<T> converts an element of type T to a double when the collector computes the average for the stream elements.

Table 16.7 Static Methods in the Collectors Class

Method name (ref.)Return typeFunctional interface parametersFunction type of parameters
averagingDouble
(p. 1000)
Collector<T,?,Double>(ToDoubleFunction<T> mapper)T -> double
averagingInt
(p. 1000)
Collector<T,?,Double>(ToIntFunction<T> mapper)T -> int
averagingLong
(p. 1000)
Collector<T,?,Double>(ToLongFunction<T> mapper)T -> long
collectingAndThen
(p. 997)
Collector<T,A,RR>(Collector<T,A,R> downstream, Function<R,RR> finisher)(T,A) -> R, R -> RR
counting
(p. 998)
Collector<T,?,Long>() 
filtering
(p. 992)
Collector<T,?,R>(Predicate<T> predicate, Collector<T,A,R> downstream)T -> boolean,

(T,A) -> R
flatMapping
(p. 994)
Collector<T,?,R>(Function<T, Stream<U>> mapper, Collector<U,A,R> downstream)T->Stream<U>,

(U,A) -> R
groupingBy
(p. 985)
Collector<T,?, Map<K,List<T>>>(Function<T,K> classifier)T -> K
groupingBy
(p. 985)
Collector<T,?, Map<K,D>>(Function<T,K> classifier, Collector<T,A,D> downstream)T -> K,
(T,A) -> D
groupingBy
(p. 985)
Collector<T,?,Map<K,D>>(Function<T,K> classifier, Supplier<Map<K,D>> mapSupplier, Collector<T,A,D> downstream)T -> K,

()->Map<K,D>,

(T,A)->D
joining
(p. 984)
Collector
<CharSequence,?,String>
() 
joining
(p. 984)
Collector
<CharSequence,?,String>
(CharSequence delimiter) 
joining
(p. 984)
Collector
<CharSequence,?,String>
(CharSequence delimiter, CharSequence prefix, CharSequence suffix) 
mapping
(p. 993)
Collector<T,?,R>(Function<T,U> mapper, Collector<U,A,R> downstream)T -> U,
(U,A) -> R
maxBy
(p. 999)
Collector<T,?,Optional<T>>(Comparator<T> comparator)(T,T) -> T
minBy
(p. 999)
Collector<T,?,Optional<T>>(Comparator<T> comparator)(T,T) -> T
partitioningBy
(p. 989)
Collector<T,?,
Map<Boolean,List<T>>>
(Predicate<T> predicate)T -> boolean
partitioningBy
(p. 989)
Collector<T,?,
Map<Boolean,D>>
(Predicate<T> predicate, Collector<T,A,D> downstream)T -> boolean,
(T,A) -> D
reducing
(p. 1002)
Collector<T,?,Optional<T>>(BinaryOperator<T> op)(T,T) -> T
reducing
(p. 1002)
Collector<T,?,T>(T identity, BinaryOperator<T> op)T -> T,
(T,T) -> T
reducing
(p. 1002)
Collector<T,?,U>(U identity, Function<T,U> mapper, BinaryOperator<U> op)U -> U,
T -> U,

(U,U) -> U
summarizingDouble
(p. 1001)
Collector<T,?,
DoubleSummaryStatistics>
(ToDoubleFunction<T> mapper)T -> double
summarizingInt
(p. 1001)
Collector<T,?,
IntSummaryStatistics>
(ToIntFunction<T> mapper)T -> int
summarizingLong
(p. 1001)
Collector<T,?,
LongSummaryStatistics>
(ToLongFunction<T> mapper)T -> long
summingDouble
(p. 978)
Collector<T,?,Double>(ToDoubleFunction<T> mapper)T -> double
summingInt
(p. 978)
Collector<T,?,Integer>(ToIntFunction<T> mapper)T -> int
summingLong
(p. 978)
Collector<T,?,Long>(ToLongFunction<T> mapper)T -> long
toCollection
(p. 979)
Collector<T,?,C>(Supplier<C> collFactory)() -> C
toList
toUnmodifiableList
(p. 980)
Collector<T,?,List<T>>() 
toMap
(p. 981)
Collector<T,?,Map<K,U>>(Function<T,K> keyMapper, Function<T,U> valueMapper)T -> K,
T -> U
toMap
(p. 981)
Collector<T,?,Map<K,U>>(Function<T,K> keyMapper, Function<T,U> valueMapper,
BinaryOperator<U> mergeFunction)
T -> K,
T -> U,
(U,U) -> U
toMap
(p. 981)
Collector<T,?,Map<K,U>>(Function<T,K> keyMapper, Function<T,U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<Map<K,U>> mapSupplier)T -> K,
T -> U,
(U,U) -> U,
()-> Map<K,U>
toSet
toUnmodifiableSet
(p. 980)
Collector<T,?,Set<T>>() 

Table 16.8 shows a comparison of methods in the stream interfaces that perform reduction operations and static factory methods in the Collectors class that implement collectors with equivalent functionality.

Table 16.8 Method Comparison: The Stream Interfaces and the Collectors Class

Method names in the stream interfacesStatic factory method names in the Collectors class
collect (p. 964)collectingAndThen (p. 997)
count (p. 953)counting (p. 998)
filter (p. 912)filtering (p. 992)
flatMap (p. 924)flatMapping (p. 994)
map (p. 921)mapping (p. 993)
max (p. 954)maxBy (p. 999)
min (p. 954)minBy (p. 999)
reduce (p. 955)reducing (p. 1002)
toList (p. 972)toList (p. 980)
average (p. 972)averagingInt, averagingLong, averagingDouble (p. 1001)
sum (p. 972)summingInt, summingLong, summingDouble (p. 978)
summaryStatistics (p. 972)summarizingInt, summarizingLong, summarizingDouble (p. 1001)

Accessing Date Units in a Period – Date and Time

Accessing Date Units in a Period

The Period class provides the obvious getXXX() methods to read the values of date units of a Period object, where XXX can be Years, Months, or Days.

Click here to view code image

Period period5 = Period.of(2, 4, -10);
System.out.println(“Period: ” + period5);             // Period: P2Y4M-10D
System.out.println(“Years:  ” + period5.getYears());  // Years:  2
System.out.println(“Months: ” + period5.getMonths()); // Months: 4
System.out.println(“Days:   ” + period5.getDays());   // Days:   -10

Reading the value of date units of a Project object can also be achieved using the get(unit) method, where only the date units shown in the code below are allowed. A list of these valid temporal units can be obtained by calling the getUnits() method of the Period class.

The class also has methods to check if any date unit of a period has a negative value or if all date units of a period have the value zero.

Click here to view code image

System.out.println(“Years:  ” + period5.get(ChronoUnit.YEARS)); // Years:  2
System.out.println(“Months: ” + period5.get(ChronoUnit.MONTHS));// Months: 4
System.out.println(“Days:   ” + period5.get(ChronoUnit.DAYS));  // Days: -10
List<TemporalUnit> supportedUnits = period5.getUnits(); // [Years, Months, Days]
System.out.println(“Total months: ” + period5.toTotalMonths()); // 28 months
System.out.println(period5.isNegative());                       // true
System.out.println(period5.isZero());                           // false

The class Period provides the method toTotalMonths() to derive the total number of months in a period. However, this calculation is solely based on the number of years and months in the period; the number of days is not considered. A Period just represents an amount of time, so it has no notion of a date. Conversion between months and years is not a problem, as 1 year is 12 months. However, conversion between the number of days and the other date units is problematic. The number of days in a year and in a month are very much dependent on whether the year is a leap year and on a particular month in the year, respectively. A Period is oblivious to both the year and the month in the year, as it represents an amount of time and not a point on the timeline.

int getYears()
int getMonths()
int getDays()

Return the value of a specific date unit of this period, indicated by the name of the method.

long get(TemporalUnit unit)

Returns the value of the specified unit in this Period. The only supported date ChronoUnits are YEARS, MONTHS, and DAYS (p. 1044). All other units throw an exception.

List<TemporalUnit> getUnits()

Returns the list of date units supported by this period: YEARS, MONTHS, and DAYS (p. 1044). These date units can be used in the get(TemporalUnit) method.

long toTotalMonths()

Returns the total number of months in this period, based on the values of the years and months units. The value of the days unit is not considered.

boolean isNegative()

Determines whether the value of any date units of this period is negative.

boolean isZero()

Determines whether the values of all date units of this period are zero.

Working with Instants – Date and Time

17.4 Working with Instants

The temporal classes LocalTime, LocalDate, LocalDateTime, and ZonedDateTime are suitable for representing human time in terms of year, month, day, hour, minute, second, and time zone. The Instant class can be used for representing computer time, specially timestamps that identify to a higher precision when an event occurred on the timeline. Instants are suitable for persistence purposes—for example, in a database.

An Instant represents a point on the timeline, measured with nanosecond precision from a starting point or origin which is defined to be at 1970-01-01T00:00:00Z—that is, January 1, 1970, at midnight—and is called the epoch. Instants before the epoch have negative values, whereas instants after the epoch have positive values. The Z represents the time zone designator for the zero UTC offset, which is the time zone offset for all instants in the UTC standard (p. 1072). The text representation of the epoch shown above is in the ISO standard format used by the toString() method of the Instant class.

An Instant is modeled with two values:

  • A long value to represent the epoch-second
  • An int value to represent the nano-of-second

The nano-of-second must be a value in the range [0, 999999999]. This representation is reflected in the methods provided for dealing with instants. The Instant class shares many of the method name prefixes and the common method names in Table 17.2, p. 1026, and Table 17.3, p. 1026, with the other temporal classes, respectively. Although the Instant class has many methods analogous to the other temporal classes, as we shall see, there are also differences. Instant objects are, like objects of the other temporal classes, immutable and thread-safe.

Creating Instants

The Instant class provides the following predefined instants:

static Instant EPOCH
static Instant MAX
static Instant MIN

These static fields of the Instant class define constants for the epoch (1970-01-01T00:00:00Z), the maximum (1000000000-12-31T23:59:59.999999999Z), and the minimum instants (-1000000000-01-01T00:00Z), respectively.

Following are selected methods for creating and converting instances of the Instant class:

static Instant now()

Returns the current instant based on the system clock.

Click here to view code image

static Instant ofEpochMilli(long epochMilli)
static Instant ofEpochSecond(long epochSecond)
static Instant ofEpochSecond(long epochSecond, long nanoAdjustment)

These static factory methods return an Instant based on the millisecond, second, and nanosecond specified.

Nanoseconds are implicitly set to zero. The argument values can be negative. Note that the amount is specified as a long value.

String toString()

Returns a text representation of this Instant, such as “2021-01-11T14:18:30Z”. Formatting is based on the ISO instant format for date-time:

Click here to view code image

uuuu-MM-ddTHH:mm:ss.SSSSSSSSSZ

where Z designates the UTC standard (also known as Coordinated Universal Time).

Click here to view code image

static Instant parse(CharSequence text)

Returns an Instant parsed from a character sequence, such as “2021-04-28T14:18:30Z”, based on the ISO instant format. A DateTimeParse-Exception is thrown if the text cannot be parsed to an instant.

Click here to view code image

ZonedDateTime atZone(ZoneId zone)

Returns a ZonedDateTime by combining this instant with the specified time zone (p. 1072).

Analogous to the other temporal classes, the Instant class also provides the now() method to obtain the current instant from the system clock.

Click here to view code image

Instant currentInstant = Instant.now();      // 2021-03-09T10:48:01.914826Z

The Instant class provides the static factory method ofEpochUNIT() to construct instants from seconds and nanoseconds. There is no method to construct an instant from just nanoseconds.

Click here to view code image

Instant inst1 = Instant.ofEpochMilli(-24L*60*60*1000);// Date 1 day before epoch.
Instant inst2 = Instant.ofEpochSecond(24L*60*60);     // Date 1 day after epoch.
Instant inst3 = Instant.ofEpochSecond(24L*60*60 – 1,  // Date 1 day after epoch.
                                      1_000_000_000L);
out.println(“A day before: ” + inst1); // Date 1 day before: 1969-12-31T00:00:00Z
out.println(“A day after:  ” + inst2); // Date 1 day after : 1970-01-02T00:00:00Z
out.println(“A day after:  ” + inst3); // Date 1 day after : 1970-01-02T00:00:00Z

Note that the amount specified is a long value. The last statement above also illustrates that the nanosecond is adjusted so that it is always between 0 and 999,999,999. The adjustment results in the nanosecond being set to 0 and the second being incremented by 1.

The toString() method of the Instant class returns a text representation of an Instant based on the ISO standard. The code shows the text representation of the instant 500 nanoseconds after the epoch.

Click here to view code image

Instant inst4 = Instant.ofEpochSecond(0, 500);
out.println(“Default format:  ” + inst4);       // 1970-01-01T00:00:00.000000500Z

The Instant class also provides the parse() static method to create an instant from a string that contains a text representation of an instant, based on the ISO standard. Apart from treating the value of the nanosecond as optional, the method is strict in parsing the string. If the format of the string is not correct, a DateTimeParseException is thrown.

Click here to view code image

Instant instA = Instant.parse(“1970-01-01T00:00:00.000000500Z”);
Instant instB = Instant.parse(“1949-03-01T12:30:15Z”);
Instant instC = Instant.parse(“-1949-03-01T12:30:15Z”);
Instant instD = Instant.parse(“-1949-03-01T12:30:15”); // DateTimeParseException!

The code below illustrates creating an Instant by combining a LocalDateTime object with a time zone offset. Three different zone-time offsets are specified at (2), (3), and (4) to convert the date-time created at (1) to an Instant on the UTC timeline, which has offset zero. Note that an offset ahead of UTC is subtracted and an offset behind UTC is added to adjust the values of the date/time from the LocalDateTime object to the UTC timeline.

Click here to view code image

LocalDateTime ldt = LocalDate.of(2021, 12, 25).atStartOfDay();  //(1)
Instant i1 = ldt.toInstant(ZoneOffset.of(“+02:00”));     // (2) Ahead of UTC
Instant i2 = ldt.toInstant(ZoneOffset.UTC);              // (3) At UTC
Instant i3 = ldt.toInstant(ZoneOffset.of(“-02:00”));     // (4) Behind UTC
System.out.println(“ldt: ” + ldt);
System.out.println(“i1:  ” + i1);
System.out.println(“i2:  ” + i2);
System.out.println(“i3:  ” + i3);

Output from the code:

ldt: 2021-12-25T00:00
i1:  2021-12-24T22:00:00Z
i2:  2021-12-25T00:00:00Z
i3:  2021-12-25T02:00:00Z

Click here to view code image

// LocalDateTime
default Instant toInstant(ZoneOffset offset)

Converts a date-time to an instant by combining this LocalDateTime object with the specified time zone. The valid offset in Java is in the range from –18 to +18 hours. The absolute value of the offset is added to or subtracted from the date-time depending on whether it is specified as a negative or positive value, respectively, keeping in mind that an Instant represents a point in time on the UTC timeline.

This method is inherited by the LocalDateTime class from its superinterface java.time.chrono.ChronoLocalDateTime.