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

Java 8 Features - 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 - Ultimate Guide (Part 2) - 1

5. New Features in Java 8 Libraries

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

5.1. Optional class

The famous NullPointerException is by far the most common cause of Java application crashes. A long time ago, the great Google project Guava introduced Optionalas a solution NullPointerException, thereby preventing code from being polluted by null checks, and as a result, encouraging writing 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 just be null. It provides many useful methods so that explicit null checks are no longer justified. See the official documentation for more details. Let's look at two small use casesOptional: 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 to result if Optionalnull, accepting functions to generate a default value. The map () method transforms the current value Optionaland returns a new instance of 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 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 complete addition to the Java library and allows Java developers to be much more efficient and also enables them to create efficient, clean and concise code. The Stream API makes handling 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 idea of ​​points (or pseudo complexities) and can be either OPEN or CLOSE . Let's introduce a small collection of tasks 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 are going to find out is how many points do tasks with the OPEN status have now? Prior to Java 8, the usual solution for this would be to use an iterator foreach. But in Java 8, the answer is in streams: a sequence of elements that support serial 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 see what's going on here. First, the collection of tasks is converted to a streaming representation. The operation then filterfilters out all tasks with a status of CLOSED . In the next step, the operation mapToIntconverts stream Tasks to stream Integers using a method Task::getPointson each instance Task. Finally, all scores are summed up 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 operationsreturn a new stream. They are always lazy, when performing intermediate operations such as filter, they do not actually filter, instead a new stream is created, which, when completed, contains the elements of the original stream that match the given predicate. End operations such forEachassum, can be passed through the stream to get a result or a side effect. After the final operation is performed, the stream is considered used and can no longer be used. In almost all cases, the end operations tend to complete their traversal of 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 tasks.
// 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 the tasks in parallel and calculate the final result using the reduce. Here is the output to the console:
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 output to the console will be as follows:
{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}
To finish with the task examples, let's calculate the total percentage (or weight) of each task in the collection based on the total score:
// Подсчет веса каждой задачи (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 stream, returns an equivalent stream with an additional private handle. A private handler is called when the 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 answers to modern paradigms in software development. For more detailed information, please refer to the official documentation .

5.3. Date/Time API (JSR 310)

Java 8 brings a new perspective 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.Calendardidn't generally improve the situation (perhaps made it even more confusing). That's how Joda-Time was born : a great Java date/time API alternative . The new Date/Time API in Java 8 (JSR 310) is heavily influenced by Joda-Time and takes the best of it. The new package java.timecontains all classes for date, time, date/time, timezones, durations, and time manipulation.. The design of the API takes immutability very seriously: changes are not allowed (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 the timezone. 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 that we will consider are LocaleDateand LocalTime. LocaleDatecontains only the date part without the timezone in the ISO-8601 calendar system. Accordingly, LocalTimeit contains only part of the time code>.
// получить местную 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 );
Console output example:
2014-04-12
2014-04-12
11:25:54.568
15:25:54.568
LocalDateTimejoins together LocaleDateand LocalTimeand contains the date and time, but without the time zone in the ISO-8601 calendar system. A simple example is shown 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 );
Console output example:
2014-04-12T11:37:52.309
2014-04-12T15:37:52.309
In case you need a date/time for a particular 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 );
Console output example:
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 Duration: time span in seconds and nanoseconds. This makes the calculation between two dates very easy. Let's see how to do it:
// Получаем разницу между двумя датами
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 above example calculates the duration (in days and hours) between two dates, April 16, 2014 and April 16, 2015 . Here is an example output to the console:
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 reviewed and the voices of the developers were heard. See the official documentation for details .

5.4. Nashorn JavaScript engine

Java 8 ships with the new Nashorn JavaScript engine , which allows you to develop and run certain kinds of JavaScript applications on the JVM. The Nashorn JavaScript engine is just another implementation of javax.script.ScriptEngine that follows the same set of rules to allow Java and JavaScript to interact. Here is 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;" ) );
Console output example:
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's very easy to use, an 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 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 processing of arrays. Perhaps the most important of these is parallelSort(), which can greatly speed up sorting on multi-core machines. The small example below shows the new method family ( 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 little piece of code uses a method parallelSetAll()to populate an array with 20000 random values. After that it is applied parallelSort(). The program prints the first 10 elements before and after sorting to show that the array is indeed sorted. An example output from the program might look like this (Note that the array elements 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 might be considered a better alternative for the not so good java.util.concurrent.locks.ReadWriteLock). New classes that have been added to the package java.util.concurrent.atomic:
  • Double Accumulator
  • Double Adder
  • long accumulator
  • Long Adder

6. New Features in the Java Runtime Environment (JVM)

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

7. Conclusion

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

8. Sources

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