Friday, June 24, 2022

Bruce Eckel on Java modules, text blocks, and more

Bruce wraps up his series by examining Project Jigsaw, triple double quotes, try-with-resources, and NullPointerException.

Modules

Before JDK 9, a Java program required the entire Java library. This meant that the simplest program carried an enormous amount of library code that was never used. If you used component A, there was no language support to tell the compiler what other components A depended upon. Without that information, the only thing the compiler could do is include the entire Java library.

Java Modules, Core Java, Oracle Java Tutorial and Materials, Oracle Java Preparation, Oracle Java Career, Java Skills, Java Jobs, Java Certification

There’s a second and more important problem. Although package access appears to effectively hide a class from use outside that package, it can be circumvented using reflection. For many years, some Java programmers have accessed low-level Java library components that were never meant for direct use, thus tying their code to those hidden components. This meant the Java library designers were unable to modify those components without breaking user code, which greatly hindered improvements to the Java library.

To solve this second problem, library components need the option to be completely unavailable to outside programmers.

JDK 9 finalized the introduction of modules, which solve both these problems. Using this system, Java library designers can now cleanly divide code into modules that programmatically specify every module they depend on, and they can define which components are exported and which components are completely unavailable.

JDK 9’s Project Jigsaw split the JDK library into about a hundred platform modules. Nowadays, when you use a library component, you only get that component’s module and its dependencies—and you won’t get modules you don’t use.

To continue using hidden library components, you must explicitly enable an escape hatch, which makes it clear that you are violating the intended design of the library by doing so, and you are thus responsible for any breakage that might occur in the future due to any updates to that hidden component (or even due to it being removed entirely).

Exploring modules. You can explore the new module system using some new command-line flags. To display all available modules, run the following at the command prompt:

java --list-modules

That command produces output like the following:

java.base@11

java.compiler@11

java.datatransfer@11

java.desktop@11

java.instrument@11

java.logging@11

java.management@11

java.management.rmi@11

java.naming@11

java.net.http@11

...

The @11 is for information only and indicates the version of the JDK being used. It is not included when you refer to the module. To see the contents of a module, for example, the base module, run this at the command prompt.

java --describe-module java.base

You’ll see the following:

java.base@11

exports java.io

exports java.lang

exports java.lang.annotation

exports java.lang.invoke

exports java.lang.module

exports java.lang.ref

exports java.lang.reflect

exports java.math

exports java.net

exports java.net.spi

exports java.nio

...

uses java.text.spi.DateFormatSymbolsProvider

uses sun.util.locale.provider.LocaleDataMetaInfo

uses java.time.chrono.Chronology

uses java.nio.channels.spi.AsynchronousChannelProvider

uses sun.text.spi.JavaTimeDateTimePatternProvider

...

provides java.nio.file.spi.FileSystemProvider with

  jdk.internal.jrtfs.JrtFileSystemProvider

...

qualified exports sun.security.timestamp to jdk.jartool

qualified exports sun.security.validator to jdk.jartool

qualified exports jdk.internal.org.xml.sax to jdk.jfr

qualified exports sun.security.provider.certpath to java.naming

qualified exports sun.security.tools to jdk.jartool

...

contains sun.text

contains sun.text.bidi

contains sun.text.normalizer

contains sun.text.resources

contains sun.text.resources.cldr

contains sun.text.spi

contains sun.util

contains sun.util.calendar

contains sun.util.locale

contains sun.util.resources.cldr

contains sun.util.spi

This gives you an idea of the kind of component structure that modules enable for the Java library.

Should you use modules for your own application? It is certainly possible to use the module system for your own applications, but it seems that for most projects the benefits do not outweigh the effort. You can continue to write applications without using modules and simply benefit from the modular standard Java library.

If you are writing a large and complex library, however, you may want to invest effort to learn how to implement your library using the module system. For anything except large libraries, however, building without defining and using your own modules should be perfectly adequate.

Text blocks

With JEP 378, JDK 15 finalized the addition of text blocks, which allow you to easily create multiline text. Triple double quotes denote a block of text including newlines.

// strings/TextBlocks.java

// {NewFeature} Since JDK 15

// Poem: Antigonish by Hughes Mearns

public class TextBlocks {

  public static final String OLD =

    "Yesterday, upon the stair,\n" +

    "I met a man who wasn't there\n" +

    "He wasn't there again today\n" +

    "I wish, I wish he'd go away...\n" +

    "\n" +

    "When I came home last night at three\n" +

    "The man was waiting there for me\n" +

    "But when I looked around the hall\n" +

    "I couldn't see him there at all!\n";

  public static final String NEW = """

    Yesterday, upon the stair,

    I met a man who wasn't there

    He wasn't there again today

    I wish, I wish he'd go away...


    When I came home last night at three

    The man was waiting there for me

    But when I looked around the hall

    I couldn't see him there at all!

    """;

  public static void main(String[] args) {

    System.out.println(OLD.equals(NEW));

  }

}

/* Output:

true

*/

(The {NewFeature} comment tag excludes this example from the Gradle build that uses JDK 8.)

OLD shows the traditional way of handling multiple lines, with lots of \n newline characters and + signs. NEW eliminates these with text blocks to produce much nicer and more readable syntax.

Notice that the newline after the opening triple double quote (""") is automatically removed, and the common indentation in the block is stripped off, so the result of NEW has no indentation. If you want to preserve indentation, move the final """ to the left to produce the desired indent, as follows:

// strings/Indentation.java

// {NewFeature} Since JDK 15

public class Indentation {

  public static final String NONE = """

          XXX

          YYY

          """; // No indentation

  public static final String TWO = """

          XXX

          YYY

        """;   // Produces indent of 2

  public static final String EIGHT = """

          XXX

          YYY

  """;         // Produces indent of 8

  public static void main(String[] args) {

    System.out.print(NONE);

    System.out.print(TWO);

    System.out.print(EIGHT);

  }

}

/* Output:

XXX

YYY

  XXX

  YYY

        XXX

        YYY

*/

To support text blocks, a new formatted() method was added to the String class.

// strings/DataPoint.java

// {NewFeature} Since JDK 15: formatted()

// Since JDK 16: record

record DataPoint(String location, Double temperature) {

  @Override public String toString() {

    return """

    Location: %s

    Temperature: %.2f

    """.formatted(location, temperature);

  }

  public static void main(String[] args) {

    var hill = new DataPoint("Hill", 45.2);

    var dale = new DataPoint("Dale", 65.2);

    System.out.print(hill);

    System.out.print(dale);

  }

}

/* Output:

Location: Hill

Temperature: 45.20

Location: Dale

Temperature: 65.20

*/

Note that formatted() is a method and not a separate static function like String.format(). This makes it nicer and cleaner to use for any String, because you can just add it to the end of that String. The result of a text block is a regular String, so you can do anything with it that you can do with any other String.

Better NullPointerException reporting

One frustrating issue with NullPointerException errors used to be the lack of information produced. The messages were made more informative in JDK 14 with JEP 358, but the helpful messages weren’t enabled by default. In JDK 15, JDK-8233014 turned on those helpful messages as the default.

In the following example, nulls are inserted into a chain of objects, causing the error:

// exceptions/BetterNullPointerReports.java

// {NewFeature} Since JDK 15

// Since JDK 16: record

record A(String s) {}

record B(A a) {}

record C(B b) {}

public class BetterNullPointerReports {

  public static void main(String[] args) {

    C[] ca = {

      new C(new B(new A(null))),

      new C(new B(null)),

      new C(null),

    };

    for(C c: ca) {

      try {

        System.out.println(c.b().a().s());

      } catch(NullPointerException npe) {

        System.out.println(npe);

      }

    }

  }

}

Before this improvement, similar code (replacing the record references with their more verbose class counterparts) produces very little information.

null

java.lang.NullPointerException

java.lang.NullPointerException

However, if you use JDK 15 and beyond, you see the following:

null

java.lang.NullPointerException: Cannot invoke "A.s()" because the return value of "B.a()" is null

java.lang.NullPointerException: Cannot invoke "B.a()" because the return value of "C.b()" is null

This change makes it significantly easier to understand and resolve NullPointerException errors.

Effectively final variables in try-with-resources

The original try-with-resources requires that all managed variables be defined within the resource specification header (the parenthesized list after try). For some reason, this was considered by the Java team to be, at times, somewhat awkward. JDK 9 added the ability to define these variables before the try, if those variables are either explicitly or effectively final. Here’s a comparison of the old try-with-resources syntax to the (optional) JDK 9 syntax.

// exceptions/EffectivelyFinalTWR.java

// {NewFeature} Since JDK 9

import java.io.*;

public class EffectivelyFinalTWR {

  static void old() {

    try (

      InputStream r1 = new FileInputStream(

        new File("TryWithResources.java"));

      InputStream r2 = new FileInputStream(

        new File("EffectivelyFinalTWR.java"));

    ) {

      r1.read();

      r2.read();

    } catch(IOException e) {

      // Handle exceptions

    }

  }

  static void jdk9() throws IOException {

    final InputStream r1 = new FileInputStream(

      new File("TryWithResources.java"));

    // Effectively final:

    InputStream r2 = new FileInputStream(

      new File("EffectivelyFinalTWR.java"));

    try (r1; r2) {

      r1.read();

      r2.read();

    }

    // r1 and r2 are still in scope. Accessing

    // either one throws an exception:

    r1.read();

    r2.read();

  }

  public static void main(String[] args) {

    old();

    try {

      jdk9();

    } catch(IOException e) {

      System.out.println(e);

    }

  }

}

/* Output:

java.io.IOException: Stream Closed

*/

The jdk9() passes exceptions out by specifying throws IOException. This is because the definitions of r1 and r2 are no longer inside a try since they are in old(). The inability to catch exceptions is one reason the feature needed improvement.

It’s also possible to reference variables after they have been released by try-with-resources, as you can see at the end of jdk9(). The compiler allows this, but you will get an exception when you access r1 or r2 outside the try block.

Source: oracle.com

Related Posts

0 comments:

Post a Comment