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

Side Effects – Streams

Side Effects

Efficient execution of parallel streams that produces the desired results requires the stream operations (and their behavioral parameters) to avoid certain side effects.

  • Non-interfering behaviors

The behavioral parameters of stream operations should be non-interfering (p. 909)—both for sequential and parallel streams. Unless the stream data source is concurrent, the stream operations should not modify it during the execution of the stream. See building streams from collections (p. 897).

  • Stateless behaviors

The behavioral parameters of stream operations should be stateless (p. 909)— both for sequential and parallel streams. A behavioral parameter implemented as a lambda expression should not depend on any state that might change during the execution of the stream pipeline. The results from a stateful behavioral parameter can be nondeterministic or even incorrect. For a stateless behavioral parameter, the results are always the same.

Shared state that is accessed by the behavior parameters of stream operations in a pipeline is not a good idea. Executing the pipeline in parallel can lead to race conditions in accessing the global state, and using synchronization code to provide thread-safety may defeat the purpose of parallelization. Using the three-argument reduce() or collect() method can be a better solution to encapsulate shared state.

The intermediate operations distinct(), skip(), limit(), and sorted() are stateful (p. 915, p. 915, p. 917, p. 929). See also Table 16.3, p. 938. They can carry extra performance overhead when executed in a parallel stream, as such an operation can entail multiple passes over the data and may require significant data buffering.

Ordering

An ordered stream (p. 891) processed by operations that preserve the encounter order will produce the same results, regardless of whether it is executed sequentially or in parallel. However, repeated execution of an unordered stream— sequential or parallel—can produce different results.

Preserving the encounter order of elements in an ordered parallel stream can incur a performance penalty. The performance of an ordered parallel stream can be improved if the ordering constraint is removed by calling the unordered() intermediate operation on the stream (p. 932).

The three stateful intermediate operations distinct(), skip(), and limit() can improve performance in a parallel stream that is unordered, as compared to one that is ordered (p. 915, p. 915, p. 917). The distinct() operation need only buffer any occurrence of a duplicate value in the case of an unordered parallel stream, rather than the first occurrence. The skip() operation can skip any n elements in the case of an unordered parallel stream, not necessarily the first n elements. The limit() operation can truncate the stream after any n elements in the case of an unordered parallel stream, and not necessarily after the first n elements.

The terminal operation findAny() is intentionally nondeterministic, and can return any element in the stream (p. 952). It is specially suited for parallel streams.

The forEach() terminal operation ignores the encounter order, but the forEachOrdered() terminal operation preserves the order (p. 948). The sorted() stateful intermediate operation, on the other hand, enforces a specific encounter order, regardless of whether it executed in a parallel pipeline (p. 929).

Autoboxing and Unboxing of Numeric Values

As the Stream API allows both object and numeric streams, and provides support for conversion between them (p. 934), choosing a numeric stream when possible can offset the overhead of autoboxing and unboxing in object streams.

As we have seen, in order to take full advantage of parallel execution, composition of a stream pipeline must follow certain rules to facilitate parallelization. In summary, the benefits of using parallel streams are best achieved when:

  • The stream data source is of a sufficient size and the stream is easily splittable into substreams.
  • The stream operations have no adverse side effects and are computation-intensive enough to warrant parallelization.

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.

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.

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 Periods – Date and Time

Creating Periods

Like the temporal classes, the Period class does not provide any public constructors, but rather provides an overloaded static factory method of() to construct periods of different lengths, based on date units.

Click here to view code image

Period p = Period.of(2, 4, 8);         // (1)
System.out.println(p);                 // (2) P2Y4M8D (2 Years, 4 Months, 8 Days)
Period p1 = Period.ofYears(10);        // P10Y, period of 10 years.
Period p2 = Period.ofMonths(14);       // P14M, period of 14 months.
Period p3 = Period.ofDays(40);         // P40D, period of 40 days.
Period p4 = Period.ofWeeks(2);         // P14D, period of 14 days (2 weeks).

The most versatile of() method requires the amount of time for all date units: years, months, and days, as at (1). Other of() methods create a period based on a particular date unit, as shown in the examples above.

The toString() method of the Period class returns a text representation of a Period according to the ISO standard: PyYmMdD—that is, y Years, m Months, and d Days. The output from (2) above, P2Y4M8D, indicates a period of 2 years, 4 months, and 8 days.

The code snippet below does not create a period of 3 years, 4 months, and 5 days— it creates a period of only 5 days. The first method call is invoked with the class name, and the subsequent method calls are on the new Period object returned as a consequence of the previous call. The of() method creates a new Period object based on its argument.

Click here to view code image

Period period = Period.ofYears(3).ofMonths(4).ofDays(5); // P5D. Logical error.

As we would expect, we can create a period that represents the amount of time between two dates by calling the static method between() of the Period class.

Click here to view code image

LocalDate d1 = LocalDate.of(2021, 3, 1);  // 2021-03-01
LocalDate d2 = LocalDate.of(2022, 3, 1);  // 2022-03-01
Period period12 = Period.between(d1, d2); // P1Y
Period period21 = Period.between(d2, d1); // P-1Y

The Period class also provides the static method parse() to create a period from a string that contains a text representation of a period in the ISO standard. If the format of the string is not correct, a java.time.format.DateTimeParseException is thrown.

Click here to view code image

Period period2 = Period.parse(“P1Y15M20D”); // 1 year, 15 months, 20 days
Period period3 = Period.parse(“P20D”);      // 20 days
Period period4 = Period.parse(“P5W”);       // 35 days (5 weeks)
//  Period pX = Period.parse(“P24H”); // java.time.format.DateTimeParseException

static Period ZERO

This constant defines a Period of length zero (P0D).

Click here to view code image

static Period of(int years, int months, int days)
static Period ofYears(int years)
static Period ofMonths(int months)
static Period ofWeeks(int weeks)
static Period ofDays(int days)

These static factory methods return a Period representing an amount of time equal to the specified value of a date unit. Date units that are implicit are set to zero. A week is equal to 7 days. The argument value can be negative.

Click here to view code image

static Period between(LocalDate startDateInclusive,
                      LocalDate endDateExclusive)

This static method returns a Period consisting of the number of years, months, and days between the two dates. The calculation excludes the end date. The result of this method can be a negative period if the end date is before the start date.

String toString()

Returns a text representation of a Period according to the ISO standard. Typical formats are PyYmMdD and PnW—that is, y Years, m Months, and d Days, or n Weeks.

Click here to view code image

static Period parse(CharSequence text)

This static method returns a Period parsed from a character sequence—for example, “P3Y10M2D” (3 years, 10 months, 2 days). A java.time.format.Date-TimeParseException is thrown if the text cannot be parsed to a period.

Temporal Arithmetic with Dates and Times – Date and Time

Temporal Arithmetic with Dates and Times

The temporal classes provide plus and minus methods that return a copy of the original object that has been incremented or decremented by a specific amount of time— for example, by number of hours or by number of months.

The LocalTime and LocalDate classes provide plus/minus methods to increment/ decrement a time or a date by a specific amount in terms of a time unit (e.g., hours, minutes, and seconds) or a date unit (e.g., years, months, and days), respectively. The LocalDateTime class provides plus/minus methods to increment/decrement a date-time object by an amount that is specified in terms of either a time unit or a date unit. For example, the plusMonths(m) and plus(m, ChronoUnit.MONTHS) method calls to a LocalDate object will return a new LocalDate object after adding the specified number of months passed as an argument to the method. Similarly, the minus-Minutes(mm) and minus(mm, ChronoUnit.MINUTES) method calls on a LocalTime class will return a new LocalTime object after subtracting the specified number of minutes passed as an argument to the method. The change is relative, and is reflected in the new temporal object that is returned. Such plus/minus methods are also called relative adjusters, in contrast to absolute adjusters (p. 1035). The ChronoUnit enum type implements the TemporalUnit interface (p. 1044).

Click here to view code image

// LocalTime, LocalDateTime
LocalTime/LocalDateTime minusHours(long hours)
LocalTime/LocalDateTime plusHours(long hours)
LocalTime/LocalDateTime minusMinutes(long minutes)
LocalTime/LocalDateTime plusMinutes(long minutes)
LocalTime/LocalDateTime minusSeconds(long seconds)
LocalTime/LocalDateTime plusSeconds(long seconds)
LocalTime/LocalDateTime minusNanos(long nanos)
LocalTime/LocalDateTime plusNanos(long nanos)

Return a copy of this LocalTime or LocalDateTime object with the specified amount either subtracted or added to the value of a specific time field. The calculation always wraps around midnight.

For the methods of the LocalDateTime class, a DateTimeException is thrown if the result exceeds the date range.

Click here to view code image

// LocalDate, LocalDateTime
LocalDate/LocalDateTime minusYears(long years)
LocalDate/LocalDateTime plusYears(long years)
LocalDate/LocalDateTime minusMonths(long months)
LocalDate/LocalDateTime plusMonths(long months)
LocalDate/LocalDateTime minusWeeks(long weeks)
LocalDate/LocalDateTime plusWeeks(long weeks)
LocalDate/LocalDateTime minusDays(long days)
LocalDate/LocalDateTime plusDays(long days)

Return a copy of this LocalDate or LocalDateTime with the specified amount either subtracted or added to the value of a specific date field.

All methods throw a DateTimeException if the result exceeds the date range.

The first four methods will change the day of the month to the last valid day of the month if necessary, when the day of the month becomes invalid as a result of the operation.

The last four methods will adjust the month and year fields as necessary to ensure a valid result.

Click here to view code image

// LocalTime, LocalDate, LocalDateTime
LocalTime/LocalDate/LocalDateTime minus(long amountToSub,
                                        TemporalUnit unit)
LocalTime/LocalDate/LocalDateTime plus(long amountToAdd, TemporalUnit unit)
boolean isSupported(TemporalUnit unit)

The minus() and plus() methods return a copy of this temporal object with the specified amount subtracted or added, respectively, according to the TemporalUnit specified. The ChronoUnit enum type implements the TemporalUnit interface, and its enum constants define specific temporal units (p. 1044).

The isSupported() method checks if the specified TemporalUnit is supported by this temporal object. It avoids an exception being thrown if it has been determined that the unit is supported.

The minus() or the plus() method can result in any one of these exceptions: DateTimeException (the amount cannot be subtracted or added), Unsupported-TemporalTypeException (unit is not supported), or ArithmeticException (numeric overflow occurred).

Click here to view code image

// LocalTime, LocalDate, LocalDateTime
LocalTime/LocalDate/LocalDateTime minus(TemporalAmount amountToSub)
LocalTime/LocalDate/LocalDateTime plus(TemporalAmount amountToAdd)

Return a copy of this temporal object with the specified temporal amount subtracted or added, respectively. The classes Period (p. 1057) and Duration (p. 1064) implement the TemporalAmount interface.

The minus() or the plus() method can result in any one of these exceptions: DateTimeException (the temporal amount cannot be subtracted or added) or ArithmeticException (numeric overflow occurred).

Click here to view code image

// LocalTime, LocalDate, LocalDateTime
long until(Temporal endExclusive, TemporalUnit unit)

Calculates the amount of time between two temporal objects in terms of the specified TemporalUnit (p. 1044). The start and the end points are this temporal object and the specified temporal argument endExclusive, where the end point is excluded. The result will be negative if the other temporal is before this temporal.

The until() method can result in any one of these exceptions: DateTime-Exception (the temporal amount cannot be calculated or the end temporal cannot be converted to the appropriate temporal object), UnsupportedTemporalType-Exception (unit is not supported), or ArithmeticException (numeric overflow occurred).

Click here to view code image

// LocalDate
Period until(ChronoLocalDate endDateExclusive)

Calculates the amount of time between this date and another date as a Period (p. 1057). The calculation excludes the end date. The LocalDate class implements the ChronoLocalDate interface.

Example 17.3 demonstrates what we can call temporal arithmetic, where a LocalDate object is modified by adding or subtracting an amount specified as days, weeks, or months. Note how the value of the date fields is adjusted after each operation. In Example 17.3, the date 2021-10-23 is created at (1), and 10 months, 3 weeks, and 40 days are successively added to the new date object returned by each plus method call at (2), (3), and (4), respectively, resulting in the date 2022-10-23. We then subtract 2 days, 4 weeks, and 11 months successively from the new date object returned by each minus() method call at (5), (6), and (7), respectively, resulting in the date 2021-10-23. The method calls at (5), (6), and (7) are passed the temporal unit explicitly. In Example 17.3, several assignment statements are used to print the intermediate dates, but the code can be made more succinct by method chaining.

Click here to view code image

LocalDate date = LocalDate.of(2021, 10, 23);             // 2021-10-23
date = date.plusMonths(10).plusWeeks(3).plusDays(40);    // Method chaining
System.out.println(date);                                // 2022-10-23
date = date.minus(2, ChronoUnit.DAYS)
           .minus(4, ChronoUnit.WEEKS)
           .minus(11, ChronoUnit.MONTHS);                // Method chaining
System.out.println(date);                                // 2021-10-23

The following code snippet illustrates the wrapping of time around midnight, as one would expect on a 24-hour clock. Each method call returns a new LocalTime object.

Click here to view code image

LocalTime witchingHour = LocalTime.MIDNIGHT              // 00:00
    .plusHours(14)                                       // 14:00
    .plusMinutes(45)                                     // 14:45
    .plusMinutes(30)                                     // 15:15
    .minusHours(15)                                      // 00:15
    .minusMinutes(15);                                   // 00:00

The next code snippet illustrates how the plusYears() method adjusts the day of the month, if necessary, when the year value is changed. The year in the date 2020-02-29 is changed to 2021 by adding 1 year, resulting in the following date: 2021-02-29. The plusYears() method adjusts the day of the month to the last valid day of the month, 28; as the year 2021 is not a leap year, the month of February cannot have 29 days.

Click here to view code image

LocalDate date5 = LocalDate.of(2020, 2, 29);  // Original: 2020-02-29
date5 = date5.plusYears(1);                   // Expected: 2021-02-29
System.out.println(“Date5: ” + date5);        // Adjusted: 2021-02-28

A temporal can also be adjusted by a temporal amount—for example, by a Period (p. 1057) or a Duration (p. 1064). The methods plus() and minus() accept the temporal amount as an argument, as shown by the code below.

Click here to view code image

LocalTime busDep = LocalTime.of(12, 15);                   // 12:15
Duration d1 = Duration.ofMinutes(30);                      // PT30M
LocalTime nextBusDep = busDep.plus(d1);                    // 12:45
LocalDate birthday = LocalDate.of(2020, 10, 23);           // 2020-10-23
Period p1 = Period.ofYears(1);                             // P1Y
LocalDate nextBirthday = birthday.plus(p1);                // 2021-10-23

The until() method can be used to calculate the amount of time between two compatible temporal objects. The code below calculates the number of days to New Year’s Day from the current date; the result, of course, will depend on the current date. In the call to the until() method at (1), the temporal unit specified is ChronoUnit.DAYS, as we want the difference between the dates to be calculated in days.

Click here to view code image

LocalDate currentDate = LocalDate.now();
LocalDate newYearDay = currentDate.plusYears(1).withMonth(1).withDayOfMonth(1);
long daysToNewYear = currentDate.until(newYearDay, ChronoUnit.DAYS); // (1)
System.out.println(“Current Date: ” + currentDate); // Current Date: 2021-03-08
System.out.println(“New Year’s Day: ” + newYearDay);// New Year’s Day: 2022-01-01
System.out.println(“Days to New Year: ” + daysToNewYear);// Days to New Year: 299

The statement at (1) below is meant to calculate the number of minutes until midnight from now, but throws a DateTimeException because it is not possible to obtain a LocalDateTime object from the end point, which is a LocalTime object.

Click here to view code image

long minsToMidnight = LocalDateTime.now()             // (1) DateTimeException!
         .until(LocalTime.MIDNIGHT.minusSeconds(1), ChronoUnit.MINUTES);

However, the statement at (2) executes normally, as both the start and end points are LocalTime objects.

Click here to view code image

long minsToMidnight = LocalTime.now()                 // (2)
         .until(LocalTime.MIDNIGHT.minusSeconds(1), ChronoUnit.MINUTES);

Example 17.3 Temporal Arithmetic

Click here to view code image

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
public class TemporalArithmetic {
  public static void main(String[] args) {
    LocalDate date = LocalDate.of(2021, 10, 23);           // (1)
    System.out.println(“Date:             ” + date);       // 2021-10-23
    date = date.plusMonths(10);                            // (2)
    System.out.println(“10 months after:  ” + date);       // 2022-08-23
    date = date.plusWeeks(3);                              // (3)
    System.out.println(“3 weeks after:    ” + date);       // 2022-09-13
    date = date.plusDays(40);                              // (4)
    System.out.println(“40 days after:    ” + date);       // 2022-10-23
    date = date.minus(2, ChronoUnit.DAYS);                 // (5)
    System.out.println(“2 days before:    ” + date);       // 2022-10-21
    date = date.minus(4, ChronoUnit.WEEKS);                // (6)
    System.out.println(“4 weeks before:   ” + date);       // 2022-09-23
    date = date.minus(11, ChronoUnit.MONTHS);              // (7)
    System.out.println(“11 months before: ” + date);       // 2021-10-23
  }
}

Output from the program:

Date:             2021-10-23
10 months after:  2022-08-23
3 weeks after:    2022-09-13
40 days after:    2022-10-23
2 days before:    2022-10-21
4 weeks before:   2022-09-23
11 months before: 2021-10-23

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)