Tuesday, November 30, 2021

Quiz yourself: Functional interfaces and error handling in Java

Oracle Java, Oracle Java Exam Prep, Oracle Java Tutorial and Materials, Oracle Java Career, Oracle Java Jobs, Oracle Java Certification

Simon and Mikalai raise the question of how functional-style software should represent a recoverable error.

Download a PDF of this article

Imagine that you are developing a high-load message board application that will use nonblocking communication with a database. This communication will be handled using the following service class:

public class MsgBoardService {

  public int getCommentCount(int threadId) throws SQLException {

    ... // implementation logic here

  }

  ...   // more similar methods 

}

Which functional interface could be used to expose the MsgBoardService getCommentCount method? Choose one.

A. Function

B. Supplier

C. IntUnaryOperator

D. Callable

E. A custom interface

Answer. You have a method that takes a single argument of int type and returns an int. Let’s walk through each of the proposed functional interfaces to see how well they match up with these types.

The Function<A,R> interface declares a method that returns an object of type R and takes a single argument of type A. Of course, these generic arguments must be of reference type, but primitive values can be handled using autoboxing if you are willing to sacrifice a little efficiency.

The Supplier<R> interface declares a method that returns an object of type R but takes no arguments. That’s not a great fit for tasks. It is possible to write a function factory that would embed the necessary argument into a closure and then have that new function delegate to the getCommentCount method. This, however, is clearly a rather circuitous route and can’t really be considered a proper fit.

The IntUnaryOperator interface declares a method that returns a primitive int and accepts a primitive int as its argument. This looks promising as IntUnaryOperator is a perfect fit in terms of the argument and return types, but let’s not jump to conclusions.

The Callable<R> interface declares a method that takes no arguments and returns an object of type R. At this level of investigation, this appears to be the same situation as with Supplier—but why would two identical interfaces exist? Well, of course, they’re not identical. The Callable call() method is declared with throws Exception, whereas the Supplier, in keeping with all the interfaces of the java.util.function package, does not declare any throws clause.

This distinction highlights the observation that the getCommentCount method is declared as throws SQLException, which is a checked exception and, thus, regardless of the argument and return types, the Function, Supplier, and IntUnaryOperator interfaces are all unsuited to this question’s task because they cannot support the checked exception. Since Callable is also unsuited because it cannot take an argument, you must conclude that options A, B, C, and D are incorrect and that a custom interface is necessary. Therefore, option E is correct.

The custom interface could look like this.

public interface ExceptionIntUnaryOperator {

  int apply(int i) throws Exception;

}

There are some side notes worth discussing that arise from this question. Perhaps the most compelling is, “Since so many of Java’s core libraries report problems with checked exceptions, why do the standard functional interfaces in java.util.function not declare any checked exceptions?” There are two levels of answer.

◉ To be specific about a case, if an exception is thrown by a method that’s passed to a map method of a stream, the entire stream processing method will crash. That’s not a failure of the stream implementation. If you think about it, there’s no clean way to embed exception handling (which by nature must be specific to the application domain) into the general stream code.

◉ At a more fundamental level, in functional programming a pure function always returns a value of the expected type. If it doesn’t always return such a value, it’s called a partial function.

The only use for exceptions is to represent catastrophic errors either in the design or the implementation of the code. Such errors should result in a system shutting down, not in a recovery attempt. This is simply because if the system has done something that by the intended design should be impossible, it’s impossible to know how messed up everything might be, and you have no solid ground from which to attempt a recovery.

This observation then raises the question of how functional-style software should represent a recoverable error (such as a database being offline, which merely requires the plug to be reconnected to permit a retry). Generally, the returned value of a function that might fail will be an object (or tuple) that contains one of two values: It must either return the successful result or provide the reason for failure. Such a class is often called an Either; such a class exists in the Vavr functional library, for example.

If you don’t care about the reason for a failure, and the only reason for the absence of a result is failure, the Optional class in the core Java APIs could be used instead. In that case, you could put this system together using IntFunction<R>, which declares a method that takes a primitive int and returns an object of type R. The result could look like this.

IntFunction<OptionalInt> msgCommentCounter = threadId -> {

  try {

    return OptionalInt.of(getCommentCount(threadId));

  } catch(SQLException sqle) {

    return OptionalInt.empty();

  }

};

Conclusion. The correct answer is option E.

Source: oracle.com

Monday, November 29, 2021

Add a distributed database to your Java application with i.o.cluster

The interference open cluster is an open-source framework that allows you to build distributed applications with their own persistent storage.

Download a PDF of this article

The interference open cluster, or i.o.cluster, is a simple Java framework that lets you add a distributed database and complex event processing service within your Java application using an interface similar to the Java Persistence API (JPA) and uses annotations for structure mapping and data operations.

The name comes from the Interference Project; in this context, interference is a fundamental physical phenomenon in which two waves superpose to form a resultant wave of greater, lower, or the same amplitude.

The i.o.cluster project is an open source, pure Java framework. The basic unit of the i.o.cluster service is a node, which can be a standalone running service or a service running within a Java application.

Each i.o.cluster node has its own persistent storage and can be considered and used as a local database. It operates with plain old Java objects (POJOs). In addition, it uses base JPA annotations (such as @Entity, @Table, @Column, @Transient, @Index, and @GeneratedValue) for object mapping directly to persistent storage. It provides the following basic features:

◉ It supports transactions and SQL queries with READ COMMITTED isolation level.

◉ It uses persistent indices for fast access to data and to increase the performance of SQL joins.

◉ It allows flexible management of data in memory for the stable operation of a node at any ratio of storage size to available memory. Depending on the problem being solved, you can choose to allocate all data directly in memory with a sufficient heap size or use access to huge storage resources with a minimum heap size in the Java application.

Each of the nodes includes the following mechanisms; Figure 1 shows how they fit together.

◉ Core algorithms for structured persistent storage, indices, custom serialization, heap management, and local and distributed sync processes

◉ SQL and complex event processing (CEP) engine

◉ Event transport to exchange messages between nodes as well as between a node and a client application

Oracle Java, Oracle Java Exam Prep, Oracle Java Exam Preparation, Oracle Java Tutorial and Materials, Oracle Java Career, Oracle Java Certification

Figure 1. The parts of an i.o.cluster node

Nodes can be joined into a cluster, and you can insert data and run SQL queries from any node included in the cluster. SQL queries are scaled horizontally, and you can run transparent cluster-level transactions. Within a cluster, you can use both CEP and simple streaming SQL, and the i.o.cluster nodes do not require any additional coordinators.

Getting started with i.o.cluster


To get started with i.o.cluster, download the source code for the current release (which is 2021.1 as of late September 2021) from GitHub; then build the source code using Maven and install the JAR file into your local Maven repository. Then add the following dependency to your project’s pom.xml file. Note that the minimum requirements for the build are JDK 1.8 and Maven 3.

<dependencies>
    ...
    <dependency>
        <groupId>su.interference</groupId>
        <artifactId>interference</artifactId>
        <version>2021.1</version>
    </dependency>
    ...
</dependencies>

Before starting the application, create a text file with a name such as iocluster.properties that has properties similar to the example given in the source code’s config directory and in accordance with the description provided in the i.o.cluster documentation. Place the file in your project’s config directory. In the launch command line, specify the following parameter:

-Dsu.interference.config=iocluster.properties

Once this is done, launch the service anywhere in your code, as follows:

Instance instance = Instance.getInstance();
Session session = Session.getSession();
instance.startupInstance(session);

The i.o.cluster service implements a simple data management model, which is based on several standard JPA-like methods on the Session object and which can be obtained by calling the static method Session.getSession().

◉ persist() places an object of an @Entity-annotated class into storage.
◉ find() finds and returns an object from storage using a unique identifier.
◉ execute() executes a SQL query and returns a ResultSet object that contains a poll() method to retrieve the query execution results.
◉ commit() commits a transaction.
◉ rollback() rolls back a transaction.

More details about configuring your application for the launch of the i.o.cluster service are provided in the documentation attached to the source code and also on the developers’ website along with detailed examples.

By the way, the i.o.cluster software includes a remote client that provides the ability to remotely connect to any of the cluster nodes using internal event transport and then execute standard JPA-like methods to persist, find, execute, commit, and roll back transactions. Figure 2 shows how the remote client works.

Oracle Java, Oracle Java Exam Prep, Oracle Java Exam Preparation, Oracle Java Tutorial and Materials, Oracle Java Career, Oracle Java Certification
Figure 2. The remote client in the i.o.cluster service

The distributed persistent model


The i.o.cluster system is decentralized, meaning that the cluster does not use any coordination nodes. Instead, each node follows a set of formal rules of behavior that guarantees the integrity and availability of data within a certain interaction framework.

Within the framework of these rules, all nodes of the i.o.cluster are equivalent. There is no separation in the system between controller and replica nodes; changes to user tables can be made from any node. All changes are replicated to all nodes, regardless of which node initiated the change. In a transaction, running commit in a local user session automatically ensures that the changed data is visible on all nodes in the cluster.

To include a node in the cluster, specify the full list of cluster nodes in the cluster.nodes configuration parameter file. The minimum number of cluster nodes is 2, and the maximum is 64.

After the configuration is complete, you may start all configured nodes in any order. All nodes will use specific messages (events) for internode data consistency and horizontal-scaling queries.

These are the formal rules.

◉ All nodes in the cluster are equivalent.
◉ Changes made on any node are mapped to the other nodes immediately.
◉ If replication is not possible, a persistent change queue is created for the node. This might occur if a node is temporarily unavailable or if a connection is broken.
◉ The owner of any data frame is the node on which the frame has been allocated.
◉ The system generates unique identifiers for entities (using the @DistributedId annotation) so that each identifier is unique within the cluster.
◉ The system does not use any additional checks for uniqueness.
◉ Data inserts are performed in the local storage structure, after which the changes are replicated to the other nodes.

By the way, all SQL queries called on any of the cluster nodes will be automatically distributed among all cluster nodes for parallel processing, if the volume of tasks indicates this will be advantageous.

If some nodes become unavailable during the processing of a request (for example, if the network fails or the service stops), the tasks assigned to that node will be automatically rescheduled to another available node.

Complex event processing


The i.o.cluster service supports CEP using the SELECT STREAM clause in a SQL statement. A SELECT STREAM query can use any of the following three CEP modes:

◉ Events are processed as they arrive, without any aggregations.
◉ Events are aggregated by column value with the use of any group functions (tumbling window).
◉ Events are aggregated by a window of a certain size for every new record (sliding window).

The basic difference between a streaming query and a standard query is that with SELECT STREAM, the execute() method returns a StreamQueue object. The request is executed asynchronously until the StreamQueue.stop() method is called or until the application terminates.

With CEP, the StreamQueue.poll() method returns all records previously inserted into the table according to the WHERE condition (if one has been specified) and continues to return newly added records. Each StreamQueue.poll() method always returns the next record after the last polled position within the session. This means that if the SQL request is stopped and called again within the same session, the data retrieved will be continued from the last fixed position in the table. However, if there’s a new session, the data will be retrieved from the beginning of the table.

Note that unlike a normal SQL series, a streaming request does not support transactions and always returns actually inserted rows, regardless of whether the commit() method is used in a session inserting data. This could result in dirty reads, that is, uncommitted rows from another transaction.

All this means that when your code uses the SELECT STREAM clause, you can retrieve records in exactly the same order in which they were added. In general, at the cluster level, the session.persist() operation can be considered as publishing a persistent event. Based on i.o.cluster’s distribution rules, this event is sent to all the nodes.

Source: oracle.com

Friday, November 26, 2021

Java is criminally underhyped

Recent computer science graduate Jackson Roberts never took a single class in Java. That’s just wrong, he says.

Download a PDF of this article

It’s likely that you read the title of this post and thought, “What is this guy smoking? Java is everywhere!” You’re correct. Java still dominates enterprises and runs some of the world’s largest mission-critical applications.

But Java’s adoption isn’t what I’m talking about. I’m talking about its hype. I spend a lot of time around inexperienced programmers. What do inexperienced programmers love doing? Getting excited and opinionated about tools such as programming languages. None of the computer science undergrads I meet are hyped about Java, but they should be.

Young and naive developers (myself included) often fall into the trap of fetishizing new languages and tools at the expense of productivity and sanity. Prior to working at Halp (now owned by Atlassian), I had a nearly romantic relationship with back-end TypeScript. I thought the Node.js ecosystem was the coolest thing ever: I loved the idea of transpiled code, live debugging, the massive package library, and even the weird and fragmented build systems. However, when I used it in production and spoke to more-experienced engineers, the magic quickly faded away.

I had an irrational affinity towards the JavaScript ecosystem because it was the hot thing; it had hype. Reality did not live up to my expectations. Today, the wonderful things I expected from JavaScript I am currently enjoying as I gain experience in Java. I feel betrayed that hype did not lead me to Java sooner. Java is fun to write and productive, and it gets an unfair reputation among new developers as a dinosaur.

Ergonomics are what make Java great


This cannot be understated: Java simply feels good to write. A lot of this is due to the craftsmanship JetBrains puts into IntelliJ IDEA. Everything is autocompleted, jump-to-definition is fast, find-usage works well, and refactoring is easy. However, where Java truly shines is the developer experience with third-party libraries.

Dependency-heavy workloads and industry trends. My experience is limited, but I feel the winds have shifted towards liberal usage of external dependencies. The “not invented here” syndrome is out; “not invented there” is in.

JavaScript developers in particular are extremely likely to include third-party libraries, even for trivial operations such as left-padding a number. I don’t think the current affinity for third-party dependencies is particularly harmful, but upstream API changes can wreak havoc on untyped JavaScript and Python codebases.

When you consume third-party libraries in Java, you always know exactly what types need to be passed to a method. Most importantly, incorrect use of a function will result in red squiggles in your code editor. Given that heavy library usage is in, more people should be excited about Java.

Nominal typing. There are several disadvantages with dynamic/duck/weak/“whatever” typing. When a dependency changes an API method, and your application fails at runtime rather than at build time, that’s a problem. When a developer must refer to the implementation of a method to figure out which types to pass it, that’s a waste of time. TypeScript and Python type hints solve this problem a bit, but they lack the ability to validate passed types at runtime without extra code.

Type guards are my least favorite TypeScript feature. They’re essentially duck typing that you must implement yourself, and you must trust that they’re implemented correctly. In my opinion, this is the worst of both worlds. Consider the code in Figure 1. There’s something about declaring a type and having to write validation logic for that type that really rubs me the wrong way. The code in Figure 1 smells like someone using the wrong tool.

Oracle Java Tutorial and Materials, Oracle Java Certification, Oracle Java Preparation, Core Java, Java Career
Figure 1. Code smell

Unlike TypeScript definitions, Java’s nominal type systems take a load off the programmer’s brain by crystallizing type definitions and guaranteeing type guards by default.

Removal of responsibility for optimization. Java developers can confidently trust the JVM to do what’s best. Whether they’re implementing a multithreaded application or storing a large amount of data on the heap, they can be confident they won’t shoot themselves in the foot with memory management or data races. This advantage is largely missing in C++, which contains a multitude of “footguns.”

This ability to trust the JVM is part of Java’s ergonomic experience. When developers can worry less about technicalities, they can focus more on the problem at hand.

A holy grail of productivity. How many languages can you think of that meet the following conditions?

◉ Quality package manager and build system (I love Maven)
◉ Nominal typing
◉ A large community
◉ Hands-off optimization

Java is the only qualifying tool I’ve used, but let me know if there are others!

[Author’s note: As Reddit user Jwosty pointed out, Microsoft’s competitor to Java—C#—has all these characteristics. I have never used C# outside the Unity game engine, so I cannot fairly judge it. However, even if you include Mono, C#’s portability seems to be lacking when compared to Java.]

Surprising absence from university curriculum


I attended the University of Colorado Boulder (CU); it’s a great school but it’s certainly not known for computer science. However, most of its upper-division computer science curriculum is shamelessly stolen from Carnegie Mellon or Stanford, assignments and all. During my time at CU, I used the following programming languages. Java did not make a single appearance.

◉ C++. This language was chosen for all the required core courses, for example, computer systems, operating systems, data structures, and so on. This language is a reasonable choice because it enables direct memory management and the creation of kernel modules, and it presents many challenges and learning opportunities.

◉ Python and Julia. As you might expect, these languages were the darlings of numerical computation and discrete math professors.

◉ Scala. This language was used in principles of programming languages instruction, primarily for its functional programming and pattern matching features. While Scala uses the JVM and interoperates with Java, it provides a very different developer experience than Java.

◉ Web languages: HTML, Cascading Style Sheets (CSS), and JavaScript. These were used only in a single course called “Software Development Methods and Tools,” which focused on industry trends.

The delightful scrutability of JVM bytecode


Debugging external libraries, particularly if they exist as a compiled binary, can be exceedingly challenging. This difficulty is compounded further if the library vendor declines to include debugging symbols or a source map. The ability for developers to easily include and inspect library code is one of the reasons for JavaScript’s and Python’s popularity.

Let’s consider the worst-case scenario for working with an external library: The library is behaving in unexpected or undocumented ways and the code is obfuscated.

One way this can happen is due to code minification, which is extremely common in the JavaScript ecosystem. If you use the jump-to-definition capability and land in a minified JavaScript file, you’re overwhelmed by a completely incomprehensible blob of syntax.

A similarly difficult situation comes from attempting to debug static libraries. While a few products (such as Hopper) can produce pseudocode from binaries, it’s still extremely difficult to interpret.

JetBrains' bytecode decompiler makes this nightmare slightly less bad. While still not ideal compared to source code, it provides the easiest experience for investigating libraries that you don’t have the source code to. This is possible because JVM bytecode is organized into classes rather than as a single stack of instructions (like you’d see from optimized gcc output).

Reddit user nonbirithm left an insightful comment about how Java propelled Minecraft’s popularity by making extensive modding possible (even against the game developer’s intentions).

The amount of vitriol Java receives in the game development space is rather strong. However, whenever people dismiss Java as being a bad language, I can’t help but think of Minecraft.

Yes, it’s one of the few mainstream titles where Java has been successfully applied, but I believe Minecraft’s success was specifically because it was written in Java.

With no JVM, there would have been no Minecraft mods at the level of flexibility that Forge provides, and the game would probably have stagnated in comparison with the sheer amount of community content from the past ten years that’s available. Being able to use libraries like ASM that allow you to transform the compiled bytecode in radical ways were possible only because Minecraft targeted a virtual machine. The incredible thing was that this was in spite of the obfuscation Mojang applied to the compiled source. If it was possible to create a flexible mod system at all thanks to the JVM, people were just too motivated for any deterrence to stop them.

For the people who say that Minecraft should have been written in C++ or something from the start, because Java is a mediocre programming language, there’s Bedrock Edition. Nobody I know cares about it enough to play it. For all its bloat and performance issues, the benefits of the JVM were simply too convincing.

Out of all the machine code formats, the JVM’s is easily the most human-understandable. While it may not be relevant often, it’s still an interesting boon for the JVM ecosystem.

Source: oracle.com

Wednesday, November 24, 2021

Amber, Lambda, Loom, Panama, Valhalla: The major named Java projects

Many JEPs are collected into named projects. Here’s the latest on the progress of these initiatives.

Download a PDF of this article

The vehicle of long standing for updating the Java language and the JVM has been JDK Enhancement Proposal (JEP) documents, which are prepared using an established template and posted on the OpenJDK website. As of early September 2021, they reach up to JEP 417, which is a proposal for a vector API. While JEPs represent individual proposals, they are frequently adopted as groups of related enhancements that form what the Java team refers to as projects. These projects are named rather randomly, sometimes after things (Loom, where threads are turned into cloth) or places (Valhalla, the fabled hall of Norse mythology) or the technology itself (Lambda).

Because the Java Platform Group and the OpenJDK contributors have so many projects going on simultaneously—showing how remarkably fast the language and the JVM are evolving—it can be difficult to track what the project names refer to. This article discusses the most important active projects and touches briefly on some older projects whose nicknames are still referred to in talks and articles.

I’ll start with the big three projects: Loom, Valhalla, and Panama and then look at Amber and a few others.

Project Loom

Project Loom has gained considerable attention from the Java community because it promises to deliver lightweight threads. Today, concurrency in Java is delivered via nonlightweight threads, which are, for all intents, wrappers around operating-system threads. While these threads have worked well over the years, they suffer from several limitations: They are heavyweight (the default stack size is 1 MB per thread), they are limited in number to several thousand and, most importantly, their execution is scheduled by the operating system—not by the JVM. Traditionally, the way to reduce some of these costs has been to employ a thread pool, in which a certain number of threads are preallocated and then borrowed individually for specific tasks, after which they are returned to the pool for reuse.

Project Loom aims to deliver a lighter version of threads, called virtual threads. In the planned implementation, a virtual thread is programmed just as a thread normally would be, but you specify at thread creation that it’s virtual. A virtual thread is multiplexed with other virtual threads by the JVM onto operating system threads. This is similar in concept to Java’s green threads in its early releases and to fibers in other languages.

How does this work? You create a virtual thread to which you assign a task, and the JVM manages scheduling this virtual thread on one of the OS threads. Because the JVM has knowledge of what your task is doing, it can optimize the scheduling. It will move your virtual thread (that is, the task) off the OS thread when it’s idle or waiting and intelligently move some other virtual thread onto the OS thread. When implemented correctly, this allows many lightweight threads to share a single OS thread.

The benefit is that the JVM, rather than the OS, schedules your task. This difference enables application-aware magic to occur behind the curtains. Suppose you’re using a thread to do blocking I/O. The JVM will convert the I/O request into an asynchronous request and run your virtual thread long enough to make the I/O call. Then, while you’re waiting for the response, the JVM will move your virtual thread off the OS thread and allow another virtual thread to run. As soon as the I/O response is received, your task is swapped back in—and you get the data. To you, this swapping is entirely invisible. Your code experiences the threaded task just as it would if it were running on a single, conventional OS thread.

A historical note: The goal of trying to circumvent the OS threads in order to manage thread scheduling in the JVM long predates Project Loom. In the early 2000s, BEA Systems, which sold the WebLogic Java EE server (later acquired by Oracle in 2007), was working on what it called “bare metal Java.” This was an attempt to run BEA’s JRockit JVM on hardware without an OS layer. In the experimental project, bare metal Java used a minimal software shim to handle file system interactions and networking. Everything else was handled in the JVM. In this way, the JVM could control the scheduling of all threads and, in theory, provide better performance.

Project Valhalla

Project Valhalla aims to improve performance as it relates to access to data items. Currently in Java, data items exist in two forms.

◉ Primitives (such as chars, integers, and floats), which are created on the stack, that is, in the memory allocated inside the execution context of the executing thread. These are accessed directly.

◉ Objects, which are created on the heap and which are accessed indirectly via a pointer. (There are a few exceptions when the Java compiler’s code optimizations create an object on the stack rather than on the heap.)

Dereferencing a pointer (that is, getting the value at the address the pointer points to) can be an expensive operation. The expense is particularly steep if the pointer or the object it points to is not in the processor’s cache, because this causes a cache miss, which is remediated by an expensive read from memory.

The problem is compounded in arrays of objects, where stepping through the array means repeatedly dereferencing pointer after pointer. An example of the problem is shown in Figure 1, which is an image used in Oracle’s presentations on Project Valhalla:

Amber, Lambda, Loom, Panama, Valhalla, Java Projects, Oracle Java Career, Oracle Java Preparation, Oracle Java Learning, Oracle Java Skills
Figure 1. Stepping through an array of objects means dereferencing many pointers.

Valhalla seeks to solve this problem by introducing value types, which are a new form of data type that is programmed like objects but accessed like primitives. Specifically, value types are data aggregates that contain only data (no state) and are not mutable. By this means, the value types in Figure 1 can be stored as a single array with only a single header field for the entire array and direct access to the individual fields. Such a layout would look like Figure 2.

Amber, Lambda, Loom, Panama, Valhalla, Java Projects, Oracle Java Career, Oracle Java Preparation, Oracle Java Learning, Oracle Java Skills
Figure 2. The layout in memory of data items from Figure 1 after Valhalla

Notice the single header and the absence of internal pointers. Access is now direct and faster, and the possibility of a cache miss when accessing an element is greatly reduced. Given the increase in the use of Java for big data applications where literally millions of values might be on the heap at any given moment, Valhalla has the potential to deliver substantial performance benefits.

A second phase of the project, which will be rolled out after the value types, is a refinement to generics to include these new types.

Valhalla has taken a long time to go from proposal to delivery. This is due to the extent of the changes entailed in creating a new kind of data type: It requires changes in the compiler, the class file structure, and the JVM. Reflecting the size of the challenge, the Java team has prototyped six different solutions to see what problems ensued from the trial implementations and how the prototype broke either existing code or the implicit covenants of the Java language.

As the project has progressed, terminology has changed: Value types were renamed inline classes and then renamed again to primitive classes. The name might change again, so for the purposes of clarity, I’ve stayed with the term used in most of the talks and presentations on Valhalla—just be aware that the terminology is evolving.

Project Panama


Project Panama simplifies the process of connecting Java programs to non-Java components. In particular, Panama aims to enable straightforward communication between Java applications and C-based libraries. In other languages, equivalent technology is often referred to as the foreign-function interface, or FFI. In Java, it is known as Java Native Interface, or JNI, and can be difficult to use.

Panama comprises several subprojects, which are beginning to ship in incubator status in the recent releases of Java. These include

◉ The Foreign-Memory Access API (JEP 393 and JEP 412) formalizes how Java programs can access foreign memory without concern about garbage collection and without using the existing JNI tools. Presently, the most common ways of doing this are the ByteBuffer API (available in Java since version 1.4) or using the soon-to-be-deprecated sun.misc.Unsafe package. This new API has been incubated multiple times, most recently in Java 16, and is slated to appear in Java 17.

◉ The Foreign Linker API (JEP 389) links to C libraries and accesses C library symbol information.

◉ The Vector API (JEP 338) facilitates the use of vector math. Vectors in this sense refer to the values placed in the special wide registers of the Intel x64 and ARM AArch64 architecture in which multiple calculations can be performed simultaneously. These registers vary from 64 bits to 512 bits in length. If you’ve programmed with Intel’s Streaming SIMD Extensions (SSE) and Advanced Vector Extensions (AVX), you’ve used vectors. This API, which will be released for another round of incubation, is a developer API—that is, it’s distinct from the Java runtime’s existing optimization that makes use of vector extensions, where available.

Project Amber


The three projects discussed so far are the ones that most often come up in presentations and conversations on the future direction of Java and the JVM. If you have difficulty keeping the names straight, here’s how I do it: The primary material for a loom is threads; Valhalla is a huge Norse meeting hall where dead warriors congregate—they most likely got there using pointy things (hence, pointers); and Panama sits between the Gulf of Mexico and the Pacific just like the FFI sits between the JVM and non-Java languages.

As you might expect, there are other projects ongoing, especially those gathered up in Project Amber (for which I have no catchy mnemonic aid).

Project Amber describes its goal as “explore and incubate smaller, productivity-oriented Java language features that have been accepted as candidate JEPs.” Amber has been the springboard from which several of the recent language improvements have been launched, starting with the addition of the var keyword for local variables in Java 10.

More recently, Amber has been responsible for text blocks, which came out in Java 13; records in Java 16; and pattern matching in instanceof comparisons, which was finalized in Java 16.

Several Amber subprojects are still in progress.

Sealed classes, which have been previewed in the last few Java releases and are scheduled to be finalized in Java 17. Sealed classes (and interfaces) can limit which other classes or interfaces can extend or implement them.

◉ Pattern matching in switches is a feature that will be previewed in Java 17. While this capability is conceptually straightforward, the myriad possible syntax options make its implementation complex. The JEP I linked to presents a detailed examination of the options and the various difficulties they pose, especially with regard to maintaining rigorous compatibility with existing switch statements.

◉ Pattern matching for records and arrays, which, like pattern matching in switches, is an extension of pattern matching with instanceof. This language feature facilitates identification of the types of fields in records and of entries in arrays. A preview of this feature is projected for Java 18.

Amber could have been multiple separate projects. Due to this multiplicity of subprojects, it’s hard to know specifically what is being referred to by that name, and so it’s best to refer to the individual features, rather than the project name.

Completed projects


Several well-known Java projects have been completed and their features have been delivered. Nonetheless, the projects are still occasionally referred to by their project names. Here’s a quick guide to the most important ones.

Project Coin was a grab bag of new small features in Java 7, which included try-with-resources, strings in switch statements, multi-catch of exceptions, and a handful of other advances.

Project Graal aimed to write a Java ahead-of-time compiler in Java itself. This project was successful enough that it spawned the much larger GraalVM project.

Project Jigsaw delivered modules in Java 9 and modularized the JDK.

Project Skara examined version-control options for the Java codebase and led eventually to the entire JDK source code moving onto Git.

Source: oracle.com

Monday, November 22, 2021

JUG spotlight: The Nederlandse Java User Group

Oracle Java, Oracle Java Group, Oracle Java Exam, Oracle Java Exam Prep, Oracle Java Certification, Oracle Java Prep, Oracle Java Learning, Oracle Java Guides

The NLJUG has 5,000 members and offers regular meetings, a two-day developer conference, a quarterly magazine, and even the Masters of Java awards.

Download a PDF of this article

Java user groups (JUGs) are an essential part of the Java ecosystem—and for many people, JUGs can be extremely rewarding on both a personal and professional level. JUGs are the first point of contact for many new Java developers. They not only help spread the word about the benefits of developing in Java or in other JVM languages but also are incredible resources for hands-on learning, networking, and even job hunting. More than 100 JUGs participate in the Java Community Process; nobody really knows how many JUGs may exist, from small groups on college campuses or within employers to huge JUGs in major cities around the world.

From time to time, Java Magazine will spotlight a particular JUG; for this article, we talked to Bert Jan Schrijver, a board member of the Nederlandse Java User Group (NLJUG) in the Netherlands. In his day job, Schrijver is CTO of OpenValue, a Java consultancy in the Netherlands, Germany, Austria, and Switzerland.

Java Magazine: Tell us a bit about yourself and why you got involved in the NLJUG.

Schrijver: I have a background as a Java developer and software architect. I first got in touch with Java around 1998 as I learned it at university. After university, I landed my first job as a developer and started working with Java.

Initially, I worked at a company where we weren’t well connected with Java communities and there wasn’t much knowledge sharing. Then I switched to another company that was actively participating in the Java community in the Netherlands. There, I was encouraged by a colleague to start becoming active in the Java community.

I first joined the editorial board of the magazine published by the NLJUG, which is also called Java Magazine. It is a printed magazine that’s published four times a year to about 5,000 members of the NLJUG. This was my first role in contributing to the Java community.

I then started participating in events as well as speaking at conferences, mainly inside the Netherlands. I joined the NLJUG board around 2014. In the NLJUG, I’m mainly responsible for all things technical, the contents of our Java Magazine, publishing conference programs, and event organization.

I like sharing experiences and helping others learn. It’s also really beneficial to my network as I’m always meeting new and interesting people and companies, even internationally. By being in a JUG and attending events, you meet others that share the same passion as you who you can also reach out to whenever you have any questions about software you’re using. Knowing the people behind the technology has been really useful for me.

Java Magazine: Tell us about the NLJUG and your events.

Oracle Java, Oracle Java Group, Oracle Java Exam, Oracle Java Exam Prep, Oracle Java Certification, Oracle Java Prep, Oracle Java Learning, Oracle Java Guides
Schrijver: The NLJUG has been around for over 15 years. It’s quite a mature organization and is a bit different than many other user groups. Typically user groups are locally oriented around a geographical area. We are as well, but our geographical area is the entire Netherlands. We have around 5,000 members at the NLJUG, as well as about 100 business partners which are Java companies in the Netherlands that want to show their connection to the NLJUG and share their knowledge and experience by organizing events.

Our business partners mainly organize regular events by opening their offices to us, which we would promote and communicate through our JUG.

Those events include conference days, meetups, workshops, and university sessions. At our events, there is a mix between technical presentations and instructor-led workshops. Our business partners run events that are run in the form of meetups with one or two talks or workshops.

One of our events, Masters of Java, is where participants can compete together to win the title of Master of Java for a year. We’ve been doing this competition for years now. Typically, we have around 50 people joining, and they team up in pairs. They have an afternoon to work on a range of assignments where they only have access to the basic Java documentation—and they cannot use the internet to find solutions. The challenges are algorithmic, and the goal is to solve the challenges as quickly as possible. There’s a grading system that automatically gives you points to see how correct your solution is. We hold this once a year, typically the day before our biggest event, J-Fall.

J-Fall is our most well-known event and this year’s was held in early November 2021. It’s a two-day conference with around 50 sessions with about eight parallel tracks where a mix of international and local speakers give a range of presentations on all things new in Java. J-Fall is the biggest Java conference in the Netherlands and one of the biggest in Europe with around 1,500 people attending.

We also have an exhibition hall at J-Fall where most of the companies that are active in the Java community have their booths. These companies usually provide all the fun and interesting things to keep our guests entertained. In the past, we have had food trucks, escape rooms, racing tracks, ice cream parlors, and more. It’s a great way for the companies to attract our guests and interact with them.

We’ve always seen J-Fall as a way to nurture local talent. People with little speaking experience have the opportunity to take the stage to talk about relevant topics. To stimulate the growth of these aspiring speakers, we also have been running a speaker mentoring program. Aspiring speakers are able to learn how to give good talks at conferences and practice in front of a live audience and get feedback. This is a very popular program that has given great outcomes and opportunities for people. We’ve seen that lots of people who start with their first speaking engagements at J-Fall are now speaking at events all around the world. So let’s say that we are a good breeding ground for local speaking talent.

Java Magazine: How has the pandemic affected the NLJUG?

Schrijver: Normally, we were sponsored by our business partners, and local Java companies would open their offices to us and would provide food and drinks.

Since the pandemic, all our meetings and conferences have been hosted online. We use a platform that fosters interaction between attendees and speakers, so it feels more like an in-person experience. We try hard for participants to interact with each other online. For example, we have added a networking carousel where everyone who wants to interact with one another is randomly matched to other participants in a separate breakout room to have a two-minute chat, as though they were bumping into each other on a conference floor.

Java Magazine: Do you find there are fewer people attending online events or more people?

Schrijver: I would say both. For smaller events online, we see more attendees than normally in person. For our short sessions that are usually two hours long, we would typically get 100 attendees in person and now we get 300 online. For our full-day conferences like J-Fall, we would get around 1,500 people attending in person, but online we won’t make that number.

Even though we still run our regular events for our members, we have had fewer events since the pandemic, because one of the main reasons we hold them is to strengthen our community in person and meet new people. We’ve tried to do our best with online events, but they don’t have the same effect even though they are well attended.

Java Magazine: Are any special events that you might have done before the pandemic worth highlighting?

Schrijver: We used to arrange a trip to Oracle Code One in San Francisco, California, and we’ve been four times already. If you saw a large, loud group of people with orange T-shirts, it was most likely us! We would go there with around 30 to 40 people with our bright orange JUG T-shirts and generally make some trouble here and there.

Java Magazine: What does the NLJUG offer members who are fairly experienced developers?

Schrijver: We have taken into consideration our experts, especially when it comes to conferences, because there are not many expert-level sessions. Therefore, we added a preconference day to J-Fall focused on more expert in-depth sessions. These are instructor-led sessions that can go really deep in terms of microservices, AI, or event storming. We’ve done this for a couple of years now and have received positive feedback.

Java Magazine: How does being in a JUG help a developer’s career development?

Schrijver: I’d say in three ways. First, you can learn relevant new technologies or approaches that can build your resume.

You can build your network. For example, when you meet somebody, you stay in contact with them and maybe a year or two later their company’s hiring and they contact you.

Also, because most of our events are organized by local Java companies, you also meet a lot of possible employers. I think the last three jobs that I have worked at are companies I met through events organized by the Netherlands JUG community.

In my case, if I look at the people I hire for OpenValue, this is definitely a big plus if they’re active in the JUG community and an active speaker.

Java Magazine: If someone wants to join a user group, what should they expect in their first few meetings?

Schrijver: I’d say you’re going to expect a couple of things when becoming a part of a large and warm community. In terms of meetings, you can expect to receive regular announcements via our mailing lists about relevant free events organized by our partners in various areas of the Netherlands. The chances are fairly big that you can pick a couple of events per year that are fairly close to your home. We encourage everybody to pick the meetings that they think are interesting and relevant to them.

We have a connection with a couple of educational institutions in the Netherlands. This is called NLJUG Academy, where we actively are in touch with universities and high schools to connect students to companies but also to share knowledge and invite them to join our events for free. We regularly have groups of students joining our events, which helps broaden their view outside what they’ve learned during their education.

Membership for students is free. We hope that once they have joined us as a student, they will stay a member for the rest of their professional career. The numbers of student members we have are definitely not yet a big part of all students in the Netherlands. So there’s definitely work to be done there. But we do see a growing interest from different educational institutions.

By the way, we used to be less focused on international members, so in the beginning, the Netherlands JUG was 100% in the Dutch language. For our events, we switched to English a couple of years ago. Now our magazine is moving towards partly Dutch, partly English and most of our communications are in English.

We have definitely tried to be a welcoming and an open group for people who live in the Netherlands but do not speak Dutch. Especially around the Amsterdam area, there are a lot of experts working in tech and in the Java fields who don’t speak Dutch, but we want them to feel included.

Java Magazine: What should developers not expect from a JUG? Any dos and don’ts?

Schrijver: They shouldn’t expect that we are a recruitment organization, because that’s what we typically try to avoid at our events. Our events are about knowledge sharing and network building. Sure, members are free to reach out to companies during an event to discuss whether they have any job offers. However, we try to block any active recruiting to our community, because our priority is to focus on content and not on jobs.

We do not tolerate harassment of any type. We try to be as open and warm and welcoming as we can, so there’s no place for people to not be inclusive.

We also ask people not to try to sell anything to our members or give product pitches. If you have an amazing project and show the open source so people can use it for free, maybe they’ll be interested in the commercial version later—but don’t try selling products or services.

Java Magazine: Any final words about the NLJUG?

Schrijver: If you are working with Java and the JVM in or around the Netherlands, you should definitely join the NLJUG so you don’t miss out on all the cool events and interesting things we are sharing.

Images courtesy of the NLJUG.

Source: oracle.com

Friday, November 19, 2021

Fight ambiguity and improve your code with Java 17’s sealed classes

Use sealed classes and interfaces to create hierarchies that accurately reflect your business domain and knowledge.

Download a PDF of this article

Assumptions and ambiguities. As developers, we all hate them. Sealed classes—defined and implemented by JEP 409, as a standard language feature in Java 17—can help. Sealed classes will also help you avoid writing unnecessary code.

Sealed classes let you define hierarchies that accurately capture the knowledge of your business domain. By doing so, you can clearly define the code that handles the subclasses in a deliberate way, rather than defining generalized code that handles the unexpected classes in an unwanted manner.

In this article, you’ll learn what sealed classes are, why you need them, and how they will help improve your applications.

What is a sealed class and what does it look like?

Declaring a class or interface as sealed enables you to control the classes or interfaces that can extend or implement it. Figure 1 shows the syntax for a sealed base class and its derived classes. I’ve deliberately used an image so you just get a feel of what this feature looks like and the hierarchy it creates.

Oracle Java Exam Prep, Oracle Java Certification, Oracle Java Guides, Oracle Java Career, Oracle Java Preparation, Oracle Java Skills, Oracle Java Jobs
Figure 1. The syntax for a sealed base class and its derived classes

A couple of details are shown in the preceding image, such as using the new modifier sealed to seal the class Plant, a permits clause that declares a list of derived classes, and modifiers for all these derived classes (final, sealed, and non-sealed). As mentioned above, you can seal interfaces too; don’t worry, I’ll cover all these details shortly.

The goal of this language feature is to let you control the possible hierarchies in your business domain in a declarative manner. But why would you ever need to create such restricted hierarchies?

Imagine you are creating an application that helps users with gardening activities. Depending on the type of plant, a gardener might need to do different activities. You can model the plant hierarchy as follows (I’m intentionally not detailing the classes at this time):

abstract class Plant {}

class Herb extends Plant {}
class Shrub extends Plant {}
class Climber extends Plant {}

class Cucumber extends Climber {}

The following code shows how the Gardener class might use this hierarchy:

public class Gardener {
   int process(Plant plant) {
       if (plant instanceof Cucumber) {
           return harvestCucumber(plant);
       } else if (plant instanceof Climber) {
           return sowClimber(plant);
       } else if (plant instanceof Herb) {
           return sellHerb(plant);
       } else if (plant instanceof Shrub) {
           return pruneShrub(plant);
       } else {
           System.out.println("Unreachable CODE. Unknown Plant type");
           return 0;
       }
   }

   private int pruneShrub(Plant plant) {...}
   private int sellHerb(Plant plant) {...}
   private int sowClimber(Plant plant) {...}
   private int harvestCucumber(Plant plant) {...}
}

The preceding code has two problems:

◉ There is no straightforward way to control the hierarchy of the public class Plant or to restrict it to specific classes. You might need this restriction to align the code with a business use case (for example, if this application is supposed to work with only a specific type type of plants).

◉ There is an assumption that developers have to deal with in the last else construct, which is to define actions even though they are sure that all possible types of the method parameters have been already addressed. In the preceding code, the code in the last else block might look unreachable now, but what happens if some other developer adds a class to this hierarchy? Also, the compiler can’t help the method process to check whether it has handled all the subclasses.

◉ Sealed classes can help by imposing restrictions on the class hierarchies at the language level.

Define secure hierarchies with sealed classes


With the sealed modifier, you can declare a class as a sealed class. A sealed class can provide a permits clause that lists the classes that extend it directly. If a class doesn’t appear in this list, it isn’t allowed to extend the sealed class. Knowing this, I’ll revisit the code from the previous example using this new functionality.

public abstract sealed class Plant permits Herb, Shrub, Climber {
}

public final class Shrub extends Plant {}
public non-sealed class Herb extends Plant {}
public sealed class Climber extends Plant permits Cucumber{}

public final class Cucumber extends Climber {}

There are three types of classes that can extend a sealed class: final, non-sealed, and sealed. You are probably used to working with final classes, which prevent any other class from extending them further. The non-sealed classes are quite interesting: When a derived class is declared as non-sealed, it can be further extended by any other class; in other words, this part of the hierarchy is open to extension. Don’t miss that the base class still has an exhaustive list of its immediate subclasses. When a derived class is defined as a sealed class, it must follow the same rules as a sealed base class.

Before addressing how to iterate exhaustively over the subclasses of Plant, you should be sure you know how to compile the preceding code. Fortunately, sealed classes are a standard language feature in Java 17. You don’t need to use the --enable-preview argument for either your compiler or runtime to use it.

Modify the processing of Plant types in class Gardener


After creating a sealed hierarchy, you will be able to process an instance from the hierarchy in a precise way, and you won’t need to deal with any unknown implementations. For this example, you’ll replace the long chain of if-else statements with pattern matching for switch.

Introduced in Java 17 as a preview language feature, pattern matching for switch can be used to switch over a value based on its type, by using a type pattern.

I will rewrite the process method, in class Gardener, using a switch expression. I’ll use type patterns in the case labels to test the type of a Plant value. The Java compiler is smart enough to exploit the fact that Plant is a sealed class to require only pattern labels testing the permitted subclasses, as follows:

int process(Plant plant) {
   return switch (plant) {
       case Cucumber c -> harvestCucumber(c);
       case Climber cl -> sowClimber(cl);
       case Herb h -> sellHerb(h);
       case Shrub s -> pruneShrub(s);
   };
}

Does the preceding code seem a lot cleaner, easier to write, and easier to understand? I believe it does to the developer in Figure 2.

Oracle Java Exam Prep, Oracle Java Certification, Oracle Java Guides, Oracle Java Career, Oracle Java Preparation, Oracle Java Skills, Oracle Java Jobs
Figure 2. A developer who really likes sealed classes and pattern matching for switch

By the way, there’s a slight difference in how you would compile a class that uses pattern matching for switch, because it is a preview language feature in Java 17.

On the command prompt, include the arguments --enable-preview and -source 17 (or --release 17) to compile code that uses pattern matching for switch. Since it is a preview language feature, it must be explicitly enabled during the compilation process, as follows:

C:\code>javac -source 17 –enable-preview SealClassesAndSwitch.java
Note: SealClassesAndSwitch uses preview features of Java SE 17.
Note: Recompile with -Xlint:preview for details

At runtime, the execution process needs only the --enable-preview argument.

C:\code>java –-enable-preview SealClassesAndSwitch

Revisiting the iteration of Plant types in method process


The process method defined earlier doesn’t need a default case because it handles all the permitted subclasses of class Plant. However, class Cucumber is not a direct subclass of class Plant. The following code also handles all permitted subclasses of class Plant:

int process(Plant plant) {
   return switch (plant) {
       case Climber cl -> sowClimber(cl);
       case Herb h -> sellHerb(h);
       case Shrub s -> pruneShrub(s);
   };
}

The list of cases in the preceding code is exhaustive since class Plant is defined as an abstract class. If you define class Plant as a concrete class, which can be instantiated, you will need to add a case that checks whether the type of instance passed to the switch expression is of type Plant. Here’s the modified code.

int process(Plant plant) {
   return switch (plant) {
       case Climber cl -> sowClimber(cl);
       case Herb h -> sellHerb(h);
       case Shrub s -> pruneShrub(s);
       case Plant s -> 0;
   };
}

Let me show you how sealed classes can free you from needing to use tricks to prevent a class from being subclassed.

Decoupling accessibility and extensibility


If a class is accessible, it shouldn’t necessarily be open for extension too. By permitting a predefined set of classes to extend your class, you can decouple accessibility from extensibility. You can make your sealed class accessible to other packages and modules, while still controlling who can extend it.

In the past, to prevent classes from being extended, developers created package-private classes. But this also meant that these classes had limited accessibility. Another approach to prevent extension was to create public classes with private or package-private constructors. Though it enabled a class to be visible, it gave limited control on the exact types that could extend your class.

This is no longer the case if you use sealed classes. The goal of the sealed classes is to model your business domain with more precision.

You can’t create another class, say, AquaticPlant, that tries to extend the sealed class Plant without adding it to the permits clause of the class Plant. That’s why the following code for class AquaticPlant won’t compile:

public abstract sealed class Plant permits Herb, Shrub, Climber {
}
class AquaticPlant extends Plant {}

Here’s the compilation error when you try to compile class AquaticPlant.

C:\code>javac AquaticPlant.java
AquaticPlant.java:14: error: class is not allowed to extend sealed
class: Plant (as it is not listed in its permits clause)
Class AquaticPlant extended Plant{}
^
1 error

Package and module restrictions


Sealed classes and their implementations are generally a set of classes that are developed together, since the idea is that the developer of the base class is able to control the list of its subclasses. This leads to a few restrictions on where sealed classes can be defined.

Sealed classes and their implementations can’t span multiple Java modules. If a sealed base class is declared in a named Java module, all its implementations must be defined in the same module. However, the classes can appear in different packages. For a sealed class declared in an unnamed Java module, all its implementations must be defined in the same package.

Implicit subclasses. If you define a sealed class and its derived classes in the same source file, you can omit the explicit permits clause; the compiler will infer it for you. In the following example, the compiler will infer that the permitted subclasses of Gas are Nitrogen and Oxygen.

sealed public class Gas {}
final class Nitrogen extends Gas {}
non-sealed class Oxygen extends Gas {}

Sealed interfaces. Unlike classes, interfaces cannot define constructors. Before the introduction of sealed classes, a public class could define a private or package-private constructor to limit its extensibility, but interfaces couldn’t do that.

A sealed interface allows you to explicitly specify the interfaces that can extend it and the classes that can implement it. It follows rules similar to sealed classes.

However, you can’t declare an interface using the modifier final, because doing so would clash with its purpose: Interfaces are meant to be implemented. However, you can specify an inheriting interface as either sealed or non-sealed.

The permits clause of an interface declaration lists the classes that can directly implement a sealed interface and the interfaces that can extend it. An implementing class must be either final, sealed, or non-sealed. Here’s the code for reference.

sealed public interface Move permits Athlete, Jump, Kick{}
final class Athlete implements Move {}
non-sealed interface Jump extends Move {}
sealed interface Kick extends Move permits Karate {}
final class Karate implements Kick {}

Sealed classes and records. Records can implement a sealed interface, and since records are implicitly final, they don’t need an explicit final modifier. In the following code, classes Copper and Aluminum are explicitly final, and record class Gold is implicitly final:

sealed public interface Metal {}
final class Copper implements Metal {}
final class Aluminum implements Metal {}
Record Gold (double price) implements Metal {}

Records, by the way, implicitly extend the java.lang.Record class, so records can’t extend sealed classes.

Explicit casts and usage of instanceof


The instanceof operator evaluates the possibility of an instance being of a specific type. However, the compiler can rule out this possibility in certain cases. Consider the following interface and class:

interface NonLiving {}
class Plant {}

The Java compiler can’t rule out the possibility of a Plant instance being of type NonLiving, because it is feasible for a subclass of Plant to implement the interface NonLiving. Thus, the following code compiles successfully:

void useInstanceof(Plant plant) {
if (Plant instanceof NonLiving {
   System.out.printlin(print.toString());
   }
}

However, if class Plant is modified and defined as a final class, the preceding instanceof comparison will no longer compile. Because Plant can no longer be extended, the compiler is sure there won’t be any Plant instance that implements the interface NonLiving, and you’ll see the error shown in Figure 3.

Oracle Java Exam Prep, Oracle Java Certification, Oracle Java Guides, Oracle Java Career, Oracle Java Preparation, Oracle Java Skills, Oracle Java Jobs
Figure 3. You can’t cast a final class to a nonfinal interface.

Here is how the instanceof operator works with a set of sealed classes.

interface NonLiving {}
sealed class Plant permits Herbs, Climber {}
final class Herb extends Plant {}
sealed class Climber extends Plant permits Cucumber {}
final class Cucumber extends Climber {}

In the preceding code, class Plant is sealed and all its derived classes are either sealed or final—and none of them implements the interface NonLiving. Therefore, the instanceof check in the useInstanceof method won’t compile, as shown in Figure 4.

Oracle Java Exam Prep, Oracle Java Certification, Oracle Java Guides, Oracle Java Career, Oracle Java Preparation, Oracle Java Skills, Oracle Java Jobs
Figure 4. Another casting error

However, if you open the hierarchy by defining any of its subclasses, say, Herb, as a non-sealed class, the useInstanceof method will compile because the compiler can’t ensure that none of the Plant instances would implement the interface NonLiving. Thus, a class that extends non-sealed class Herb might implement the interface NonLiving, as shown in Figure 5.

Oracle Java Exam Prep, Oracle Java Certification, Oracle Java Guides, Oracle Java Career, Oracle Java Preparation, Oracle Java Skills, Oracle Java Jobs
Figure 5. Opening up the hierarchy

Sealed classes and the Java API


Java 17 itself makes use of sealed types. For example, the interface ConstantDesc in the package java.lang.constant is now a sealed interface, as you can see in Figure 6.

Oracle Java Exam Prep, Oracle Java Certification, Oracle Java Guides, Oracle Java Career, Oracle Java Preparation, Oracle Java Skills, Oracle Java Jobs
Figure 6. Java 17 uses sealed types itself, such as in the ConstantDesc interface.

The class java.lang.Class adds two methods, getPermittedSubclasses() and isSealed(), for working with the sealed types. You can also use those methods to enumerate the complete sealed hierarchy at runtime.

public static void outputMetaData() {
   var aClass = Plant.class;
   if (aClass.isSealed()) {
       Arrays.stream(aClass.getPermittedSubclasses())
             .forEach(System.out::println);
   }
}

Stronger code analysis with a closed list of subclasses


Sealed classes and interfaces let you specify an explicit list of inheritors that is known to the compiler, IDE, and the runtime (via reflection). This closed list of subclasses makes code analysis more powerful in IDEs such as IntelliJ IDEA.

For example, consider the following completely sealed hierarchy of the WritingDevice class, which doesn’t have non-sealed subtypes:

interface Erasable {}
sealed class WritingDevice permits Pen, Pencil {}
final class Pencil extends WritingDevice {}
sealed class Pen extends WritingDevice permits Marker {}
final class Marker extends Pen {}

If you code in this style, instanceof and casts can check the complete hierarchy statically. The code on line1 and line2 below generate compilation errors, because the compiler checks all the inheritors from the permits clause and finds that none of them implements the Erasable or the CharSequence interface.

class UseWritingDevice {
   static void write(WritingDevice pen) {
       if (pen instanceof Erasable) {                   // line1
       }
       CharSequence charSequence = ((CharSequence) pen);// line2
   }
}

The video in Figure 7 demonstrates this principle in IntelliJ IDEA.

Figure 7. Sealed classes help with code analysis.

Source: oracle.com