Java 9 - Notes

What's New in Java 9

Posted by Tomy Jaya on November 19, 2017

Source:

The following notes are extracted from a Pluralsight Course: What’s New in Java 9 which gives a good overview of the interesting features of Java 9.


1. Java Platform Module System

1.1. Key Concepts

  • Module provides another layer of encapsulation, interface definition, dependency management within a single jar file or a bunch or jars within a classpath
  • Even the core java (rt.jar) itself is modularized now (e.g. it by default only has java.base, it doesn’t have java.xml).
  • JavaDoc now shows which module a class belongs to.
  • You might encounter issues if you use non-default modules (e.g. ones ship as part java.se.ee). Solution: add --add-modules flag when compiling & running

1.2. Sample module descriptors

  • module-info.java in project two
module two {
    exports com.example.person; //defines what to export
}
  • module-info.java in project one
module one {
    requires java.logging; // from standard library
    requires two; // an existing predefined module
}

NOTE:

  1. You’ll only be able to access publicly exported classes in the defined packages.
  2. In IntelliJ IDEA, you need to explicitly define the module dependencies in the Project Settings - Modules on top of Java 9’s module definitions. The two module systems are independent of each other. Refer to the paragraph towards the end in this jetbrains blogpost. E.g.

    intellij-module-dependencies

1.3. Useful commands

# list all JDK modules
java --list-modules 
# display the module info
java --describe-module java.sql

# using non-default modules
# compile
javac --add-modules java.xml.bind Main.java
# run
java --add-module java.xml.bind Main

# jdeps can be used to see all package level dependencies:
jdeps -jdkinternals Main.class
jdeps --module-path outDir --module main.app

NOTE: At runtime, Java 9 is backward compatible. That means, even if you use private encapsulated types (e.g. X500Name) in your jdk 8 codes, that jar will still run in Java 9, but with a warning. If you want to deny such access, you can use --illegal-access=deny flag which will be the default in future release.

However, at compile time, uses of private encapsulated types will throw an error. You can patch by using the new javac’s --add-exports flag.

1.4. Transitivity

Dependency of module is not transitive by default. You need requires transitive for transitive dependencies. E.g.:

module java.sql {
  // ...
  requires transitive java.logging
  requires transitive java.xml 
}

Users of java.sql will automatically get java.logging and java.xml.

You can also use requires transitive to create an aggregator module.

1.5. Qualified exports

You can do qualified exports to expose some package to a limited package. E.g. when you use javafx, LauncherImpl uses reflection to access your defined class. To fix, you can add the below:

exports javamodularity.easytext.gui to javafx.graphics

1.6. Services

  • Supports the notion of “depending on interfaces instead of implementations”.
  • Similar to Service Discovery & Registration concept in microservices, Dependency Injection in Spring and the old java JNDI.

API Interface definition:

module myApi {
   exports com.api;
}
interface MyService {
  void doWork();
}

Consumer of the API:

module myConsumer {
   requires myApi;
   uses com.api.MyService;
}

Implementor/ Provider of the API interface:

module myProvider {
   requires myApi;
   provides com.api.MyService
       with myProvider.MyServiceImpl;
}

Using ServiceLoader to retrieve all available implementations:

ServiceLoader<MyService> services = ServiceLoader.load(MyService.class);
for (MyService svc: services) {
  svc.doWork();
}
  • Linking creates an optimized custom runtime image with low footprint (e.g. don’t need the entire rt.jar).
# create the image
jlink --module-path <modulepath> --add-modules <modules> --limit-modules <modules> --output <path>

# then run the image 
image/bin/easytext.cli test.txt

2. Jshell

  • Auto adds semicolon (“;”)
  • all commands starts with slash (“/”). E.g.
/exit
/help

/vars      // show all declared vars
/imports   // show all declared imports
/methods   // show all declared methods
/types     // show all declared types

/save mysession.jsh   // save session
/open mysession.jsh   // open existing session
/open Person.java     // import java file
  • If you want to add a library with jshell, use the below:
jshell --class-path commons-lang-3-3-5.jar

3. Collection Factory Methods

  • The generated collection is immutable.
  • Sample:
List<Integer> ints = List.of(1,2,3);

Set.of("first", "second");
// cannot put duplicate
// cannot put null

Map.of("Key1",1,"Key2",2);
// alternate key and value

Map.ofEntries(Map.entry("Key1", true), Map.entry("Key2",false));
// cannot put duplicate key

4. New Stream Methods

  • takeWhile: collect the item in the stream while the predicate is true.
Stream.of("a", "b", "c", "de", "f", "g", "h")
                .takeWhile(s -> s.length() <= 1)
                .collect(Collectors.toList()); // [a, b, c]
  • dropWhile: skip the item the stream while the predicate is true.
Stream.of("a", "b", "c", "de", "f", "g", "h")
                .dropWhile(s -> s.length() <= 1)
                .collect(Collectors.toList()); // [de, f, g, h]
  • ofNullable: conveniently transform possibly null value to Stream. Handy while working with old APIs which can return null.
long one = Stream.ofNullable("42").count(); // 1
long zero = Stream.ofNullable(null).count(); // 0
  • iterate: now overloaded with a way to create a finite stream
// Java 8
Stream.iterate(0, i -> i + 1)
  .forEach(System.out::println); //  1 2 3 4 5

// Java 9
Stream.iterate(0, i -> i < 5, i -> i + 1)
  .forEach(System.out::println); // 1 2 3 4 

5. New Optional Methods

  • Optional.ifPresentOrElse: nothing fancy, works as named
// before
if (a.isPresent()) {
  System.out.println(a.get());
} else {
  System.out.println("Nothing");
}

// now 
a.ifPresentOrElse(System.out::println, () -> System.out.println("Nothing"));
  • Optional.stream: helps improve interoperability between Streams and Optionals. Use it with flatMap.
Stream<Optional<Integer>> optionals = Stream.of(Optional.of(1), Optional.empty(), Optional.of(2));
Stream<Integer> ints = optionals.flatMap(Optional::stream);
ints.forEach(System.out::println); // 1 2
  • Optional.or: Alternative way to defaulting empty case without unboxing the Optional container.
public static void main(String... args) {
    Optional<Book> localFallback = Optional.of(Book.getBook());

    // Before Optional.or
    Book bestBookBefore = getBestOffer()
            .orElse(getExternalOffer().orElse(localFallback.get()));  // .get() is BAD!

    Optional<Book> bestBook =
            getBestOffer()
            .or(() -> getExternalOffer())
            .or(() -> localFallback);
    System.out.println(bestBook);
}

static Optional<Book> getBestOffer() {
    return Optional.empty();
}

static Optional<Book> getExternalOffer() {
    return Optional.of(new Book("External Book", Set.of(), 11.99));
}

6. Private Method in Interfaces

public interface PricedObject {
    // Private fields are not allowed
    // private double TAX = 1.21;

    double getPrice();

    /* Before private interface methods, shared logic could not be extracted into a
       new method (at least not without it becoming part of the public API).
    
    [getPrice() * 1.21] is duplicated below.

    default double getPriceWithTax() {
        return getPrice() * 1.21;
    }

    default double getOfferPrice(double discount) {
        return getPrice() * 1.21 * discount;
    }
    */

    default double getPriceWithTax() {
       return getTaxedPriceInternal();
    }

    default double getOfferPrice(double discount) {
        return getTaxedPriceInternal() * discount;
    }

    // This is now possible in Java 9
    private double getTaxedPriceInternal() {
        return getPrice() * getTax();
    }

    private static double getTax() {
        return 1.21;
    }

}

7. Process APIs

// get current process's PID
long pid = ProcessHandle.current().pid(); 


// get the process by PID
Optional<ProcessHandle> ph = ProcessHandle.of(123); // 123 is the PID

// get parent
Optiional<ProcessHandle> parentsPh = ph.flatMap(p -> p.parent()); 

// get info
ph.map(ProcessHandle::info).ifPresent(System.out::println); 
// [
//   user: Optional[****], 
//   cmd: /Library/Java/JavaVirtualMachines/jdk-9.0.1.jdk/Contents/Home/bin/jshell, 
//   startTime: Optional[2017-11-21T15:02:31.672Z]
// ]

// kill process
ph.ifPresent(p -> p.destroy());