4. New Features in Java libraries

Java 8 adds a lot of new classes and extends existing ones in order to provide better support of modern concurrency, functional programming, date/time, and many more.

4.1. Optional

The famous NullPointerException is by far the most popular cause of Java application failures. Long time ago the great Google Guava project introduced the Optionals as a solution to NullPointerExceptions, discouraging codebase pollution with null checks and encouraging developers to write cleaner code. Inspired by Google Guava, the Optional is now a part of Java 8 library.

Optional is just a container: it can hold a value of some type T or just be null. It provides a lot of useful methods so the explicit null checks have no excuse anymore. Please refer to official Java 8 documentation for more details.

We are going to take a look on two small examples of Optional usages: with the nullable value and with the value which does not allow nulls.

  1. Optional< String > fullName = Optional.ofNullable( null );
  2. System.out.println( "Full Name is set? " + fullName.isPresent() );
  3. System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) );
  4. System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );

The isPresent() method returns true if this instance of Optional has non-null value and false otherwise. The orElseGet() method provides the fallback mechanism in case Optional has null value by accepting the function to generate the default one. The map() method transforms the current Optional’s value and returns the new Optional instance. The orElse() method is similar to orElseGet() but instead of function it accepts the default value. Here is the output of this program:

  1. Full Name is set? false
  2. Full Name: [none]
  3. Hey Stranger!

Let us briefly look on another example:

  1. Optional< String > firstName = Optional.of( "Tom" );
  2. System.out.println( "First Name is set? " + firstName.isPresent() );
  3. System.out.println( "First Name: " + firstName.orElseGet( () -> "[none]" ) );
  4. System.out.println( firstName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
  5. System.out.println();

And here is the output:

  1. First Name is set? true
  2. First Name: Tom
  3. Hey Tom!

For more details please refer to official documentation.

4.2. Streams

The newly added Stream API (java.util.stream) introduces real-world functional-style programming into the Java. This is by far the most comprehensive addition to Java library intended to make Java developers significantly more productive by allowing them to write effective, clean, and concise code.

Stream API makes collections processing greatly simplified (but it is not limited to Java collections only as we will see later). Let us take start off with simple class called Task.

  1. public class Streams {
  2. private enum Status {
  3. OPEN, CLOSED
  4. };
  5.  
  6. private static final class Task {
  7. private final Status status;
  8. private final Integer points;
  9.  
  10. Task( final Status status, final Integer points ) {
  11. this.status = status;
  12. this.points = points;
  13. }
  14.  
  15. public Integer getPoints() {
  16. return points;
  17. }
  18.  
  19. public Status getStatus() {
  20. return status;
  21. }
  22.  
  23. @Override
  24. public String toString() {
  25. return String.format( "[%s, %d]", status, points );
  26. }
  27. }
  28. }

Task has some notion of points (or pseudo-complexity) and can be either OPEN or CLOSED. And then let us introduce a small collection of tasks to play with.

  1. final Collection< Task > tasks = Arrays.asList(
  2. new Task( Status.OPEN, 5 ),
  3. new Task( Status.OPEN, 13 ),
  4. new Task( Status.CLOSED, 8 )
  5. );

The first question we are going to address is how many points in total all OPEN tasks have? Up to Java 8, the usual solution for it would be some sort of foreach iteration. But in Java 8 the answers is streams: a sequence of elements supporting sequential and parallel aggregate operations.

  1. // Calculate total points of all active tasks using sum()
  2. final long totalPointsOfOpenTasks = tasks
  3. .stream()
  4. .filter( task -> task.getStatus() == Status.OPEN )
  5. .mapToInt( Task::getPoints )
  6. .sum();
  7.  
  8. System.out.println( "Total points: " + totalPointsOfOpenTasks );

And the output on the console looks like that:

  1. Total points: 18

There are a couple of things going on here. Firstly, the tasks collection is converted to its stream representation. Then, the filter operation on stream filters out all CLOSED tasks. On next step, the mapToInt operation converts the stream of Tasks to the stream of Integers using Task::getPoints method of the each task instance. And lastly, all points are summed up using sum method, producing the final result.

Before moving on to the next examples, there are some notes to keep in mind about streams (more details here). Stream operations are divided into intermediate and terminal operations.

Intermediate operations return a new stream. They are always lazy, executing an intermediate operation such as filter does not actually perform any filtering, but instead creates a new stream that, when traversed, contains the elements of the initial stream that match the given predicate

Terminal operations, such as forEach or sum, may traverse the stream to produce a result or a side-effect. After the terminal operation is performed, the stream pipeline is considered consumed, and can no longer be used. In almost all cases, terminal operations are eager, completing their traversal of the underlying data source.

Yet another value proposition of the streams is out-of-the box support of parallel processing. Let us take a look on this example, which does sums the points of all the tasks.

  1. // Calculate total points of all tasks
  2. final double totalPoints = tasks
  3. .stream()
  4. .parallel()
  5. .map( task -> task.getPoints() ) // or map( Task::getPoints )
  6. .reduce( 0, Integer::sum );
  7.  
  8. System.out.println( "Total points (all tasks): " + totalPoints );

It is very similar to the first example except the fact that we try to process all the tasks in parallel and calculate the final result using reduce method.

Here is the console output:

  1. Total points (all tasks): 26.0

Often, there is a need to performing a grouping of the collection elements by some criteria. Streams can help with that as well as an example below demonstrates.

  1. // Group tasks by their status
  2. final Map< Status, List< Task > > map = tasks
  3. .stream()
  4. .collect( Collectors.groupingBy( Task::getStatus ) );
  5. System.out.println( map );

The console output of this example looks like that:

  1. {CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}

To finish up with the tasks example, let us calculate the overall percentage (or weight) of each task across the whole collection, based on its points.

  1. // Calculate the weight of each tasks (as percent of total points)
  2. final Collection< String > result = tasks
  3. .stream() // Stream< String >
  4. .mapToInt( Task::getPoints ) // IntStream
  5. .asLongStream() // LongStream
  6. .mapToDouble( points -> points / totalPoints ) // DoubleStream
  7. .boxed() // Stream< Double >
  8. .mapToLong( weigth -> ( long )( weigth * 100 ) ) // LongStream
  9. .mapToObj( percentage -> percentage + "%" ) // Stream< String>
  10. .collect( Collectors.toList() ); // List< String >
  11.  
  12. System.out.println( result );

The console output is just here:

  1. [19%, 50%, 30%]

And lastly, as we mentioned before, the Stream API is not only about Java collections. The typical I/O operations like reading the text file line by line is a very good candidate to benefit from stream processing. Here is a small example to confirm that.

  1. final Path path = new File( filename ).toPath();
  2. try( Stream< String > lines = Files.lines( path, StandardCharsets.UTF_8 ) ) {
  3. lines.onClose( () -> System.out.println("Done!") ).forEach( System.out::println );
  4. }

The onClose method called on the stream returns an equivalent stream with an additional close handler. Close handlers are run when the close() method is called on the stream.

Stream API together with Lambdas and Method References baked by Interface’s Default and Static Methods is the Java 8 response to the modern paradigms in software development. For more details, please refer to official documentation.

4.3. Date/Time API (JSR 310)

Java 8 makes one more take on date and time management by delivering New Date-Time API (JSR 310). Date and time manipulation is being one of the worst pain points for Java developers. The standard java.util.Date followed by java.util.Calendar hasn’t improved the situation at all (arguably, made it even more confusing).

That is how Joda-Time was born: the great alternative date/time API for Java. The Java 8’s New Date-Time API (JSR 310) was heavily influenced by Joda-Time and took the best of it. The new java.time package contains all the classes for date, time, date/time, time zones, instants, duration, and clocks manipulation. In the design of the API the immutability has been taken into account very seriously: no change allowed (the tough lesson learnt from java.util.Calendar). If the modification is required, the new instance of respective class will be returned.

Let us take a look on key classes and examples of their usages. The first class is Clock which provides access to the current instant, date and time using a time-zone. Clock can be used instead of System.currentTimeMillis() and TimeZone.getDefault().

  1. // Get the system clock as UTC offset
  2. final Clock clock = Clock.systemUTC();
  3. System.out.println( clock.instant() );
  4. System.out.println( clock.millis() );

The sample output on a console:

  1. 2014-04-12T15:19:29.282Z
  2. 1397315969360

Other new classes we are going to look at are LocaleDate and LocalTime. LocaleDate holds only the date part without a time-zone in the ISO-8601 calendar system. Respectively, LocaleTime holds only the time part without time-zone in the ISO-8601 calendar system. Both LocaleDate and LocaleTime could be created from Clock.

  1. // Get the local date and local time
  2. final LocalDate date = LocalDate.now();
  3. final LocalDate dateFromClock = LocalDate.now( clock );
  4.  
  5. System.out.println( date );
  6. System.out.println( dateFromClock );
  7.  
  8. // Get the local date and local time
  9. final LocalTime time = LocalTime.now();
  10. final LocalTime timeFromClock = LocalTime.now( clock );
  11.  
  12. System.out.println( time );
  13. System.out.println( timeFromClock );

The sample output on a console:

  1. 2014-04-12
  2. 2014-04-12
  3. 11:25:54.568
  4. 15:25:54.568

The LocalDateTime combines together LocaleDate and LocalTime and holds a date with time but without a time-zone in the ISO-8601 calendar system. A quick example is shown below.

  1. // Get the local date/time
  2. final LocalDateTime datetime = LocalDateTime.now();
  3. final LocalDateTime datetimeFromClock = LocalDateTime.now( clock );
  4.  
  5. System.out.println( datetime );
  6. System.out.println( datetimeFromClock );

The sample output on a console:

  1. 2014-04-12T11:37:52.309
  2. 2014-04-12T15:37:52.309

If case you need a date/time for particular timezone, the ZonedDateTime is here to help. It holds a date with time and with a time-zone in the ISO-8601 calendar system. Here are a couple of examples for different timezones.

  1. // Get the zoned date/time
  2. final ZonedDateTime zonedDatetime = ZonedDateTime.now();
  3. final ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock );
  4. final ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of( "America/Los_Angeles" ) );
  5.  
  6. System.out.println( zonedDatetime );
  7. System.out.println( zonedDatetimeFromClock );
  8. System.out.println( zonedDatetimeFromZone );

The sample output on a console:

  1. 2014-04-12T11:47:01.017-04:00[America/New_York]
  2. 2014-04-12T15:47:01.017Z
  3. 2014-04-12T08:47:01.017-07:00[America/Los_Angeles]

And finally, let us take a look on Duration class: an amount of time in terms of seconds and nanoseconds. It makes very easy to compute the different between two dates. Let us take a look on that.

  1. // Get duration between two dates
  2. final LocalDateTime from = LocalDateTime.of( 2014, Month.APRIL, 16, 0, 0, 0 );
  3. final LocalDateTime to = LocalDateTime.of( 2015, Month.APRIL, 16, 23, 59, 59 );
  4.  
  5. final Duration duration = Duration.between( from, to );
  6. System.out.println( "Duration in days: " + duration.toDays() );
  7. System.out.println( "Duration in hours: " + duration.toHours() );

The example above computes the duration (in days and hours) between two dates, 16 April 2014 and 16 April 2015. Here is the sample output on a console:

  1. Duration in days: 365
  2. Duration in hours: 8783

The overall impression about Java 8’s new date/time API is very, very positive. Partially, because of the battle-proved foundation it is built upon (Joda-Time), partially because this time it was finally tackled seriously and developer voices have been heard. For more details please refer to official documentation.

4.4. Nashorn JavaScript engine

Java 8 comes with new Nashorn JavaScript engine which allows developing and running certain kinds of JavaScript applications on JVM. Nashorn JavaScript engine is just another implementation of javax.script.ScriptEngine and follows the same set of rules, permitting Java and JavaScript interoperability. Here is a small example.

  1. ScriptEngineManager manager = new ScriptEngineManager();
  2. ScriptEngine engine = manager.getEngineByName( "JavaScript" );
  3.  
  4. System.out.println( engine.getClass().getName() );
  5. System.out.println( "Result:" + engine.eval( "function f() { return 1; }; f() + 1;" ) );

The sample output on a console:

  1. jdk.nashorn.api.scripting.NashornScriptEngine
  2. Result: 2

We will get back to the Nashorn later in the section dedicated to new Java tools.

4.5. Base64

Finally, the support of Base64 encoding has made its way into Java standard library with Java 8 release. It is very easy to use as following example shows off.

  1. package com.javacodegeeks.java8.base64;
  2.  
  3. import java.nio.charset.StandardCharsets;
  4. import java.util.Base64;
  5.  
  6. public class Base64s {
  7. public static void main(String[] args) {
  8. final String text = "Base64 finally in Java 8!";
  9.  
  10. final String encoded = Base64
  11. .getEncoder()
  12. .encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );
  13. System.out.println( encoded );
  14.  
  15. final String decoded = new String(
  16. Base64.getDecoder().decode( encoded ),
  17. StandardCharsets.UTF_8 );
  18. System.out.println( decoded );
  19. }
  20. }

The console output from program run shows both encoded and decoded text:

  1. QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==
  2. Base64 finally in Java 8!

There are also URL-friendly encoder/decoder and MIME-friendly encoder/decoder provided by the Base64 class (Base64.getUrlEncoder() / Base64.getUrlDecoder(), Base64.getMimeEncoder() / Base64.getMimeDecoder()).

4.6. Parallel Arrays

Java 8 release adds a lot of new methods to allow parallel arrays processing. Arguably, the most important one is parallelSort() which may significantly speedup the sorting on multicore machines. The following small example demonstrates this new method family (parallelXxx) in action.

  1. package com.javacodegeeks.java8.parallel.arrays;
  2.  
  3. import java.util.Arrays;
  4. import java.util.concurrent.ThreadLocalRandom;
  5.  
  6. public class ParallelArrays {
  7. public static void main( String[] args ) {
  8. long[] arrayOfLong = new long [ 20000 ];
  9.  
  10. Arrays.parallelSetAll( arrayOfLong,
  11. index -> ThreadLocalRandom.current().nextInt( 1000000 ) );
  12. Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
  13. i -> System.out.print( i + " " ) );
  14. System.out.println();
  15.  
  16. Arrays.parallelSort( arrayOfLong );
  17. Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
  18. i -> System.out.print( i + " " ) );
  19. System.out.println();
  20. }
  21. }

This small code snippet uses method parallelSetAll() to fill up arrays with 20000 random values. After that, the parallelSort() is being applied. The program outputs first 10 elements before and after sorting so to ensure the array is really ordered. The sample program output may look like that (please notice that array elements are randomly generated):

  1. Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378
  2. Sorted: 39 220 263 268 325 607 655 678 723 793

4.7. Concurrency

New methods have been added to the java.util.concurrent.ConcurrentHashMap class to support aggregate operations based on the newly added streams facility and lambda expressions. Also, new methods have been added to the java.util.concurrent.ForkJoinPool class to support a common pool (check also our free course on Java concurrency).

The new java.util.concurrent.locks.StampedLock class has been added to provide a capability-based lock with three modes for controlling read/write access (it might be considered as better alternative for infamous java.util.concurrent.locks.ReadWriteLock).

New classes have been added to the java.util.concurrent.atomic package:

  • DoubleAccumulator
  • DoubleAdder
  • LongAccumulator
  • LongAdder