JavaRush /Java Blog /Random EN /Java 8 Features – The Ultimate Guide (Part 2)
0xFF
Level 9
Донецк

Java 8 Features – The Ultimate Guide (Part 2)

Published in the Random EN group
The second part of the translation of the article Java 8 Features – The ULTIMATE Guide . The first part is here (link may change). Java 8 Features – The Ultimate Guide (Part 2) - 1

5. New features in Java 8 libraries

Java 8 has added many new classes and extended existing ones to better support modern concurrency, functional programming, date/time, and more.

5.1. Class Optional

The famous NullPointerException is by far the most common cause of Java application failures. A long time ago, Google's excellent project Guava presented Optionalas a solution NullPointerException, thereby preventing code from being polluted by null checks, and as a result encouraging the writing of cleaner code. The Google-inspired Guava class Optionalis now part of Java 8. OptionalIt's just a container: it can contain a value or some type Т, or simply be null. It provides many useful methods so that explicit null checks are no longer justified. Refer to the official documentation for more detailed information. Let's look at two small examples of use Optional: with and without null.
Optional<String> fullName = Optional.ofNullable( null );
System.out.println( "Full Name is set? " + fullName.isPresent() );
System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) );
System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
The method isPresent()returns true if the instance Optionalcontains a non-null value and false otherwise. The method orElseGet()contains a fallback mechanism for the result if Optionalit contains null, accepting functions to generate a default value. The map () method transforms the current value Optionaland returns a new instance Optional. The method orElse()is similar to orElseGet(), but instead of a function it takes a default value. Here is the output of this program:
Full Name is set? false
Full Name: [none]
Hey Stranger!
Let's take a quick look at another example:
Optional<String> firstName = Optional.of( "Tom" );
System.out.println( "First Name is set? " + firstName.isPresent() );
System.out.println( "First Name: " + firstName.orElseGet( () -> "[none]" ) );
System.out.println( firstName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
System.out.println();
The result will be like this:
First Name is set? true
First Name: Tom
Hey Tom!
For more detailed information, please refer to the official documentation .

5.2. Streams

The newly added Stream API ( java.util.stream) introduces real functional style programming in Java. It is by far the most comprehensive addition to the Java library and allows Java developers to be significantly more efficient and also enables them to create efficient, clean and concise code. The Stream API makes processing collections much easier (but not limited to them, as we'll see later). Let's take a simple class as an example Task.
public class Streams  {
    private enum Status {
        OPEN, CLOSED
    };

    private static final class Task {
        private final Status status;
        private final Integer points;

        Task( final Status status, final Integer points ) {
            this.status = status;
            this.points = points;
        }

        public Integer getPoints() {
            return points;
        }

        public Status getStatus() {
            return status;
        }

        @Override
        public String toString() {
            return String.format( "[%s, %d]", status, points );
        }
    }
}
Task has some sense of points (or pseudo-difficulties) and can be either OPEN or CLOSE . Let's introduce a small collection of problems to play with.
final Collection<Task> tasks = Arrays.asList(
    new Task( Status.OPEN, 5 ),
    new Task( Status.OPEN, 13 ),
    new Task( Status.CLOSED, 8 )
);
The first question we intend to find out is how many points do OPEN tasks currently contain? Before Java 8, the usual solution for this would have been to use an iterator foreach. But in Java 8, the answer is streams: a sequence of elements that support sequential and parallel aggregate operations.
// Подсчет общего количества очков всех активных задач с использованием sum()
final long totalPointsOfOpenTasks = tasks
    .stream()
    .filter( task -> task.getStatus() == Status.OPEN )
    .mapToInt( Task::getPoints )
    .sum();

System.out.println( "Total points: " + totalPointsOfOpenTasks );
And the console output will look like:
Total points: 18
Let's look at what's going on here. First, the task collection is converted into a streaming representation. The operation then filterfilters out all tasks with a CLOSED status . In the next step, the operation mapToIntconverts stream Tasks into stream Integers using a method Task::getPointsfor each instance Task. Finally, all points are summed using the method sum, which provides the final result. Before moving on to the next examples, there are some notes about threads to keep in mind (more details here ). Operations streamare divided into intermediate and final operations. Intermediate operations return a new stream. They are always lazy; when performing intermediate operations such as filter, they do not actually perform filtering, but instead create a new stream, which, when completed, contains the elements of the original stream that match the given predicate. Finite operations , such as forEachand sum, can be passed through a stream to produce a result or side effect. Once the final operation is completed, the stream is considered used and cannot be used again. In almost all cases, the end operations tend to complete their traversal through the underlying data source. Another valuable feature of threads is the support for parallel processes out of the box. Let's look at this example, which finds the sum of the scores of all problems.
// Calculate total points of all tasks
final double totalPoints = tasks
   .stream()
   .parallel()
   .map( task -> task.getPoints() ) // or map( Task::getPoints )
   .reduce( 0, Integer::sum );

System.out.println( "Total points (all tasks): " + totalPoints );
This is very similar to the first example, except that we are trying to process all tasks in parallel and calculate the final result using the method reduce. Here is the console output:
Total points (all tasks): 26.0
Often there is a need to group elements according to a certain criterion. The example demonstrates how threads can help with this.
// Группировка задач по их статусу
final Map<Status, List<Task>> map = tasks
    .stream()
    .collect( Collectors.groupingBy( Task::getStatus ) );
System.out.println( map );
The console output will be as follows:
{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}
To finish with the problem examples, let's calculate the overall percentage (or weight) of each problem in the collection based on the total points:
// Подсчет веса каждой задачи (How процент от общего количества очков)
final Collection<String> result = tasks
    .stream()                                        // Stream<String>
    .mapToInt( Task::getPoints )                     // IntStream
    .asLongStream()                                  // LongStream
    .mapToDouble( points -> points / totalPoints )   // DoubleStream
    .boxed()                                         // Stream<Double>
    .mapToLong( weigth -> ( long )( weigth * 100 ) ) // LongStream
    .mapToObj( percentage -> percentage + "%" )      // Stream<String>
    .collect( Collectors.toList() );                 // List<String>

System.out.println( result );
The console output will be like this:
[19%, 50%, 30%]
Finally, as we noted earlier, the Stream API is not just for Java collections. A typical I/O operation, such as reading text files line by line, is a very good candidate for using stream processing. Here is a small example to prove this.
final Path path = new File( filename ).toPath();
try( Stream<String> lines = Files.lines( path, StandardCharsets.UTF_8 ) ) {
    lines.onClose( () -> System.out.println("Done!") ).forEach( System.out::println );
}
The method onConsole, which is called on a thread, returns an equivalent thread with an additional private handler. The private handler is called when a method close()is called on a thread. The Stream API along with lambdas and reference methods along with default and static methods in Java 8 are the answer to modern software development paradigms. For more detailed information, please refer to the official documentation .

5.3. Date/Time API (JSR 310)

Java 8 brings a new look to date and time management by providing a new Date and Time API (JSR 310) . Date and time manipulation is one of the worst pain points for Java developers. The standard java.util.Datefollowing java.util.Calendardid not generally improve the situation (maybe even made it more confusing). That's how Joda-Time was born : a great date/time API alternative for Java . The new Date/Time API in Java 8 (JSR 310) is heavily influenced by Joda-Time and takes the best from it. The new package java.timecontains all the classes for date, time, date/time, time zones, durations, and time manipulation . The API design took immutability very seriously: changes are not allowed (a hard lesson learned from java.util.Calendar). If modification is required, a new instance of the corresponding class will be returned. Let's look at the main classes and examples of their use. The first class Clock, which provides access to the current instant, date and time using a time zone. Clockcan be used instead of System.currentTimeMillis()and TimeZone.getDefault().
// Получить системное время How смещение UTC
final Clock clock = Clock.systemUTC();
System.out.println( clock.instant() );
System.out.println( clock.millis() );
Example console output:
2014-04-12T15:19:29.282Z
1397315969360
Other new classes we'll be looking at are LocaleDateand LocalTime. LocaleDatecontains only the date part without the time zone in the ISO-8601 calendar system. Accordingly, LocalTimeit contains only part of the timecode>.
// получить местную date и время время
final LocalDate date = LocalDate.now();
final LocalDate dateFromClock = LocalDate.now( clock );

System.out.println( date );
System.out.println( dateFromClock );

// получить местную date и время время
final LocalTime time = LocalTime.now();
final LocalTime timeFromClock = LocalTime.now( clock );

System.out.println( time );
System.out.println( timeFromClock );
Example console output:
2014-04-12
2014-04-12
11:25:54.568
15:25:54.568
LocalDateTimeconcatenates LocaleDateand LocalTimeand contains a date and time, but no time zone, in the ISO-8601 calendar system. A simple example is given below.
// Get the local date/time
final LocalDateTime datetime = LocalDateTime.now();
final LocalDateTime datetimeFromClock = LocalDateTime.now( clock );

System.out.println( datetime );
System.out.println( datetimeFromClock );
Example console output:
2014-04-12T11:37:52.309
2014-04-12T15:37:52.309
In case you need date/time for a specific time zone, ZonedDateTime. It contains the date and time in the ISO-8601 calendar system. Here are some examples for different time zones.
// Получение даты/времени для временной зоны
final ZonedDateTime zonedDatetime = ZonedDateTime.now();
final ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock );
final ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of( "America/Los_Angeles" ) );

System.out.println( zonedDatetime );
System.out.println( zonedDatetimeFromClock );
System.out.println( zonedDatetimeFromZone );
Example console output:
2014-04-12T11:47:01.017-04:00[America/New_York]
2014-04-12T15:47:01.017Z
2014-04-12T08:47:01.017-07:00[America/Los_Angeles]
And finally, let's take a look at the class Duration: time span in seconds and nanoseconds. This makes calculation between two dates very simple. Let's see how to do this:
// Получаем разницу между двумя датами
final LocalDateTime from = LocalDateTime.of( 2014, Month.APRIL, 16, 0, 0, 0 );
final LocalDateTime to = LocalDateTime.of( 2015, Month.APRIL, 16, 23, 59, 59 );

final Duration duration = Duration.between( from, to );
System.out.println( "Duration in days: " + duration.toDays() );
System.out.println( "Duration in hours: " + duration.toHours() );
The example above calculates the duration (in days and hours) between two dates, April 16, 2014 and April 16, 2015 . Here is an example of the console output:
Duration in days: 365
Duration in hours: 8783
The overall impression of the new date/time in Java 8 is very, very positive. Partly because the changes are based on a battle-tested foundation (Joda-Time), partly because this time the issue was seriously reconsidered and the voices of the developers were heard. For details, please refer to the official documentation .

5.4. Nashorn JavaScript engine

Java 8 comes with the new Nashorn JavaScript engine , which allows you to develop and run certain types of JavaScript applications on the JVM. The Nashorn JavaScript engine is simply another implementation of javax.script.ScriptEngine that follows the same set of rules to allow Java and JavaScript to interact. Here's a small example.
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName( "JavaScript" );

System.out.println( engine.getClass().getName() );
System.out.println( "Result:" + engine.eval( "function f() { return 1; }; f() + 1;" ) );
Example console output:
jdk.nashorn.api.scripting.NashornScriptEngine
Result: 2

5.5. Base64

Finally, support for Base64 encoding found its way into the Java standard library with the release of Java 8. It is very easy to use, the example demonstrates this.
package com.javacodegeeks.java8.base64;

import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class Base64s {
    public static void main(String[] args) {
        final String text = "Base64 finally in Java 8!";

        final String encoded = Base64
            .getEncoder()
            .encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );
        System.out.println( encoded );

        final String decoded = new String(
            Base64.getDecoder().decode( encoded ),
            StandardCharsets.UTF_8 );
        System.out.println( decoded );
    }
}
The console output of the program shows both encoded and decoded text:
QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==
Base64 finally in Java 8!
There are also classes for URL-friendly encoders/decoders, as well as MIME-friendly encoders/decoders ( Base64.getUrlEncoder()/ Base64.getUrlDecoder(), Base64.getMimeEncoder()/ Base64.getMimeDecoder()).

5.6. Parallel Arrays

The Java 8 release adds many new methods for parallel array processing. Perhaps the most important of these is parallelSort(), which can greatly speed up sorting on multi-core machines. The small example below demonstrates the new family of methods ( parallelXxx) in action.
package com.javacodegeeks.java8.parallel.arrays;

import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom;

public class ParallelArrays {
    public static void main( String[] args ) {
        long[] arrayOfLong = new long [ 20000 ];

        Arrays.parallelSetAll( arrayOfLong,
            index -> ThreadLocalRandom.current().nextInt( 1000000 ) );
        Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
            i -> System.out.print( i + " " ) );
        System.out.println();

        Arrays.parallelSort( arrayOfLong );
        Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
            i -> System.out.print( i + " " ) );
        System.out.println();
    }
}
This small piece of code uses a method parallelSetAll()to fill an array with 20,000 random values. After this it is applied parallelSort(). The program prints the first 10 elements before and after sorting to show that the array is actually sorted. An example program output might look like this (Note that the elements of the array are random).
Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378
Sorted: 39 220 263 268 325 607 655 678 723 793

5.7. Parallelism

New methods have been added to the class java.util.concurrent.ConcurrentHashMapto support aggregate operations based on the newly added stream objects and lambda expressions. Also new methods have been added to the class java.util.concurrent.ForkJoinPoolto support shared pooling (see also our free course on Java concurrency ). A new class java.util.concurrent.locks.StampedLockhas been added to provide capability-based locking with three access modes for read/write control (it can be considered as a better alternative to the not so good one java.util.concurrent.locks.ReadWriteLock). New classes that have been added to the package java.util.concurrent.atomic:
  • DoubleAccumulator
  • DoubleAdder
  • LongAccumulator
  • LongAdder

6. New features in the Java runtime environment (JVM)

The area PermGenhas been retired and replaced by Metaspace (JEP 122). JVM options -XX:PermSizeand -XX:MaxPermSizehave been replaced by -XX:MetaSpaceSizeand -XX:MaxMetaspaceSizerespectively.

7. Conclusion

The future is here: Java 8 has moved its platform forward by delivering features that allow developers to be more productive. It's still too early to move production systems to Java 8, but adoption should slowly begin to grow over the next few months. However, now is the time to start preparing your codebase for Java 8 compatibility and be ready to incorporate Java 8 changes when it is secure and stable enough. As a testament to the community's acceptance of Java 8, Pivotal recently released the Spring Framework with production support for Java 8 . You can provide your input about the exciting new features in Java 8 in the comments.

8. Sources

Some additional resources that discuss various aspects of Java 8 features in depth:
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION