Reader small image

You're reading from  Java Coding Problems - Second Edition

Product typeBook
Published inMar 2024
PublisherPackt
ISBN-139781837633944
Edition2nd Edition
Right arrow
Author (1)
Anghel Leonard
Anghel Leonard
author image
Anghel Leonard

Anghel Leonard is a Chief Technology Strategist and independent consultant with 20+ years of experience in the Java ecosystem. In daily work, he is focused on architecting and developing Java distributed applications that empower robust architectures, clean code, and high-performance. Also passionate about coaching, mentoring and technical leadership. He is the author of several books, videos and dozens of articles related to Java technologies.
Read more about Anghel Leonard

Right arrow

Sealed and Hidden Classes

This chapter includes 13 problems covering sealed and hidden classes. The first 11 recipes will cover sealed classes, a very cool feature introduced in JDK 17 (JEP 409) to sustain closed hierarchies. The last two problems cover hidden classes, a JDK 15 (JEP 371) feature that allows frameworks to create and use runtime (dynamic) classes hidden in the JVM’s internal linkages of bytecode, and to the explicit usage of class loaders.

You’ll be skilled in both topics by the end of this chapter.

Problems

Use the following problems to test your programming prowess in manipulating sealed classes and hidden classes in Java. I strongly encourage you to give each problem a try before you turn to the solutions and download the example programs:

  1. Creating an electrical panel (hierarchy of classes): Write the stub of a Java application that shapes an electrical panel. You can assume that the electrical panel is made of several types of electrical components (for instance, resistors, transistors, and so on), and electrical circuits (for instance, parallel circuits, series circuits, and so on).
  2. Closing the electrical panel before JDK 17: Use the Java features (for instance, the final keyword and package-private hacks) to close this hierarchy (close to extension).
  3. Introducing JDK 17 sealed classes: Provide a brief introduction to JDK 17 sealed classes. Exemplify how to write closed hierarchies in a single source file via sealed classes.
  4. Introducing the permits...

172. Creating an electrical panel (hierarchy of classes)

Let’s assume that we want to model in code lines an electrical panel. Of course, we are not electricians, so for our purposes, an electric panel means a box with some internal circuits made of electrical components and a breaker that turns on/off the electrical panel.

Figure 8.1.png

Figure 8.1: Electrical panel components

Everything in an electrical panel can be considered an electrical component, so we can start our code by defining an interface that must be implemented by everything in this panel:

public interface ElectricComponent {}

Before continuing, let’s look at a diagram of the electric panel interfaces and classes that will help you to follow what comes after more easily:

Figure 8.2.png

Figure 8.2: A model of the electrical panel

An electrical panel consists of more electrical circuits that interact (or do not interact) with each other. We can represent such a circuit via an abstract class as follows...

173. Closing the electrical panel before JDK 17

By its nature, an electrical panel is a closed unit of work. But our code from the previous problem is far from being a closed hierarchy. We can extend and implement almost any class/interface from inside or outside the hierarchy.

Using anything before JDK 17, closing a hierarchy of classes and interfaces can be done using several tools.

Applying the final modifier

For instance, we have the powerful final modifier. Once we declare a class as final, it cannot be extended, so it is completely closed to extension. Obviously, we cannot apply this technique consistently across a hierarchical model because it will lead to a non-hierarchical model.

If we scan our electrical panel model, then we can use the final modifier in several places. First, we eliminate interfaces (ElectricComponent and ElectricBreaker) since interfaces cannot be declared as final. Next, we can look at the ElectricCircuit class and its subclasses (ParallelCircuit...

174. Introducing JDK 17 sealed classes

Among the cool features of JDK 17, we have JEP 409 (sealed classes). This JEP provides an explicit, intuitive, crystal-clear solution for nominating who will extend a class/interface or will implement an interface. In other words, sealed classes can control inheritance at a finer level. Sealed classes can affect classes, abstract classes, and interfaces and sustain the readability of the code – you have an easy and expressive solution to tell your colleagues who can extend/implement your code.

Figure 8.3.png

Figure 8.3: JDK 17, JEP 409

Via sealed classes, we have finer control over a hierarchy of classes. As you can see from the previous figure, sealed classes are the missing piece of the puzzle sitting between final and package-private. In other words, sealed classes provide a granularity that we cannot obtain via the final modifier and package-private access.

Important note

Sealed classes don’t affect the semantics...

175. Introducing the permits clause

In the previous problem, you saw how to write a closed hierarchical model in a single source file. Next, let’s use the Fuel.java source file to rewrite this model by using separate sources and separate packages.

Working with sealed classes in separate sources (same package)

Let’s consider the sealed Fuel interface from Fuel.java in package com.refinery.fuel:

public sealed interface Fuel {}   // Fuel.java

We know that this interface is extended by three other interfaces: SolidFuel, LiquidFuel, and SolidFuel. Let’s define SolidFuel in the SolidFuel.java source (same package), as follows:

public sealed interface SolidFuel {} // SolidFuel.java

As you’ll see, this code will not compile (it is like the compiler is asking: hey, what’s the point of a sealed interface without any implementation/extension?). This time, we have to explicitly nominate the interfaces that can extend/implement the Fuel...

176. Closing the electrical panel after JDK 17

Do you remember our electrical panel model introduced earlier in Problems 172 and 173? In Problem 173, we closed this model as much as possible by using the Java capabilities available before JDK 17. Now, we can revisit that model (Problem 173) and close it completely via JDK 17 sealed classes.

We start with the ElectricComponent interface, which is declared as follows:

public interface ElectricComponent {}

At this moment, this interface is not closed. It can be extended/implemented from any other point of the application. But we can close it by transforming it into a sealed interface with the proper permits clause, as follows:

public sealed interface ElectricComponent
  permits ElectricCircuit, ElectricBreaker, 
          Capacitor, Resistor, Transistor {}

Next, let’s focus on the semi-closed ElectricCircuit class. This is an abstract class that uses a package-private constructor to block any extension from...

177. Combining sealed classes and records

As you know from Chapter 4, Java records are final classes that cannot be extended and cannot extend other classes. This means that records and sealed classes/interfaces can team up to obtain a closed hierarchy.

For instance, in the following figure, we can identify the classes that can be good candidates to become Java records in the Fuel model:

Figure 8.6.png

Figure 8.6: Identify classes that can become Java records

As you can see, we have four classes that can become Java records: Coke, Charcoal, Hydrogen, and Propane. Technically speaking, these classes can be Java records since they are final classes and don’t extend other classes:

public record Coke() implements SolidFuel {}
public record Charcoal() implements SolidFuel {}
public record Hydrogen() implements NaturalGas {}
public record Propane() implements GaseousFuel {}

Of course, the technical aspect is important but it is not enough. In other words, you don’...

178. Hooking sealed classes and instanceof

Sealed classes influence how the compiler understands the instanceof operator and, implicitly, how it performs internal cast and conversion operations.

Let’s consider the following snippet of code:

public interface Quadrilateral {}
public class Triangle {}

So, we have here an interface (Quadrilateral) and a class that doesn’t implement this interface. In this context, does the following code compile?

public static void drawTriangle(Triangle t) {
  if (t instanceof Quadrilateral) {
    System.out.println("This is not a triangle");
  } else {
    System.out.println("Drawing a triangle");
  }
}

We wrote if (t instanceof Quadrilateral) {…} but we know that Triangle doesn’t implement Quadrilateral, so at first sight, we may think that the compiler will complain about this. But, actually, the code compiles because, at runtime, we may have a class Rectangle that extends Triangle...

179. Hooking sealed classes in switch

This is not the first time in this book that we’ve presented an example of sealed classes and switch expressions. In Chapter 2, Problem 66, we briefly introduced such an example via the sealed Player interface with the goal of covering completeness (type coverage) in pattern labels for switch.

If, at that time, you found this example confusing, I’m pretty sure that now it is clear. However, let’s keep things fresh and see another example starting from this abstract base class:

public abstract class TextConverter {}

And, we have three converters available, as follows:

final public class Utf8 extends TextConverter {}
final public class Utf16 extends TextConverter {}
final public class Utf32 extends TextConverter {}

Now, we can write a switch expression to match these TextConverter instances, as follows:

public static String convert(
  TextConverter converter, String text) { 
  return switch (converter...

180. Reinterpreting the Visitor pattern via sealed classes and type pattern matching for switch

The Visitor pattern is part of the Gang of Four (GoF) design patterns and its goal is to define a new operation on certain classes without the need to modify those classes. You can find many excellent resources on this topic on the Internet, so for the classical implementation, we will provide here only the class diagram of our example, while the code is available on GitHub:

Figure 8.7.png

Figure 8.7: Visitor pattern class diagram (use case)

In a nutshell, we have a bunch of classes (Capacitor, Transistor, Resistor, and ElectricCircuit) that are used to create electrical circuits. Our operation is shaped in XmlExportVisitor (an implementation of ElectricComponentVisitor) and consists of printing an XML document containing the electrical circuit specifications and parameters.

Before continuing, consider getting familiar with the traditional implementation and output of this example available...

181. Getting info about sealed classes (using reflection)

We can inspect sealed classes via two methods added as part of the Java Reflection API. First, we have isSealed(), which is a flag method useful to check if a class is or isn’t sealed. Second, we have getPermittedSubclasses(), which returns an array containing the permitted classes. Based on these two methods, we can write the following helper to return the permitted classes of a sealed class:

public static List<Class> permittedClasses(Class clazz) {
  if (clazz != null && clazz.isSealed()) {
    return Arrays.asList(clazz.getPermittedSubclasses());
  }
  return Collections.emptyList();
}

We can easily test our helper via the Fuel model as follows:

Coke coke = new Coke();
Methane methane = new Methane();
// [interface com.refinery.fuel.SolidFuel, 
//  interface com.refinery.fuel.LiquidFuel, 
//  interface com.refinery.fuel.GaseousFuel]           
System.out.println("Fuel subclasses: "...

182. Listing the top three benefits of sealed classes

Maybe you have your own top three sealed class benefits that don’t match the following list. That’s OK, they are still benefits after all!

  • Sealed classes sustain better design and clearly expose their intentions: Before using sealed classes, we have to rely only on the final keyword (which is expressive enough), and package-private classes/constructors. Obviously, package-private code requires some reading between the lines to understand its intention since it is not easy to spot a closed hierarchy modeled via this hack. On the other hand, sealed classes expose their intentions very clearly and expressively.
  • The compiler can rely on sealed classes to perform finer checks on our behalf: Nobody can sneak a class into a hierarchy closed via sealed classes. Any such attempt is rejected via a clear and meaningful message. The compiler is guarding for us and acts as the first line of defense against any...

183. Briefly introducing hidden classes

Hidden classes were introduced in JDK 15 under JEP 371. Their main goal is to be used by frameworks as dynamically generated classes. They are runtime-generated classes with a short lifespan that are used by frameworks via reflection.

Important note

Hidden classes cannot be used directly by bytecode or other classes. They are not created via a class loader. Basically, a hidden class has the class loader of the lookup class.

Among other characteristics of hidden classes, we should consider that:

  • They are not discoverable by the JVM internal linkage of bytecode or by the explicit usage of class loaders (they are invisible to methods such as Class.forName(), Lookup.findClass(), or ClassLoader.findLoadedClass()). They don’t appear in stack traces.
  • They extend Access Control Nest (ACN) with classes that cannot be discovered.
  • Frameworks can define hidden classes, as many as needed, since they...

184. Creating a hidden class

Let’s assume that our hidden class is named InternalMath and is as simple, as follows:

public class InternalMath {
  public long sum(int[] nr) {
    return IntStream.of(nr).sum();
  }
}

As we mentioned in the previous problem, hidden classes have the same class loader as the lookup class, which can be obtained via MethodHandles.lookup(), as follows:

MethodHandles.Lookup lookup = MethodHandles.lookup();

Next, we must know that Lookup contains a method named defineHiddenClass(byte[] bytes, boolean initialize, ClassOption... options). The most important argument is represented by the array of bytes that contain the class data. The initialize argument is a flag specifying if the hidden class should be initialized or not, while the options argument can be NESTMATE (the created hidden class becomes a nestmate of the lookup class and has access to all the private members in the same nest) or STRONG (the created hidden class can be unloaded...

Summary

This chapter covered 13 problems. Most of them were focused on the sealed classes feature. The last two problems provided brief coverage of hidden classes.

Leave a review!

Enjoying this book? Help readers like you by leaving an Amazon review. Scan the QR code below for a 20% discount code.

*Limited Offer

lock icon
The rest of the chapter is locked
You have been reading a chapter from
Java Coding Problems - Second Edition
Published in: Mar 2024Publisher: PacktISBN-13: 9781837633944
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
undefined
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $15.99/month. Cancel anytime

Author (1)

author image
Anghel Leonard

Anghel Leonard is a Chief Technology Strategist and independent consultant with 20+ years of experience in the Java ecosystem. In daily work, he is focused on architecting and developing Java distributed applications that empower robust architectures, clean code, and high-performance. Also passionate about coaching, mentoring and technical leadership. He is the author of several books, videos and dozens of articles related to Java technologies.
Read more about Anghel Leonard