Book Image

Java Coding Problems - Second Edition

By : Anghel Leonard
Book Image

Java Coding Problems - Second Edition

By: Anghel Leonard

Overview of this book

The super-fast evolution of the JDK between versions 12 and 21 has made the learning curve of modern Java steeper, and increased the time needed to learn it. This book will make your learning journey quicker and increase your willingness to try Java’s new features by explaining the correct practices and decisions related to complexity, performance, readability, and more. Java Coding Problems takes you through Java’s latest features but doesn’t always advocate the use of new solutions — instead, it focuses on revealing the trade-offs involved in deciding what the best solution is for a certain problem. There are more than two hundred brand new and carefully selected problems in this second edition, chosen to highlight and cover the core everyday challenges of a Java programmer. Apart from providing a comprehensive compendium of problem solutions based on real-world examples, this book will also give you the confidence to answer questions relating to matching particular streams and methods to various problems. By the end of this book you will have gained a strong understanding of Java’s new features and have the confidence to develop and choose the right solutions to your problems.
Table of Contents (16 chapters)
1
Text Blocks, Locales, Numbers, and Math
Free Chapter
2
Objects, Immutability, Switch Expressions, and Pattern Matching
14
Other Books You May Enjoy
15
Index

45. Exemplify the initialization-on-demand holder design pattern

Before we tackle the solution of implementing the initialization-on-demand holder design pattern, let’s quickly recap a few ingredients of this solution.

Static vs. non-static blocks

In Java, we can have initialization non-static blocks and static blocks. An initialization non-static block (or simply, a non-static block) is automatically called every single time we instantiate the class. On the other hand, an initialization static block (or simply, a static block) is called a single time when the class itself is initialized. No matter how many subsequent instances of that class we create, the static block will never get executed again. In code lines:

public class A {
  {
    System.out.println("Non-static initializer ...");
  }
  static {
    System.out.println("Static initializer ...");
  }
}

Next, let’s run the following test code to create three instances of A:

A a1 = new A();
A a2 = new A();
A a3 = new A();

The output reveals that the static initializer is called only once, while the non-static initializer is called three times:

Static initializer ...
Non-static initializer ...
Non-static initializer ...
Non-static initializer ...

Moreover, the static initializer is called before the non-static one. Next, let’s talk about nested classes.

Nested classes

Let’s look at a quick example:

public class A {
    private static class B { ... }
}

Nested classes can be static or non-static. A non-static nested class is referred to as an inner class; further, it can be a local inner class (declared in a method) or an anonymous inner class (class with no name). On the other hand, a nested class that is declared static is referred to as a static nested class. The following figure clarifies these statements:

Figure 2.28.png

Figure 2.28: Java nested classes

Since B is a static class declared in A, we say that B is a static nested class.

Tackling the initialization-on-demand holder design pattern

The initialization-on-demand holder design pattern refers to a thread-safe lazy-loaded singleton (single instance) implementation. Before JDK 16, we can exemplify this design pattern in code as follows (we want a single thread-safe instance of Connection):

public class Connection { // singleton
  private Connection() {
  }
  private static class LazyConnection { // holder
    static final Connection INSTANCE = new Connection();
    static {
      System.out.println("Initializing connection ..." 
        + INSTANCE);
    }
  }
  public static Connection get() {
    return LazyConnection.INSTANCE;
  }
}

No matter how many times a thread (multiple threads) calls Connection.get(), we always get the same instance of Connection. This is the instance created when we called get() for the first time (first thread), and Java has initialized the LazyConnection class and its statics. In other words, if we never call get(), then the LazyConnection class and its statics are never initialized (this is why we name it lazy initialization). And, this is thread-safe because static initializers can be constructed (here, INSTANCE) and referenced without explicit synchronization since they are run before any thread can use the class (here, LazyConnection).

JDK 16+

Until JDK 16, an inner class could contain static members as constant variables but it couldn’t contain static initializers. In other words, the following code would not compile because of the static initializer:

public class A {
  public class B {
    {
      System.out.println("Non-static initializer ...");
    }
    static {
      System.out.println("Static initializer ...");
    }
  }
}

But, starting with JDK 16, the previous code is compiled without issues. In other words, starting with JDK 16, Java inner classes can have static members and static initializers.

This allows us to tackle the initialization-on-demand holder design pattern from another angle. We can replace the static nested class, LazyConnection, with a local inner class as follows:

public class Connection { // singleton
  private Connection() {
  }
  public static Connection get() {
    class LazyConnection { // holder
      static final Connection INSTANCE = new Connection();
      static {
        System.out.println("Initializing connection ..." 
          + INSTANCE);
      }
    }
    return LazyConnection.INSTANCE;
  }
}

Now, the LazyConnection is visible only in its containing method, get(). As long as we don’t call the get() method, the connection will not be initialized.