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).

Comparing Durations – Date and Time

Comparing Durations

The Duration class overrides the equals() method and the hashCode() method of the Object class, and implements the Comparable<Duration> interface. Durations can readily be used in collections. The code below illustrates comparing durations.

Click here to view code image

Duration eatBreakFast = Duration.ofMinutes(20L);                // PT20M
Duration eatLunch     = Duration.ofSeconds(30L*60);             // PT30M
Duration eatSupper    = Duration.of(45L, ChronoUnit.MINUTES);   // PT45M
out.println(eatBreakFast.equals(eatLunch));                     // false
out.println(Duration.ofSeconds(0).equals(Duration.ZERO));       // true
List<Duration> ld = Arrays.asList(eatSupper, eatBreakFast, eatLunch );
Collections.sort(ld);                            // Natural order.
out.println(ld);                                 // [PT20M, PT30M, PT45M]

Click here to view code image

boolean equals(Object otherDuration)

Determines whether the total length of this duration is equal to the total length of the other duration.

int hashCode()

Returns a hash code for this duration.

Click here to view code image

int compareTo(Duration otherDuration)

Compares the total length of this duration to the total length of the other duration.

Creating Modified Copies of Durations

The Duration class provides withUNIT() methods to set a new value for each time unit individually, while the value of the other time unit is retained. Note that each method call returns a new Duration object, and chaining method calls works as expected.

Click here to view code image

Duration oneDuration =  Duration.ZERO                     // PT0S
                                .withNanos(500_000_000)   // New copy: PT0.5S
                                .withSeconds(12L*60*60);  // New copy: PT12H0.5S

Click here to view code image

Duration withNanos(int nanoOfSecond)
Duration withSeconds(long seconds)

Return a copy of this duration where either the nanosecond or the seconds are set to the value of the argument, respectively. The value of the other time unit is retained.

Temporal Arithmetic with Durations

The Duration class provides plus and minus methods that return a copy of the original object that has been incremented or decremented by a specific amount specified in terms of a unit—for example, as a number of days, hours, minutes, or seconds.

Click here to view code image

Duration max20H = Duration.ZERO                           // PT0S
                          .plusHours(10)                  // PT10H
                          .plusMinutes(10*60 + 30)        // PT20H30M
                          .plusSeconds(6*60*60 + 15)      // PT26H30M15S
                          .minusMinutes(2*60 + 30)        // PT24H15S
                          .minusSeconds(15);              // PT24H

The plus() and the minus() methods also allow the amount to be qualified by a unit that has a standard or an estimated duration, as illustrated by the statement below, which is equivalent to the one above.

Click here to view code image

Duration max20H2 =
    Duration.ZERO                                         // PT0S
            .plus(10L,           ChronoUnit.HOURS)        // PT10H
            .plus(10*60 + 30,    ChronoUnit.MINUTES)      // PT20H30M
            .plus(6*60*60L + 15, ChronoUnit.SECONDS)      // PT26H3015S
            .minus(2*60 + 30,    ChronoUnit.MINUTES)      // PT24H15S
            .minus(15,           ChronoUnit.SECONDS);     // PT24H

The code below shows the plus() and the minus() methods of the Duration class that take a Duration as the amount to add or subtract.

Click here to view code image

Duration eatBreakFast = Duration.ofMinutes(20L);                // PT20M
Duration eatLunch     = Duration.ofSeconds(30L*60);             // PT30M
Duration eatSupper    = Duration.of(45L, ChronoUnit.MINUTES);   // PT45M

Duration totalTimeForMeals = eatBreakFast                       // PT20M
    .plus(eatLunch)                                             // PT50M
    .plus(eatSupper);                                           // PT1H35M

The statement below shows other arithmetic operations on durations and how they are carried out, together with what would be printed if the intermediate results were also written out.

Click here to view code image

Duration result = Duration.ofSeconds(-100, -500_000_000) // -100.5 => PT-1M-40.5S
                          .abs()           // abs(-100.5) = 100.5  => PT1M40.5S
                          .multipliedBy(4) // 100.5*4 = 402 => PT6M42S
                          .dividedBy(2);   // 402 / 2 = 201 => PT3M21S

Click here to view code image

Duration plusDays/minusDays(long days)
Duration plusHours/minusHours(long hours)
Duration plusMinutes/minusMinutes(long minutes)
Duration plusSeconds/minusSeconds(long seconds)
Duration plusMillis/minusMillis(long millis)
Duration plusNanos/minusNanos(long nanos)

Return a copy of this duration, with the specified value of the unit designated by the method name added or subtracted, but converted first to seconds, if necessary. Note that the argument type is long.

Click here to view code image

Duration plus(long amountToAdd, TemporalUnit unit)
Duration minus(long amountToSub, TemporalUnit unit)

Return a copy of this duration with the specified amount added or subtracted, respectively, according to the TemporalUnit specified (p. 1044).

Valid ChronoUnit constants to qualify the amount specified in the method call are the following: NANOS, MICROS, MILLIS, SECONDS, MINUTES, HOURS, HALF_DAYS, and DAYS (p. 1044). These units have a standard or an estimated duration. Other units result in an UnsupportedTemporalTypeException.

Click here to view code image

Duration plus(Duration duration)
Duration minus(Duration duration)

Return a copy of this duration, with the specified duration added or subtracted.

Duration abs()

Returns a copy of this duration with a positive length.

Duration negated()

Returns a copy of this duration where the length has been negated.

Click here to view code image

Duration dividedBy(long divisor)
Duration multipliedBy(long multiplicand)

The first method returns a new instance with the result of dividing the length of this duration by the specified divisor. Division by zero would bring down untold calamities.

The second method returns a new instance with the result of multiplying the length of this duration by the specified multiplicand.

We can perform arithmetic operations on durations and temporal objects. The following code uses the plus() and minus() methods of the LocalTime and LocalDateTime classes that take a TemporalAmount as an argument (p. 1040). We can add and subtract durations from LocalTime and LocalDateTime objects, but not from LocalDate objects, as a LocalDate object only supports date units.

Click here to view code image

LocalTime timeA = LocalTime.of(14,45,30);                 // 14:45:30
LocalDate dateA = LocalDate.of(2021, 4, 28);              // 2021-04-28
LocalDateTime dateTimeA = LocalDateTime.of(dateA, timeA); // 2021-04-28T14:45:30
Duration amount = Duration.ofMinutes(20);                 // PT20M
timeA = timeA.plus(amount);                               // 15:05:30
dateTimeA = dateTimeA.minus(amount);                      // 2021-04-28T14:25:30
dateA = dateA.minus(amount);                 // UnsupportedTemporalTypeException

Example 17.7 illustrates implementing duration-based loops. The program prints the showtimes, given when the first show starts, the duration of the show, and when the theatre closes. The for(;;) loop at (1) uses the LocalTime.isBefore() method and the LocalTime.plus(duration) method to calculate the showtimes.

Example 17.7 Duration-Based Loop

Click here to view code image

import java.time.LocalTime;
import java.time.Duration;
public class DurationBasedLoop {
  public static void main(String[] args) {
    Duration duration = Duration.ofHours(2).plusMinutes(15);     // PT2H15M
    LocalTime firstShowTime = LocalTime.of(10, 10);              // 10:10
    LocalTime endTimeExclusive = LocalTime.of(23, 0);            // 23:00
    for (LocalTime time = firstShowTime;                         // (1)
         time.plus(duration).isBefore(endTimeExclusive);
         time = time.plus(duration)) {
      System.out.println(“Showtime (” + duration + “): ” + time);
    }
    System.out.println(“Closing time: ” + endTimeExclusive);
  }
}

Output from the program:

Showtime (PT2H15M): 10:10
Showtime (PT2H15M): 12:25
Showtime (PT2H15M): 14:40
Showtime (PT2H15M): 16:55
Showtime (PT2H15M): 19:10
Closing time: 23:00

Working with Dates and Times – Date and Time

17.2 Working with Dates and Times

The classes LocalTime, LocalDate, and LocalDateTime in the java.time package represent time-based, date-based, and combined date-based and time-based temporal objects, respectively, that are all time zone agnostic. These classes represent human time that is calendar-based, meaning it is defined in terms of concepts like year, month, day, hour, minute, and second, that humans use. The Instant class can be used to represent machine time, which is defined as a point measured with nanosecond precision on a continuous timeline starting from a specific origin (p. 1049).

Time zones and daylight savings are discussed in §17.7, p. 1072.

Creating Dates and Times

The temporal classes in the java.time package do not provide any public constructors to create temporal objects. Instead, they provide overloaded static factory methods named of which create temporal objects from constituent temporal fields. We use the term temporal fields to mean both time fields (hours, minutes, seconds, nanoseconds) and date fields (year, month, day). The of() methods check that the values of the arguments are in range. Any invalid argument results in a java.time.DateTimeException.

Click here to view code image

// LocalTime
static LocalTime of(int hour, int minute)
static LocalTime of(int hour, int minute, int second)
static LocalTime of(int hour, int minute, int second, int nanoOfSecond)
static LocalTime ofSecondOfDay(long secondOfDay)

These static factory methods in the LocalTime class return an instance of Local-Time based on the specified values for the specified time fields. The second and nanosecond fields are set to zero, if not specified.

The last method accepts a value for the secondOfDay parameter in the range [0, 24 * 60 * 60 – 1] to create a LocalTime.

Click here to view code image

// LocalDate
static LocalDate of(int year, int month, int dayOfMonth)
static LocalDate of(int year, Month month, int dayOfMonth)
static LocalDate ofYearDay(int year, int dayOfYear)

These static factory methods in the LocalDate class return an instance of LocalDate based on the specified values for the date fields. The java.time.Month enum type allows months to be referred to by name—for example, Month.MARCH. Note that month numbering starts with 1 (Month.JANUARY).

The last method creates a date from the specified year and the day of the year.

Click here to view code image

// LocalDateTime
static LocalDateTime of(int year, int month, int dayOfMonth,
                        int hour, int minute)
static LocalDateTime of(int year, int month, int dayOfMonth,
                        int hour, int minute, int second)
static LocalDateTime of(int year, int month, int dayOfMonth, int hour,
                        int minute, int second, int nanoOfSecond)
static LocalDateTime of(int year, Month month, int dayOfMonth,
                        int hour, int minute, int second)
static LocalDateTime of(int year, Month month, int dayOfMonth,
                        int hour, int minute)
static LocalDateTime of(int year, Month month, int dayOfMonth,
                        int hour, int minute, int second, int nanoOfSecond)

These static factory methods in the LocalDateTime class return an instance of LocalDateTime based on the specified values for the time and date fields. The second and nanosecond fields are set to zero, if not specified. The java.time.Month enum type allows months to be referred to by name—for example, Month.MARCH (i.e., month 3 in the year).

Click here to view code image

static LocalDateTime of(LocalDate date, LocalTime time)

Combines a LocalDate and a LocalTime into a LocalDateTime.

All code snippets in this subsection can be found in Example 17.1, p. 1031, ready for running and experimenting. An appropriate import statement with the java.time package should be included in the source file to access any of the temporal classes by their simple name.

Date and Time API Overview – Date and Time

17.1 Date and Time API Overview

The java.time package provides the main support for dealing with dates and times. It contains the main classes that represent date and time values, including those that represent an amount of time.

  • LocalDate: This class represents a date in terms of date fields (year, month, day). A date has no time fields or a time zone. (This class is not to be confused with the java.util.Date legacy class.)
  • LocalTime: This class represents time in a 24-hour day in terms of time fields (hour, minute, second, nanosecond). A time has no date fields or a time zone.
  • LocalDateTime: This class represents the concept of date and time combined, in terms of both date and time fields. A date-time has no time zone.
  • ZonedDateTime: This class represents the concept of a date-time with a time zone— that is, a zoned date-time.
  • Instant: This class represents a measurement of time as a point on a timeline starting from a specific origin (called the epoch). An instant is represented with nanosecond precision and can be a negative value.
  • Period: This class represents an amount or quantity of time in terms of number of days, months, and years, which can be negative. A period is a date-based amount of time. It has no notion of a clock time, a date, or a time zone.
  • Duration: This class represents an amount or quantity of time in terms of number of seconds and nanoseconds, which can be negative. A duration is a time-based amount of time. As with instants, durations have no notion of a clock time, a date, or a time zone.

We will use the term temporal objects to mean objects of classes that represent temporal concepts.

The temporal classes implement immutable and thread-safe temporal objects. The state of an immutable object cannot be changed. Any method that is supposed to modify such an object returns a modified copy of the temporal object. It is a common mistake to ignore the object returned, thinking that the current object has been modified. Thread-safety guarantees that the state of such an object is not compromised by concurrent access.

Table 17.1 summarizes the fields in selected classes from the Date and Time API. The table shows the relative size of the objects of these classes in terms of their fields; for example, a LocalTime has only time fields, whereas a ZonedDateTime has time-, date-, and zone-based fields. The three asterisks *** indicate that this information can be derived by methods provided by the class, even though these fields do not exist in an object of the Duration class.

Table 17.1 Fields in Selected Classes in the Date and Time API

ClassesYearMonthDayHoursMinutesSeconds/NanosZone offsetZone ID
LocalTime
(p. 1027)
   +++  
LocalDate
(p. 1027)
+++     
LocalDateTime
(p. 1027)
++++++  
ZonedDate-Time
(p. 1072)
++++++++
Instant
(p. 1049)
     +  
Period
(p. 1057)
+++     
Duration
(p. 1064)
  *********+  

The information in Table 17.1 is crucial to understanding how the objects of these classes can be used. A common mistake is to access, format, or parse a temporal object that does not have the required temporal fields. For example, a LocalTime object has only time fields, so trying to format it with a formatter for date fields will result in a java.time.DateTimeException. Many methods will also throw an exception if an invalid or an out-of-range argument is passed in the method call. It is important to keep in mind which temporal fields constitute the state of a temporal object.

Table 17.2 provides an overview of the method naming conventions used in the temporal classes LocalTime, LocalDate, LocalDateTime, ZonedDateTime, and Instant. This method naming convention makes it easy to use the API, as it ensures method naming is standardized across all temporal classes. Depending on the method, the suffix XXX in a method name can be a specific field (e.g., designate the Year field in the getYear method name), a specific unit (e.g., designate the unit Days for number of days in the plusDays method name), or a class name (e.g., designate the class type in the toLocalDate method name).

Table 17.2 Selected Method Name Prefixes in the Temporal Classes

Prefix (parameters not shown)Usage
atXXX()Create a new temporal object by combining this temporal object with another temporal object. Not provided by the ZonedDateTime class.
of() ofXXX()Static factory methods for constructing temporal objects from constituent temporal fields.
get() getXXX()Access specific fields in this temporal object.
isXXX()Check specific properties of this temporal object.
minus() minusXXX()Return a copy of this temporal object after subtracting an amount of time.
plus() plusXXX()Return a copy of this temporal object after adding an amount of time.
toXXX()Convert this temporal object to another type.
with() withXXX()Create a copy of this temporal object with one field modified.

Apart from the methods shown in Table 17.2, the selected methods shown in Table 17.3 are common to the temporal classes LocalTime, LocalDate, LocalDateTime, ZonedDateTime, and Instant.

Table 17.3 Selected Common Methods in the Temporal Classes

Method (parameters not shown)Usage
now()Static method that obtains the current time from the system or specified clock in the default or specified time zone.
from()Static method to obtain an instance of this temporal class from another temporal.
until()Calculate the amount of time from this temporal object to another temporal object.
toString()Create a text representation of this temporal object.
equals()Compare two temporal objects for equality.
hashCode()Returns a hash code for this temporal object.
compareTo()Compare two temporal objects. (The class ZonedDateTime does not implement the Comparable<E> interface.)
parse()Static method to obtain a temporal instance from a specified text string (§18.6, p. 1127).
format()Create a text representation of this temporal object using a specified formatter (§18.6, p. 1127). (Instant class does not provide this method.)

Subsequent sections in this chapter provide ample examples of how to create, combine, convert, access, and compare temporal objects, including the use of temporal arithmetic and dealing with time zones and daylight savings. For formatting and parsing temporal objects, see §18.6, p. 1127.

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.

Summing – Streams

Summing

The summing collectors perform a functional reduction to produce the sum of the numeric results from applying a numeric-valued function to the input elements.

Click here to view code image

static <T> Collector<T,?,
NumType> summingNum
Type(
       To
NumType
Function<? super T> mapper)

Returns a collector that produces the sum of a numtype-valued function applied to the input elements. If there are no input elements, the result is zero. The result is of NumType.

NumType is Int (but it is Integer when used as a type name), Long, or Double, and the corresponding numtype is int, long, or double.

The collector returned by the Collectors.summingInt() method is used at (1) as a standalone collector to find the total number of tracks on the CDs. The mapper function CD::noOfTracks passed as an argument extracts the number of tracks from each CD on which the functional reduction is performed.

Click here to view code image

Integer sumTracks = CD.cdList.stream()
    .collect(Collectors.summingInt(CD::noOfTracks));   // (1) Standalone collector
System.out.println(sumTracks);                         // 42

In the pipeline below, the CDs are grouped by musical genre, and the number of tracks on CDs in each group summed by the downstream collector is returned by the Collectors.summingInt() method at (2).

Click here to view code image

Map<Genre, Integer> grpByGenre = CD.cdList.stream()
    .collect(Collectors.groupingBy(
         CD::genre,
         Collectors.summingInt(CD::noOfTracks)));    // (2) Downstream collector
System.out.println(grpByGenre);                      // {POP=18, JAZZ=24}
System.out.println(grpByGenre.get(Genre.JAZZ));      // 24

The collector Collectors.summingInt() performs effectively the same functional reduction at (3) as the IntStream.sum() terminal operation (p. 973).

Click here to view code image

int sumTracks2 = CD.cdList.stream()                  // (3) Stream<CD>
    .mapToInt(CD::noOfTracks)                        // IntStream
    .sum();
System.out.println(sumTracks2);                      // 42

Averaging

The averaging collectors perform a functional reduction to produce the average of the numeric results from applying a numeric-valued function to the input elements.

Click here to view code image

static <T> Collector<T,?,Double> averaging
NumType
(
       To
NumType
Function<? super T> mapper)

Returns a collector that produces the arithmetic mean of a numtype-valued function applied to the input elements. If there are no input elements, the result is zero. The result is of type Double.

NumType is Int, Long, or Double, and the corresponding numtype is int, long, or double.

The collector returned by the Collectors.averagingInt() method is used at (1) as a standalone collector to find the average number of tracks on the CDs. The mapper function CD::noOfTracks passed as an argument extracts the number of tracks from each CD on which the functional reduction is performed.

Click here to view code image

Double avgNoOfTracks1 = CD.cdList.stream()
    .collect(Collectors
        .averagingInt(CD::noOfTracks));             // (1) Standalone collector
System.out.println(avgNoOfTracks1);                 // 8.4

In the pipeline below, the CDs are grouped by musical genre, and the downstream collector Collectors.averagingInt() at (2) calculates the average number of tracks on the CDs in each group.

Click here to view code image

Map<Genre, Double> grpByGenre = CD.cdList.stream()
    .collect(Collectors.groupingBy(
       CD::genre,
       Collectors.averagingInt(CD::noOfTracks)      // (2) Downstream collector
       ));
System.out.println(grpByGenre);                     // {POP=9.0, JAZZ=8.0}
System.out.println(grpByGenre.get(Genre.JAZZ));     // 8.0

The collector created by the Collectors.averagingInt() method performs effectively the same functional reduction as the IntStream.average() terminal operation (p. 974) at (3).

Click here to view code image OptionalDouble avgNoOfTracks2 = CD.cdList.stream()  // Stream<CD>
    .mapToInt(CD::noOfTracks)                       // IntStream
    .average();                                     // (3)
System.out.println(avgNoOfTracks2.orElse(0.0));     // 8.4

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.

Creating Modified Copies of Dates and Times – Date and Time

Creating Modified Copies of Dates and Times

An immutable object does not provide any set methods that can change its state. Instead, it usually provides what are known as with methods (or withers) that return a copy of the original object where exactly one field has been set to a new value. The LocalTime and LocalDate classes provide with methods to set the value of a time or date field, respectively. Not surprisingly, the LocalDateTime class provides with methods to set the values of both time and date fields individually. A with method changes a specific property in an absolute way, which is reflected in the state of the new temporal object; the original object, however, is not affected. Such with methods are also called absolute adjusters, in contrast to the relative adjusters that we will meet later (p. 1040).

Click here to view code image

// LocalTime, LocalDateTime
LocalTime/LocalDateTime withHour(int hour)
LocalTime/LocalDateTime withMinute(int minute)
LocalTime/LocalDateTime withSecond(int second)
LocalTime/LocalDateTime withNano(int nanoOfSecond)

Return a copy of this LocalTime or LocalDateTime with the value of the appropriate time field changed to the specified value. A DateTimeException is thrown if the argument value is out of range.

Click here to view code image

// LocalDate, LocalDateTime
LocalDate/LocalDateTime withYear(int year)
LocalDate/LocalDateTime withMonth(int month)
LocalDate/LocalDateTime withDayOfMonth(int dayOfMonth)
LocalDate/LocalDateTime withDayOfYear(int dayOfYear)

Return a copy of this LocalDate or LocalDateTime with the value of the appropriate date field changed to the specified value. A DateTimeException is thrown if the specified value is out of range or is invalid in combination with the values of the other time or date fields in the temporal object.

The first and second methods will adjust the day of the month to the last valid day of the month, if the day of the month becomes invalid when the year or the month is changed (e.g., the month value 2 will change the date 2020-03-31 to 2020-02-29).

In contrast, the third method will throw a DateTimeException if the specified day of the month is invalid for the month-year combination (e.g., the day of month 29 is invalid for February 2021), as will the last method if the day of the year is invalid for the year (e.g., the day of year 366 is invalid for the year 2021).

Click here to view code image

// LocalTime, LocalDate, LocalDateTime
LocalTime/LocalDate/LocalDateTime with(TemporalField field, long newValue)

Returns a copy of this temporal object with the specified TemporalField (p. 1046) set to a new value. The ChronoField enum type implements the TemporalField interface, and its enum constants define specific temporal fields (p. 1046).

Using an invalid field in the with() method will result in any one of these exceptions: DateTimeException (field value cannot be set), Unsupported-TemporalTypeException (field is not supported), or ArithmeticException (numeric overflow occurred).

The code lines below are from Example 17.2. In the second assignment statement, the method calls are chained. Three instances of the LocalDate class are created consecutively, as each with method is called to set the value of a specific date field. The last assignment shows the use of temporal fields in the with() method for the same purpose.

Click here to view code image

LocalDate date2 = LocalDate.of(2021, 3, 1);                   // 2021-03-01
date2 = date2.withYear(2024).withMonth(2).withDayOfMonth(28); // 2024-02-28
LocalDate date3 = LocalDate.of(2021, 3, 1);                   // 2021-03-01
date3 = date3
    .with(ChronoField.YEAR, 2024L)
    .with(ChronoField.MONTH_OF_YEAR, 2L)
    .with(ChronoField.DAY_OF_MONTH, 28L);                     // 2024-02-28

The following code contains a logical error, such that the last two LocalDate instances returned by the with methods are ignored, and the reference date2 never gets updated.

Click here to view code image

date2 = date2.withYear(2022);                      // 2022-03-01
date2.withMonth(2).withDayOfMonth(28);             // date2 is still 2022-03-01.

In the next code examples, each call to a with method throws a DateTimeException. The minute and hour values are out of range for a LocalTime object. Certainly the month value 13 is out of range for a LocalDate object. The day of month value 31 is not valid for April, which has 30 days. The day of year value 366 is out of range as well, since the year 2021 is not a leap year.

Click here to view code image

LocalTime time = LocalTime.of(14, 45);       // 14:45
time = time.withMinute(100);       // Out of range. DateTimeException.
time = time.withHour(25);          // Out of range. DateTimeException.
LocalDate date = LocalDate.of(2021, 4, 30);  // 2021-04-30
date = date.withMonth(13);         // Out of range. DateTimeException.
date = date.withDayOfMonth(31);    // Out of range for month. DateTimeException.
date = date.withDayOfYear(366);    // Out of range for year. DateTimeException.

The code snippets below illustrate how the withYear() and withMonth() methods adjust the day of the month, if necessary, when the year or the month is changed, respectively.

Click here to view code image

LocalDate date3 = LocalDate.of(2020, 2, 29);  // Original: 2020-02-29
date3 = date3.withYear(2021);                 // Expected: 2021-02-29
System.out.println(“Date3: ” + date3);        // Adjusted: 2021-02-28
LocalDate date4 = LocalDate.of(2021, 3, 31);  // Original: 2021-03-31
date4 = date4.withMonth(4);                   // Expected: 2021-04-31
System.out.println(“Date4: ” + date4);        // Adjusted: 2021-04-30

The year in the date 2020-02-29 is changed to 2021, resulting in the following date: 2021-02-29. Since the year 2021 is not a leap year, the month of February cannot have 29 days. The withYear() method adjusts the day of the month to the last valid day of the month (i.e., 28). Similarly, the month in the date 2021-03-31 is changed to 4 (i.e., April), resulting in the following date: 2021-04-31. Since the month April has 30 days, the withMonth() method adjusts the day of the month to the last valid day of the month (i.e., 30).

Example 17.2 Using Local Dates and Local Times

Click here to view code image

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;
import java.time.temporal.ChronoField;
public class UsingTemporals {
  public static void main(String[] args) {
    // Date-Time: 1945-08-06T08:15
    LocalDateTime doomsday = LocalDateTime.of(1945, 8, 6, 8, 15);
    LocalDate date = doomsday.toLocalDate();                   // 1945-08-06
    LocalTime time = doomsday.toLocalTime();                   // 08:15
    System.out.println(“Date-Time: ” + doomsday);
    System.out.println();
    // Time: 08:15
    int hourOfDay      = time.getHour();                       // 8
    int minuteOfHour1  = time.getMinute();                     // 15
    int minuteOfHour2  = time.get(ChronoField.MINUTE_OF_HOUR); // 15
    int secondOfMinute = time.getSecond();                     // 0
    System.out.println(“Time of day:      ” + time);
    System.out.println(“Hour-of-day:      ” + hourOfDay);
    System.out.println(“Minute-of-hour 1: ” + minuteOfHour1);
    System.out.println(“Minute-of-hour 2: ” + minuteOfHour2);
    System.out.println(“Second-of-minute: ” + secondOfMinute);
    System.out.println();
    // Date: 1945-08-06
    int year       = date.getYear();                           // 1945
    int monthVal1  = date.getMonthValue();                     // 8
    int monthVal2  = date.get(ChronoField.MONTH_OF_YEAR);      // 8
    Month month    = date.getMonth();                          // AUGUST
    DayOfWeek dow  = date.getDayOfWeek();                      // MONDAY
    int day        = date.getDayOfMonth();                     // 6
    System.out.println(“Date:  ” + date);
    System.out.println(“Year:  ” + year);
    System.out.println(“Month value 1: ” + monthVal1);
    System.out.println(“Month value 2: ” + monthVal2);
    System.out.println(“Month-of-year: ” + month);
    System.out.println(“Day-of-week:   ” + dow);
    System.out.println(“Day-of-month:  ” + day);
    System.out.println();
    // Ordering
    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
    System.out.println(“Ordering:”);
    System.out.println(d1 + ” is before ”   + d2 + “: ” + result1);
    System.out.println(d2 + ” is after ”    + d1 + “: ” + result2);
    System.out.println(d1 + ” is after ”    + d1 + “: ” + result3);
    System.out.println(d1 + ” is equal to ” + d2 + “: ” + result4);
    System.out.println(d1 + ” is equal to ” + d1 + “: ” + result5);
    System.out.println(d1.getYear() + ” is a leap year: ” + result6);
    System.out.println();
    System.out.println(“Using absolute adjusters:”);
    LocalDate date2 = LocalDate.of(2021, 3, 1);
    System.out.println(“Date before adjusting: ” + date2);     // 2021-03-01
    date2 = date2.withYear(2024).withMonth(2).withDayOfMonth(28);
    System.out.println(“Date after adjusting:  ” + date2);     // 2024-02-28
    System.out.println();
    System.out.println(“Using temporal fields:”);
    LocalDate date3 = LocalDate.of(2021, 3, 1);
    System.out.println(“Date before adjusting: ” + date3);     // 2021-03-01
    date3 = date3
        .with(ChronoField.YEAR, 2024L)
        .with(ChronoField.MONTH_OF_YEAR, 2L)
        .with(ChronoField.DAY_OF_MONTH, 28L);
    System.out.println(“Date after adjusting:  ” + date3);     // 2024-02-28
  }
}

Output from the program:

Click here to view code image

Date-Time: 1945-08-06T08:15
Time of day:      08:15
Hour-of-day:      8
Minute-of-hour 1: 15
Minute-of-hour 2: 15
Second-of-minute: 0
Date:  1945-08-06
Year:  1945
Month value 1: 8
Month value 2: 8
Month-of-year: AUGUST
Day-of-week:   MONDAY
Day-of-month:  6
Ordering:
-1004-03-01 is before 1004-03-01: true
1004-03-01 is after -1004-03-01: true
-1004-03-01 is after -1004-03-01: false
-1004-03-01 is equal to 1004-03-01: false
-1004-03-01 is equal to -1004-03-01: true
1004 is a leap year: true
Using absolute adjusters:
Date before adjusting: 2021-03-01
Date after adjusting:  2024-02-28
Using temporal fields:
Date before adjusting: 2021-03-01
Date after adjusting:  2024-02-28

Summarizing – Streams

Summarizing

The summarizing collector performs a functional reduction to produce summary statistics (count, sum, min, max, average) on the numeric results of applying a numeric-valued function to the input elements.

Click here to view code image

static <T> Collector<T,?,
NumType
SummaryStatistics> summarizing
NumType
(
       To
NumType
Function<? super T> mapper)

Returns a collector that applies a numtype-valued mapper function to the input elements, and returns the summary statistics for the resulting values.

NumType is Int (but it is Integer when used as a type name), Long, or Double, and the corresponding numtype is int, long, or double.

The collector Collectors.summarizingInt() is used at (1) as a standalone collector to summarize the statistics for the number of tracks on the CDs. The mapper function CD::noOfTracks passed as an argument extracts the number of tracks from each CD on which the functional reduction is performed.

Click here to view code image

IntSummaryStatistics stats1 = CD.cdList.stream()
    .collect(
      Collectors.summarizingInt(CD::noOfTracks)      // (1) Standalone collector
     );
System.out.println(stats1);
// IntSummaryStatistics{count=5, sum=42, min=6, average=8.400000, max=10}

The IntSummaryStatistics class provides get methods to access the individual results (p. 974).

In the pipeline below, the CDs are grouped by musical genre, and the downstream collector created by the Collectors.summarizingInt() method at (2) summarizes the statistics for the number of tracks on the CDs in each group.

Click here to view code image

Map<Genre, IntSummaryStatistics> grpByGenre = CD.cdList.stream()
  .collect(Collectors.groupingBy(
     CD::genre,
     Collectors.summarizingInt(CD::noOfTracks)));    // (2) Downstream collector
System.out.println(grpByGenre);
//{POP=IntSummaryStatistics{count=2, sum=18, min=8, average=9.000000, max=10},
// JAZZ=IntSummaryStatistics{count=3, sum=24, min=6, average=8.000000, max=10}}

System.out.println(grpByGenre.get(Genre.JAZZ));   // Summary stats for Jazz CDs.
// IntSummaryStatistics{count=3, sum=24, min=6, average=8.000000, max=10}

The collector returned by the Collectors.summarizingInt() method performs effectively the same functional reduction as the IntStream.summaryStatistics() terminal operation (p. 974) at (3).

Click here to view code image

IntSummaryStatistics stats2 = CD.cdList.stream()
    .mapToInt(CD::noOfTracks)
    .summaryStatistics();                         // (3)
System.out.println(stats2);
// IntSummaryStatistics{count=5, sum=42, min=6, average=8.400000, max=10}

Comparing Instants – Date and Time

Comparing Instants

The methods isBefore() and isAfter() can be used to determine if one instant is before or after the other on the timeline, respectively.

Click here to view code image

// instA is  1970-01-01T00:00:00.000000500Z
// instB is  1949-03-01T12:30:15Z
// instC is -1949-03-01T12:30:15Z
out.println(instA.isBefore(instB));              // false
out.println(instA.isAfter(instC));               // true

The Instant class also overrides the equals() method and the hashCode() method of the Object class, and implements the Comparable<Instant> interface. Instants can readily be used in collections. The code below illustrates comparing instants.

Click here to view code image

out.println(instA.equals(instB));                 // false
out.println(instA.equals(instC));                 // false
List<Instant> list = Arrays.asList(instA, instB, instC);
Collections.sort(list);             // Natural order: position on the timeline.
// [-1949-03-01T12:30:15Z, 1949-03-01T12:30:15Z, 1970-01-01T00:00:00.000000500Z]

Click here to view code image

boolean isBefore(Instant other)
boolean isAfter(Instant other)

Determine whether this Instant is before or after the other instant on the timeline, respectively.

boolean equals(Object other)

Determines whether this Instant is equal to the other instant, based on the timeline position of the instants.

int hashCode()

Returns a hash code for this Instant.

int compareTo(Instant other)

Compares this Instant with the other instant, based on the timeline position of the instants.

Creating Modified Copies of Instants

The Instant class provides the with(field, newValue) method that returns a copy of this instant with either the epoch-second or the nano-of-second set to a new value, while the other one is unchanged.

Click here to view code image

Instant with(TemporalField field, long newValue)

Returns a copy of this instant where either the epoch-second or the nano-of-second is set to the specified value. The value of the other is retained.

This method only supports the following ChronoField constants: NANO_OF_SECOND, MICRO_OF_SECOND, MILLI_OF_SECOND, and INSTANT_SECONDS (p. 1046). For the first three fields, the nano-of-second is replaced by appropriately converting the specified value, and the epoch-second will be unchanged in the copy returned by the method. For the INSTANT_SECONDS field, the epoch-second will be replaced and the nanosecond will be unchanged in the copy returned by the method. Valid values that can be specified with these constants are [0–999999999], [0–999999], [0–999], and a long, respectively.

This method throws a DateTimeException if the field cannot be set, an Unsupported-TemporalTypeException if the field is not supported, and an ArithmeticException if number overflow occurs.

In the code below, the three instants i1, i2, and i3 will have the nano-of-second set to 5,000,000,000 nanoseconds using the with() method, but the epoch-second will not be changed.

Click here to view code image

Instant i0, i1, i2, i3;
i0 = Instant.now();
out.println(i0);                             // 2021-02-28T08:43:35.864Z
i1 = i0.with(ChronoField.NANO_OF_SECOND,  500_000_000);// 500000000 ns.
i2 = i0.with(ChronoField.MICRO_OF_SECOND, 500_000);    // 500000×1000 ns.
i3 = i0.with(ChronoField.MILLI_OF_SECOND, 500);        // 500×1000000 ns.
out.println(i1);                             // 2021-02-28T08:43:35.500Z
out.println(i1.equals(i2));                  // true
out.println(i1.equals(i3));                  // true

In the code below, oneInstant has the nano-of-second set to 500,000,000 nanoseconds and the epoch-second set to 1 day after the epoch.

Click here to view code image

Instant oneInstant = Instant.now()
                            .with(ChronoField.MILLI_OF_SECOND, 500)
                            .with(ChronoField.INSTANT_SECONDS, 24L*60*60);
out.println(oneInstant);                     // 1970-01-02T00:00:00.500Z