Thursday, August 31, 2023

Quiz yourself: Try-with-resources and PreparedStatement database access

Quiz Yourself, Oracle Java Certification, Oracle Java Prep, Oracle Java Preparation, Oracle Java Exam, Oracle Java Exam Prep, Oracle Java Certification, Oracle Java Guides, Oracle Java Learning

The best uses—and some limitations—of try-with-resources


Imagine that your system runs against a JDBC driver and a target database that fully and correctly implement the JDBC specifications, and your system has code that handles database resources properly by using the following try-with-resource statement:

String url = … //
var sql = "SELECT * FROM EMPLOYEE";
try (Connection conn = DriverManager.getConnection(url);
     PreparedStatement ps = conn.prepareStatement(sql);
     ResultSet rs = ps.executeQuery()) {
  while (rs.next()) {
    … //
  }
}

You want to refine the selection so it returns not all rows but only rows that match a certain criteria. Therefore, you changed the SQL code to the following:

var sql = "SELECT * FROM EMPLOYEE WHERE NAME LIKE ?";

Which of the following will work correctly with the new SELECT statement while ensuring that the database resources are handled properly? Choose one.


A.

try (Connection conn = DriverManager.getConnection(url);
     PreparedStatement ps = conn.prepareStatement(sql);
     ps.setString(1, "E%");
     ResultSet rs = ps.executeQuery()) {
  while (rs.next()) {
    … //
  }
}

B.

Connection conn = DriverManager.getConnection(url);
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, "E%");
ResultSet rs = ps.executeQuery();
try (conn;ps;rs) {
  while (rs.next()) {
    … //
  }
}

C.

try (Connection conn = DriverManager.getConnection(url);
     PreparedStatement ps = conn.prepareStatement(sql)) {
  ps.setString(1, "E%");
  ResultSet rs = ps.executeQuery();
  while (rs.next()) {
    … //
  }
}

D.

try (Connection conn = DriverManager.getConnection(url);
     PreparedStatement ps = conn.prepareStatement(sql)) {
  ps.setString(1, "E%");
}
try (ResultSet rs = ps.executeQuery()) {
  while (rs.next()) {
    … //
  }
}

Answer. This question investigates the use of the try-with-resources construction and also uses a PreparedStatement.

First, look at the usage of the PreparedStatement. You have modified the SQL statement to the following form:

"SELECT * FROM EMPLOYEE WHERE NAME LIKE ?"

The question mark at the end is a placeholder for a value and, before the statement is executed, a value must be provided for this. The value can be supplied correctly by the method call ps.setString(1, "E%"), which is present in all the answers’ proposed code.

Two further issues remain. One is whether the structure of the code is valid. Another is whether the rather vague requirement, “ensuring that the database resources are handled properly,” is satisfied. This latter stipulation means that all the database resources must be closed reliably by the end of the code.

The try-with-resources structure was added in Java 7 to simplify the reliable closing of resources, which can be a bit messy using only the finally block provided before then. It closes all the resources that are declared inside the parentheses that immediately follow the keyword try. As a side note, the resources are closed in the opposite order in which they are listed inside the parentheses. However, there is a restriction that code placed inside the parentheses must be one of two types of elements, as follows:

◉ An initialized declaration of a final or effectively final variable of a type that implements the interface AutoCloseable.
◉ A simple reference to a final or effectively final variable of a type that implements the interface AutoCloseable that was initialized before the start of the try-with-resources structure. (This syntax feature was added in Java 9.)

From the description above, you can immediately determine that option A presents invalid syntax because it places the call to setString inside the resources block; therefore, option A is incorrect.

In option B, you can see that the syntax is correct. It uses the second syntax option described above; it simply names effectively final resources that were declared before entry into the try block. However, although the code will compile and run, it’s not a safe approach for closure of the resources. If an exception were to arise after some of the resources have been created but before entry to the try structure, no attempt would be made to close the resources. Because of this, option B is incorrect.

Option D is also incorrect. The code deals with three resources: a Connection, a PreparedStatement, and a ResultSet. There are two separate try-with-resource statements: one for the Connection and PreparedStatement and another for the ResultSet. There are two problems with this; one is syntactic, and one is semantic. The syntax problem is that resource variables declared and initialized inside the parentheses of a try-with-resources structure behave as formal parameters. Such variables have a scope that ends with the closing curly brace of the pair that immediately follows the closing parenthesis of the try structure. This is the same as with method formal parameters. Because of this, the ps variable is out of scope before the second try-with-resource begins with the following line:

try (ResultSet rs = ps.executeQuery()) {

Additionally, even if the scope problem didn’t cause the code to fail to compile, it’s semantically incorrect. The issue is that the PreparedStatement ps and the Connection conn that supports it would have been closed at the end of the first try-with-resources and would not work for use in the second try-with-resources.

What about option C? The syntax is valid, and the code properly handles all opened resources. That might seem surprising because the ResultSet is declared in the body of the try block instead of in the resource list inside the parentheses. Because of this, the resource is not automatically closed. However, this is allowed. The documentation for Statement—which is a parent interface of PreparedStatement—explicitly says

Note: When a Statement object is closed, its current ResultSet object, if one exists, is also closed.

Because the PreparedStatement is declared inside the parentheses, it will be automatically closed, and that guarantees that the ResultSet will be closed reliably, too. Therefore, option C is correct.

A couple of side notes are worth raising.

First, what’s that waffle in the opening of this question about “fully and correctly implement the JDBC specifications”? It turns out that not all databases and drivers implement the specification fully and correctly; some actually keep a ResultSet opened after closure of their Statement. Therefore, in practice option C might let you down. A more robust approach would be to use two try-with-resource blocks nested, as follows:

try (Connection conn = DriverManager.getConnection(url);
     PreparedStatement ps = conn.prepareStatement(sql)) {
  ps.setString(1, "E%");
  try (ResultSet rs = ps.executeQuery()) {
    while (rs.next()) {
      … //
    }
  }
}

The second side note is that the items in the resource list inside the parentheses of try-with-resources must be separated using semicolons, but also permitted (but not required) is a trailing semicolon. This is similar to the array literal syntax that allows a trailing comma. Such a feature might seem odd, but it can simplify reordering the elements (since the semicolons just move around with their statements and nothing will be missing). It can also simplify machine-generated code (because the machine can simply tack a semicolon on the end of each resource without having to decide if it’s the last one).

Conclusion. The correct answer is option C.

Source: oracle.com

Monday, August 28, 2023

Going inside Java 21’s rich, upcoming goodness

Final features, continuing previews, and brand-new treats—with video links


Are you ready for all the new technology in Java 21? This article will take you on a tour of many of the changes, small and large, covering final JEPs, a progressing preview, and something entirely new for the platform.

Final features


Virtual threads. Let’s start with the big one: After two rounds of preview with barely any changes, virtual threads are final in Java 21. Now web frameworks are off to the races because they need to let you easily configure using virtual threads instead of platform threads to handle requests.

Oracle Java Career, Oracle Java Skills, Oracle Java Jobs, Oracle Java Preparation, Oracle Java Tutorial and Materials, Oracle Java Certification, Oracle Java Learning, Oracle Java Guides

Configuring virtual threads has the potential to let your app handle way more concurrent connections than before. But keep in mind that virtual threads aren’t performance pixie dust, so keep expectations realistic. Then again, if you don’t see the results you’re hoping for, there may be some easy code changes you can do that get you there. Watch episode 23 of the Inside Java Newscast for more on that and some virtual thread guidelines.

Sequenced collections. Many collections in Java have a stable iteration order (all lists and some sets, for example) but don’t necessarily allow indexed access to them (all lists do, but sets usually don’t). Java 21 steps up its collections game and introduces a set of new interfaces that capture this concept and offer related functionality.

At the core of these new interfaces is SequencedCollection, which extends Collection and is ultimately implemented by all lists, some sets, and a few other data structures. It offers the addFirst, addLast, getFirst, getLast, removeFirst, and removeLast methods, which do what you’d expect.

// getting first and last elements from a list
// (sequenced by order of addition)

var letters = List.of("c", "b", "a");
"c".equals(letters.getFirst());
"a".equals(letters.getLast());

// same but from a sorted set
// (sequenced by natural ordering)

var letterSet = new TreeSet<>(letters);
"a".equals(letters.getFirst());
"c".equals(letters.getLast());

There’s also a new method called reversed that returns a SequencedCollection that is a view on the underlying collection but in reverse order, which makes it super easy to iterate or stream over the collection.

var letters = new ArrayList<>(List.of("a", "b", "c"));
var reversedLetters = letters.reversed();

letters.addLast("d");
reversedLetters.forEach(System.out::print);
// ~> dcba

reversedLetters.addFirst("e");
letters.forEach(System.out::print);
// ~> abcde

If you want to learn more about that, the companion interfaces SequencedSet and SequencedMap, and a few odds and ends, check out episode 25 of the Inside Java Newscast.

Generational low-pause garbage collection. Garbage collection is also taking big steps forward. The Z Garbage Collector (ZGC) has a strong focus on ultralow pause times, which can lead to a higher memory footprint or higher CPU usage than other garbage collectors. Starting with Java 21, both of these metrics will be improved on many workloads when ZGC becomes generational, meaning it will maintain separate generations for young objects, which tend to die young, and old objects, which tend to be around for some time.

Preliminary benchmarks show very promising results: In a probably not-representative case, Cassandra 4 showed

◉ Four times the throughput on generational ZGC compared to ZGC with a fixed heap
◉ A quarter of the heap size on generational ZGC compared to ZGC with stable throughput

If you want to give generational ZGC a try on your workload, download a Java 21 early access build and launch it with -XX:+UseZGC -XX:+ZGenerational

Pattern matching. To effectively use pattern matching, you need three things.

◉ A capable switch that allows the application of patterns
◉ The ability to enforce limited inheritance so the switch can check exhaustiveness
◉ An easy way to aggregate and deconstruct data

var shape = loadShape();
var area = switch(shape) {
    case Circle(var r) -> r * r * Math.PI;
    case Square(var l) -> l * l;
    // no default needed
}

sealed interface Shape permits Circle, Square { }
record Circle(double radius) { }
record Square(double length) { }

There are other features that come in really handy (and they are being worked on and one even previews in Java 21—more on that later), but these are the basics, and Java 21 finalizes the last two pieces: pattern matching for switch and record patterns. With these features, you can use this powerful idiom in your projects—be it in a small or large way—if you use a functional or data-oriented approach. To see how these features play together to achieve that, check out episode 29 of the Inside Java Newscast.

Key encapsulation mechanism API. Do you know the Diffie-Hellman key exchange encapsulation (DHKEM) algorithm? If you don’t, you should definitely look into it. On the face of it, the algorithm sounds impossible. It lets two parties compute an encryption key, which is a number, while preventing an observer, who sees every exchanged message, from feasibly redoing the computation, ensuring that the key is a secret that only the two parties know. As you can imagine, that’s very helpful when you need to exchange encrypted information between parties that have no prior knowledge of each other. Hence, the DHKEM algorithm is widely used, for example, to provide forward secrecy in TLS.

Like all key encapsulation mechanisms, DHKEM is a building block of hybrid public key encryption (HPKE) and will be an important tool for defending against quantum attacks. Starting with Java 21, Java has an API to represent key encapsulation mechanisms in a natural way.

Now you’re probably wondering what the API looks like. It’s all described in episode 54 of the Inside Java Newscast with Ana-Maria Mihalceanu.

New view command for JDK Flight Recorder. The JDK Flight Recorder is an amazing piece of tech, and it’s getting better with every JDK release. JDK 21 added the view command, which displays aggregated event data on the terminal. This way, you can view information about an application without the need to dump a recording file or open up JDK Mission Control. Billy Korando explains all in episode 53 of the Inside Java Newscast.

API improvements

Java 21 comes with a number of small additions to existing APIs. Let’s quickly go over them, so you’re aware of where the JDK can do your work for you.

Emoji. The Character class gained a few static checks that let you identify emojis; first and foremost is isEmoji.

var codePoint = Character.codePointAt("😃", 0);
var isEmoji = Character.isEmoji(codePoint);
// prints "😃 is an emoji: true"
System.out.println("😃 is an emoji: " + isEmoji);

Math. The Math class got static clamp methods that take a value, a minimum, and a maximum and return a value that is forced into the [min, max] interval. There are four overloads for the four numerical primitives.

double number = 83.32;
double clamped = Math.clamp(number, 0.0, 42.0);
// prints "42.0"
System.out.println(clamped);

Repeat methods. StringBuilder and StringBuffer gained repeat methods, which allow you to add a character sequence or a code point multiple times to a string that is being built.

var builder = new StringBuilder();
builder.append("Hello");
builder.append(", ");
builder.repeat("World", 3);
builder.append("!");
// prints "Hello, WorldWorldWorld!"
System.out.println(builder);

String. String’s indexOf methods gain overloads that take a maxIndex, as follows:

var hello = "Hello, World";
var earlyCommaIndex = hello.indexOf(",", 0, 3);
// prints "-1"
System.out.println(earlyCommaIndex);

Also, string’s new splitWithDelimiters method behaves like the split method but includes the delimiters in the returned array. The same splitWithDelimiters method was added to Pattern, by the way.

var hello = "Hello; World";
var semiColonSplit = hello.splitWithDelimiters(";", 0);
//prints [Hello, ;,  World]
System.out.println(Arrays.toString(semiColonSplit));

List shuffles. Need to shuffle a List in place with a RandomGenerator? Then that’s your reason to update to Java 21! Once you do, you can pass the list and a RandomGenerator to Collections::shuffle, and it’ll shuffle the list.

var words = new ArrayList<>(List.of("Hello", "new", "Collections", "shuffle", "method"));
var randomizer = RandomGenerator.getDefault();
// using this API makes way more sense when you’re not using the default generator
Collections.shuffle(words, randomizer);
// prints the words above but with a 99.17% chance of a different order
System.out.println(words);

HttpClient. An HttpClient can now be instructed to close, to shut down, or to await termination, but those are best-effort implementations that can have adverse interactions with open request or response body streams.

var httpClient = HttpClient.newHttpClient();
// use the client
httpClient.close();

// or call shutdown and awaitTermination
// yourself for more control:
var httpClient = HttpClient.newHttpClient();
// use the client
httpClient.shutdown();
httpClient.awaitTermination(Duration.ofMinutes(1));

// it also implements AutoCloseable
try (var httpClient = HttpClient.newHttpClient()) {
    // use the client
}

Locales. The Locale.availableLocales() method returns a stream of all available locales.

var locales = Locale
    .availableLocales()
    .map(Locale::toString)
    .filter(locale -> !locale.isBlank())
    .sorted()
    .collect(Collectors.joining(", "));
// prints af, af_NA, af_ZA, af_ZA_#Latn, agq, ...
System.out.println(locales);

Case-folded tags. And because you’ve all asked for case-folded IETF BCP 47 language tags (don’t pretend that you didn’t), Locale gained the caseFoldLanguageTag method.

var lang = Locale.caseFoldLanguageTag("fi-fi");
// prints "fi-FI" (note the RFC5646-correct case)
System.out.println(lang);

Continued evolution

Now it’s time to transition from finalized features to previews, incubators, and experiments. To use a preview feature, you need to add the command-line flag --enable-preview to javac and java, and you also need to specify the Java version for javac, preferably with --release 21.

Structured concurrency. Once you get abundant virtual threads and start creating one virtual thread for every little concurrent task you have, an interesting opportunity arises: You can treat threads that you created for a set of tasks as if they are executing a single unit of work, and you can see them as children of the thread that created them.

An API that capitalizes on that would streamline error handling and cancellation, improve reliability, and enhance observability. And it would make it easy and helpful to start and end that single unit of work in the same scope, defining a unique entry and exit point for handling concurrent code. It would do for concurrency what structured programming did for control flow: add much-needed structure.

Lucky us, because such an API exists! It’s called the Structured Concurrency API.

// create task scope with desired
// error handling strategy
// (custom strategies are possible)
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {

    // fork subtasks
    Subtask<String> user = scope.fork(() -> findUser());
    Subtask<Integer> order = scope.fork(() -> fetchOrder());

    scope
        // wait for both subtasks
        .join()
        // propagate potential errors
        .throwIfFailed();

    // both subtasks have succeeded
    // ~> compose their results
    // (these calls are nonblocking)
    return new Response(user.get(), order.get());
} // task scope gets shut down

The Structured Concurrency API was incubating in Java 20 and is upgraded to a preview in Java 21. Beyond moving to a proper package, namely java.util.concurrent, the only change has been that StructuredTaskScope’s fork method now returns the new type Subtask. In Java 20, the fork method returned a Future, but that offered degrees of freedom (such as calling the blocking getmethod) that are counterproductive in structured concurrency and was overall too evocative of asynchronous programming, which is exactly what structured concurrency isn’t.

José Paumard has a video tutorial on all this; to see it, check out episode 13 of JEP Café.

Scoped values. The ThreadLocal API is used to store thread-specific information, usually in static final fields, which can then be queried from anywhere those variables are visible. That’s useful, for example, when a container, such as a web server, needs to make information accessible to other parts of its code that it doesn’t call directly. It’s also useful when it doesn’t want to pass that information on explicitly, either for convenience or integrity reasons.

class Server {

    // Principal needs to be visible to other code...
    final static ThreadLocal<Principal> PRINCIPAL = new ThreadLocal<>();

    void serve(Request request, Response response) {
        var level = request.isAuthorized() ? ADMIN : GUEST;
        var principal = new Principal(level);
        PRINCIPAL.set(principal);
        // ... but not the application
        Application.handle(request, response);
    }

}

However, ThreadLocal has a few shortcomings:

◉ Anyone with access to the ThreadLocal field can read its value and also set a new one.

◉ Values stored in ThreadLocal can be inherited from one thread to another. To prevent the other threads from reading an updated value (which the API should explicitly prevent; it’s thread local, after all), the inheriting thread must create copies. These drive up memory use, especially when there are many threads—you know, the whole “millions of virtual threads” thing.

◉ Once set, values must be explicitly removed (using the ThreadLocal::remove method) or they will leak beyond their intended use and continue to occupy memory.

To solve these problems, Java 20 incubated and Java 21 previews the Scoped Values API, which works by binding a value to the ScopedValue instance and passing the code that is allowed to read that value as a lambda—that’s the scope.

class Server {

    final static ScopedValue<Principal> PRINCIPAL = new ScopedValue<>();

    void serve(Request request, Response response) {
        var level = request.isAdmin() ? ADMIN : GUEST;
        var principal = new Principal(level);
        ScopedValue
            // binds principal to PRINCIPAL, but...
            .where(PRINCIPAL, principal)
            // ... only in the scope that is defined by this lambda
            .run(() -> Application.handle(request, response));
    }

}

The Scoped Values API addresses the following ThreadLocal issues:

◉ Within the scope, the bound value is immutable.
◉ Accordingly, no copies need to be created when inheriting, which significantly improves scalability.
◉ As the name implies, a scoped value is visible only within the defined scope; after that, the value is automatically removed, so it cannot accidentally leak.

To see scoped values in practice, watch episode 16 of JEP Café. In it, José Paumard talks about the early version of the API as it was in Java 20 and about what changes in Java 21; besides moving to java.lang, the changes mean the scope (the lambda) can now be a Callable, Runnable, and Supplier.

Vector API. The Vector API is in its sixth incubation and still waiting for Project Valhalla. There’s nothing new to see here, so please move on unless you want to see vectors in action. In that case, check out José Paumard’s episode 18 of JEP Café.

Foreign Function and Memory API. By efficiently invoking code outside the JVM (foreign functions) and by safely accessing memory not managed by the JVM (foreign memory), the Foreign Function and Memory API enables Java programs to call native libraries and process native data without the brittleness and danger of the Java Native Interface (JNI).

One of the main drivers of this API is to provide safe and timely deallocation in a programming language whose main staple is automatic deallocation (thanks, garbage collector).

Finding the right primitive to express this capability in a way that is harmonious with the rest of the Java programming model triggered a round of API changes in Java 20 and again in Java 21, which is why the API will take another round of previewing.

// 1. find foreign function on the C library path
Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
MethodHandle radixsort = linker.downcallHandle(stdlib.find("radixsort"), ...);

// 2. allocate on-heap memory to store four strings
String[] words = { "mouse", "cat", "dog", "car" };

// 3. use try-with-resources to manage the lifetime of off-heap memory
try (Arena offHeap = Arena.ofConfined()) {
    // 4. allocate a region of off-heap memory to store four pointers
    MemorySegment pointers = offHeap
        .allocateArray(ValueLayout.ADDRESS, words.length);
    // 5. copy the strings from on-heap to off-heap
    for (int i = 0; i < words.length; i++) {
        MemorySegment cString = offHeap.allocateUtf8String(words[i]);
        pointers.setAtIndex(ValueLayout.ADDRESS, i, cString);
    }

    // 6. sort the off-heap data by calling the foreign function
    radixsort.invoke(pointers, words.length, MemorySegment.NULL, '\0');

    // 7. copy the (reordered) strings from off-heap to on-heap
    for (int i = 0; i < words.length; i++) {
        MemorySegment cString = pointers.getAtIndex(ValueLayout.ADDRESS, i);
        words[i] = cString.getUtf8String(0);
    }

// 8. all off-heap memory is deallocated at the end of the try-with-resources block
}

On that note, the quality of feedback during the preview phase from projects adopting the API has been excellent and very important for its evolution. If you want to help move Java forward, the easiest way to do that is to experiment with preview features and report back to the respective mailing lists.

Another addition in Java 21 has been the so-called fallback linker, which offers a way for platforms to be compliant with Java SE without too much work by using libffi instead of fully implementing the Linker API.

Goodbye 32-bit Windows port. Microsoft Windows 10 was the last 32-bit version of Windows, and it reaches the end of its lifecycle in October 2025. As no surprise, the Java port for 32-bit Windows isn’t heavily maintained anymore. For example, its implementation of virtual threads isn’t virtual at all—the threads fall back to platform threads. So, I guess it was to be expected that the port got deprecated for removal, which Java 21 does.

Brand-new previews

In Java 21 there are three brand-new preview features that I just can’t skip—and I love how diverse they are! They span from improving a Java workhorse to refining a programming paradigm to changing how beginners learn the language.

Unnamed classes and instance main methods. Java 21 allows for much simpler entry points into a Java program. The main method no longer needs to be public or static, nor does it need the args array. And the whole surrounding class becomes optional, too, making void main the smallest possible Java program.

// content of file Hello.java
void main() {
    System.out.println("Hello, World!");
}

You can watch me demonstrate this in episode 49 of the Inside Java Newscast. Let me briefly clarify two points that I didn’t explain very well in that video.

◉ This is a preview feature, so if you use it in a single source–file program, where it clearly shines, you need to add --enable-preview --source 21 to the java command as follows:

java --enable-preview --source 21 Hello.java

◉ There are plans to shorten System.out.println to just println and to also offer a more succinct way to read from the terminal, but neither of those are part of Java 21.

Unnamed variables and patterns. Unused variables are annoying but bearable. Unused patterns during deconstruction, on the other hand, are really cumbersome and clutter code because they make you want to deconstruct less.

String pageName = switch (page) {
    case ErrorPage(var url, var ex)
        -> "💥 ERROR: " + url.getHost();
    case ExternalPage(var url, var content)
        -> "💤 EXTERNAL: " + url.getHost();
    case GitHubIssuePage(var url, var content, var links, int issueNumber)
        -> "🐈 ISSUE #" + issueNumber;
    case GitHubPrPage(var url, var content, var links, int prNumber)
        -> "🐙 PR #" + prNumber;
};

Therefore, it’s really good that Java 21 turns the underscore into a special variable and pattern, which effectively says, “I won’t be used, and you can have as many of me as you want in the same scope.”

String pageName = switch (page) {
    case ErrorPage(var url, _)
        -> "💥 ERROR: " + url.getHost();
    case ExternalPage(var url, _)
        -> "💤 EXTERNAL: " + url.getHost();
    case GitHubIssuePage(_, _, _, int issueNumber)
        -> "🐈 ISSUE #" + issueNumber;
    case GitHubPrPage(_, _, _, int prNumber)
        -> "🐙 PR #" + prNumber;
};

The change makes code more clearly readable and reduces IDE and compiler warnings. Best of all, though, it makes switching over sealed types more maintainable by allowing you to easily combine default handling of different types into a single branch while avoiding an outright default branch.

String pageEmoji = switch (page) {
    case GitHubIssuePage _ -> "🐈";
    case GitHubPrPage _ -> "🐙";
    // explicitly list remaining types to avoid default
    // branch (only possible with unnamed patterns)
    case ErrorPage _, ExternalPage _ -> "n.a.";
};

If you want to better understand why that’s important and how exactly unnamed variables and patterns work, watch episode 46 of the Inside Java Newscast.

String templates. The practice of embedding variables or simple expressions into strings isn’t popular. One reason is that it’s a bit cumbersome and the code is not perfectly readable. But more importantly, if the embedded content comes from the user, there’s the risk of injection attacks. And generally, unless you’re creating text for humans to read, there’s probably syntax and escaping to consider.

// a cumbersome and dangerous example
String property = "last_name";
String value = "Doe";

String query = "SELECT * FROM Person p WHERE p."
    + property + " = '" + value + "'";

String templates solve those problems. They make it easy to embed expressions in string literals or text blocks by encasing them between an opening backslash followed by an opening curly brace and a closing curly brace. They also enforce processing of such string templates by domain-specific string processors.

Such processors receive the string portions and the variables separately and return instances of any type.

The obvious processor simply concatenates and returns a String, but there are other possibilities out there. A SQL processor could validate and parse a statement’s syntax and return a java.sql.Statement, and a JSON processor could return a JsonNode.

// a safe and readable example
String property = "last_name";
String value = "Doe";

Statement query = SQL."""
    SELECT * FROM Person p
    WHERE p.\{property} = '\{value}'
    """;

If you want to dig deeper, check out episode 47 of the Inside Java Newscast by Ana-Maria Mihalceanu.

Source: oracle.com

Friday, August 25, 2023

Quiz yourself: The overloaded submit(…) methods in Java’s ExecutorService

Quiz Yourself, Java’s ExecutorService, Oracle Java Career, Java Skills, Java Jobs, Java Prep, Java Preparation, Java Tutorial and Materials

Know when to use Runnable and Callable in multithreaded code.


Given the following code fragment

00:  ExecutorService es = ...
01:  // es.submit(() -> {;} );
02:  // es.submit(() -> null );
03:  // es.submit(() -> { throw new NullPointerException(); });
04:  // es.submit(() -> { throw new IOException(); });
05:  // es.submit(() ->  new SQLException());

Which line or lines, when uncommented individually, will compile successfully? Choose one.

A. Only line 01
B. Only lines 01 and 02
C. Only lines 01, 02, and 03
D. Only lines 01, 02, 03, and 04
E. All lines will compile successfully

Answer. The java.util.concurrent.ExecutorService has three overloaded submit(...) methods.

  • <T> Future<T> submit(Callable<T> task);
  • Future<?> submit(Runnable task);
  • <T> Future<T> submit(Runnable task, T result);

Each of these methods takes an object that defines a task and returns an object that allows you to interact with that task and, in particular, obtain a result from it after it is completed. In each case, the task is defined by a particular method on the argument object, and that task-defining method is declared in the interface Callable or Runnable, depending on the submit method invoked. Those interfaces have the following forms:

public interface Runnable {
    public abstract void run();
}

public interface Callable<V> {
    V call() throws Exception;
}

Notice that there are two significant differences between them.

  • A Callable returns a value, whereas the Runnable declares a void method.
  • A Callable may throw a checked exception, but a Runnable can throw only an unchecked exception.

Consider the ExecutorService methods listed above in light of this. In the first overloaded submit(…) method, the Future will give access—after the task is completed—to the value of type T that is returned by the Callable or, if the method threw a checked exception, to that exception. This access happens using the get() method of the Future. If the task was completed normally, the get() method typically returns the value returned by the task. If the task threw an exception, the get() method throws an ExecutionException, the cause of which is the exception thrown by the task.

Did you notice the vague wording “the get() method typically returns…” in the description above? The second and third overloaded submit(…) methods both take a Runnable, so no value can be provided by the task. In the case of the second method, the Future returns null if the task is completed normally. By contrast, the Future returned by the third method will return the value passed as the second argument (named result in the signature shown) when Runnable is completed normally.

It’s time to see how the compiler will view each of the proposed tasks—defined by lambda expressions—in the quiz question.

Line 01: () -> {;}

This lambda body does not return any value, so it can implement only Runnable. The method has an empty body and correctly forms a void method. It’s perhaps a little surprising to see the semicolon standing by itself, and certainly that is redundant, but Java allows semicolons to be scattered in source code anywhere that a statement is expected. From this you can see that line 01 is valid and will compile.

Line 02: () -> null

This form creates a Callable because it returns a value. A Runnable must not return anything at all; null is a value and is not compatible with a void return. The essential detail here is that the code compiles; therefore, line 02 is also valid.

In lines 03 and 04, the lambda has a body that consistently throws an exception. A Runnable can throw only unchecked exceptions, but a Callable may throw any exception. This means that line 03 could be either a Runnable or a Callable, but line 04 must be a Callable. Again, however, the essence is that both lines are valid and will compile.

Line 05: () -> new SQLException()

This one is a little surprising in that it returns an exception, rather than throwing an exception. However, exceptions are objects, so the code forms a Callable because it returns a value. Therefore line 05 is valid and will compile.

Since all five lambdas are valid, the correct answer is option E.

Conclusion. The correct answer is option E.

Source: oracle.com

Wednesday, August 16, 2023

Quiz yourself: Using anonymous classes in Java

Quiz Yourself, Java Exam, Java Exam Prep, Java Tutorial and Materials, Java Prep, Java Preparation, Java Certification, Java Guides

How are anonymous classes related to the Liskov substitution principle?


Imagine that you are doing an audit of a third-party desktop Java application that interacts with users’ input and has the following code:

var c = new Control();
c.registerHandler(
  new Handler<Event>() {
    @Override
    void handle(Event e) {
      System.out.println("Event occurred: " + e);
    }
  }
);

Based on the provided code fragment, which statement is true about the Handler type? Choose one.

A. It must be an interface.
B. It must be an abstract class.
C. It can be a concrete class.
D. It can be either a class or an interface.
E. It can be a class, an interface, or an enum.
F. It can be a class, an interface, an enum, or a record.

Answer. In this era in which Java has lambda expressions, it’s not unusual to hear the idea that anonymous classes are irrelevant. However, anonymous inner classes provide capabilities that are not possible with lambda expressions. How is that?

In general, an anonymous class can extend a class, either a concrete or an abstract class, or it can implement an interface. If an abstract type is specialized, all the abstract methods in that type must be implemented. However, an anonymous class can declare only a single parent type, regardless of whether it’s an interface or a class. This limitation derives largely from the syntax, which provides only a single place in the code at which to define the parent type.

The declaration/instantiation of an anonymous class includes a parameter list. If the parent type is a class (either abstract or concrete), that parameter list is passed to the parent class’s constructor and, of course, there must be a matching constructor for that delegation. If the parent type is an interface, the parameter list must be empty.

An anonymous class can override methods of the parent type, implement abstract methods, and define arbitrary new methods and fields, even static ones, though that’s not likely to be useful.

From the description above, you can conclude that an anonymous class is not constrained to either implement an interface or extend an abstract class. Therefore, options A and B are incorrect.

You also know that, in general, an anonymous class can be derived from a concrete or abstract class or from an interface. This seems to make options C and D both look good, though the question requires a single answer. So, you have perhaps guessed there’s a bit more to this question, which we’ll get to in a moment.

Enum types place strict limits on their subtypes: Specifically, any such subtype must itself be an anonymous class declared inside the enum, and any such subtype is implicitly final. An enum that does not declare any anonymous subtypes is itself implicitly final. This means an anonymous class declared in the form shown in the question cannot possibly be a subtype of an enum. Therefore, you can reject both options E and F as incorrect.

Further, record types are always final, which makes option F impossible.

So, how can you choose between options C and D? Turn your attention to interfaces. Any method declared in an interface will default to being public if no explicit modifier is given, and most methods in an interface can be declared explicitly public. Static and concrete instance methods can also be declared as private. Notably, however, no interface method can have any intermediate accessibility—that is, no interface method can have package level, or protected, accessibility.

In addition to the restrictions on interface methods, Java seeks to impose the Liskov substitution principle on overriding or implementing methods. This principle broadly says that if a method substitutes for a method in a parent type, it should not cause any surprises. Putting it another way, the child’s method should be consistent with the declaration of the parent’s method.

Java seeks to enforce this guidance in several ways, and one of them is to prevent an overriding or implementing method from being less accessible than the method it replaces. This means that any method that claims to override an interface method must be public. (Note that you can’t use @Override to override a private method in any situation.)

In this case, however, the method handle(Event e) in the anonymous class has package accessibility. This can be valid only if the method being overridden also has package accessibility, and that tells you that the parent type must be a class and not an interface. That parent class could be either abstract or concrete, but the only option that’s valid is option C, which says the parent can be a concrete class.

So, you can conclude that option C is correct, and option D is incorrect.

Conclusion. The correct answer is option C.

Source: oracle.com

Monday, August 14, 2023

Inside the JVM: Arrays and how they differ from other objects

Arrays are unique objects inside the JVM, and understanding their structure makes for better coding.


The simplest way of classifying Java data items is to divide them into primitives and objects. Primitives, as most Java developers know, comprise booleans, bytes, chars, the integer variants (short, int, and long), and the floating-point variants (floats and doubles). Inside the JVM, these primitives are instantiated in a raw form. The declaration of an int creates a 32-bit signed integer field for the JVM to work with. These primitives are most often created on the operand stack that is constructed for every method invocation. (The notable exception is static primitives, which are created on the heap.)

Inside the JVM: Arrays and how they differ from other objects

In contrast to the simply allocated primitives, objects are entities that surround the data item with methods and sometimes with additional supporting fields. For example, a String object contains an array (which I’ll discuss shortly) that holds the contents of the string and supplementary fields that are used by a variety of methods defined for String. Objects are created on the heap. That is, they are allocated from free memory.

All objects—except arrays—have a constructor. If the source code does not define a constructor for a new object, a no-parameter constructor is created for it by the Java compiler. Most often, this constructor calls the default constructor in the Object class, which simply returns—that is, it does nothing.

The nature of arrays


Arrays are objects. However, inside the JVM, arrays are notably different from all other objects. The first major difference is that arrays are created by the JVM—not by an implicit or explicit call to new() by the developer. When the Java compiler first comes upon a set of brackets attached to a variable name, it emits a specific bytecode that tells the JVM to create an array. The compiler also specifies the kind of data items the array will hold (either primitives or objects) and how many dimensions the array has.

The JVM next creates an array of the appropriate size and type and wraps it up as an object. That is, all the methods available in Object—which arrays inherit—for example, toString(), are available to arrays. The elements of the last dimension of a newly created array are initialized to the default value for the data type (zero for the numeric types, null for objects).

Initializing arrays


As mentioned previously, arrays lack constructors. No default constructor is created by the Java compiler, and no constructor can be specified by the developer. One implication of this is that arrays must be initialized explicitly to their desired values. This is typically done through a for loop or directly at the time of the array declaration, as follows:

importantYears = new int[] {800, 1066, 1492,};

(Note that the comma after the last value is accepted in Java and won’t cause an error.) Java does not allow initialization of selected elements using the previous syntax. You must initialize a specific element individually.

Another curiosity of Java arrays is that they can have a size of zero.

unimportantYears = new int[0];

This code will not result in an error message. This surprising feature is used primarily by code generators, which might create an array and then discover there are no values to place in it. In this example, unimportantYears is not null; instead, it’s an empty array. In the same manner, a zero-length string is not null, but rather it’s a viable object.

Multidimensional arrays


While single-dimension arrays have their quirks, multidimensional arrays contain a lot more curious magic. Here is an example of a three-dimensional array, representing the x, y, and z dimensions of the three values for a financial transaction.

points = new int[2][3][4]; // a point or 1% in interest

When the compiler encounters this code, it emits a unique bytecode, MULTIANEWARRAY, which creates an array with dimensions that are each set to the specified size. This array is implemented as an array of arrays. That is, the first two dimensions contain only pointers to other arrays. So, for example, when you access the data item at 1, 2, 0, the 1 points not to a series of values but to an array of pointers to arrays of pointer values. Those values each point to yet another array—the array of integers. Put another way, points is an array of two pointers to arrays of three pointers to arrays of four ints. Figure 1 shows this design pictorially.

Inside the JVM: Arrays and how they differ from other objects
Figure 1. A three-dimensional array as it’s created inside the JVM

If you think of this design as a tree, you’ll note that only leaf arrays contain actual values. This is somewhat counterintuitive. Two-dimensional arrays are often thought of as tables. (Strictly speaking, there is no tabular analogy in the JVM’s representation of a two-dimensional array; it’s not a rows-and-columns construct.)

This design has important performance implications. The first is that to access an individual element in this example array, the JVM must dereference three pointers to get to the integer. For accessing individual values intermittently, that process represents little overhead. However, for multidimensional arrays where you are frequently updating all the values at once, such as via a for loop, the numerous dereferencing of pointers incurs significant overhead.

One way to reduce this overhead is to consider unfolding the arrays into a single-dimension array. For example, make it a 1 x 24 array and then map the three coordinates yourself to the intended element in the array. Then, updating all the values in the array can be done quickly with greatly reduced overhead. As with all things, performance should be measured carefully to make sure the trade-off is worthwhile.

Array size and the concept of arrays of arrays


Many Java collections have a method called size(), which returns an integer stating the number of elements in the collection. Arrays have no such method. There are several reasons for this, but the principal one is that arrays are simple Object instances—they are not collections. The Object class has no size() method, so arrays don’t either.

Arrays, however, have a property called length, which can be queried to get the number of elements in the specified array. In a single-dimension array, such as the first example in this article, the following code would be equal to 3.

importantYears.length

With multidimensional arrays, the same query gives a perhaps unexpected result. Using the previous points array, points.length is equal to 2, rather than the value of 24 that you might expect. The reason is that points is considered only as an array of two elements (which happen to be pointers to other arrays). If you want to get the size of all the dimensions, you need to write the following:

System.out.printf("\n Length of points: %d", points.length);
System.out.printf("\n Length of points[0]: %d", points[0].length);
System.out.printf("\n Length of points[0][0]: %d", points[0][0].length);

The code above prints the following:

Length of points: 2
Length of points[0]: 3
Length of points[0][0]: 4

As you can see, what you have is truly three arrays, working together to create the equivalent of a three-dimensional array. So, to get the size of each dimension, you need to specify exactly which dimension you want. (It’s somewhat counterintuitive that the zero dimension is not the first one in the array.)

Here’s an interesting question: What would happen in a multidimensional array if one of the dimensions were declared with a size of 0? For example,

strangePoints = new int[3][4][0][2]

In this declaration, all dimensions after the zero-size dimension are ignored. So, the result of this declaration is equivalent to a two-dimensional array of ints. This makes sense because a zero-size dimension would contain no pointers, so it’d be unable to point to subsequent layers.

Back inside the JVM


Eagle-eyed readers of my earlier statement about length being a field rather than a method call might wonder how a direct subclass of Object would have a field called length to begin with, as Object has no such field. The answer is that there is a little magic going on inside the Java compiler. When the compiler detects a reference to the length of an array, it emits a special bytecode, ARRAYLENGTH, which obtains the length of the array and returns it. This looks and behaves like a method call, but all method calls in the JVM require one of a small set of bytecodes, and they are implemented via the creation of a new frame with stack allocation and several other operations. None of that happens with this special bytecode.

No other Java objects have a corresponding bytecode for determining their size. This is just one of the many aspects that make arrays entirely unique entities inside the JVM. So, now when you code arrays, you’ll know there’s magic going on, and you’ll understand how to use the magic to get the behavior you’re looking for.

Source: oracle.com

Friday, August 11, 2023

Quiz yourself: Abstract classes and the difference between Java’s super() and this()

Quiz Yourself, Abstract classes, Java Career, Java Skills, Java Jobs, Java Prep, Java Preparation


Given the following two classes

01:  abstract class SupA {
02:    SupA() { this(null); }
03:    SupA(SubA s) {this.init();}
04:    abstract void init();
05:  }
06:  class SubA extends SupA {
07:    void init() {System.out.print("SubA");}
08:  }

Which statement is correct if you try to compile the code and create an instance of SubA? Choose one.

A. Compilation fails at line 02.
B. Compilation fails at line 03.
C. A runtime exception occurs at line 02.
D. A runtime exception occurs at line 03.
E. SubA is printed.
F. SubASubA is printed.

Answer. This question investigates object initialization, overridden method invocation, and the difference between this() and this.

Consider the process of instantiating and initializing an object, and assume that the class and all its parent types are fully loaded and initialized at the point of instantiation.

◉ First, the invocation of new causes the allocation of memory for the entire object, including all the parent elements of which it is made. That memory is also zeroed in this phase.
◉ Next, assuming no errors (such as running out of memory) occur in the first step, control is transferred to the constructor with an argument type sequence that matches, or is compatible with, the actual parameters of the invocation.

In contemporary releases of Java, including Java 17 and later, all constructors start with one of three code elements. First, there is an implicit call to super() with no arguments. This is followed by either an explicit call to super(...), which may take arguments, or by a call to this(...), which, again, may take arguments. Strictly, evaluation of any actual parameters to these calls executes before those delegating calls. (Note that there’s a proposal to allow explicit code to be placed before those calls, provided no reference is made to the uninitialized this object, but that’s for the future.)

The class SupA has two constructors. The one on line 02 delegates to the one on line 03 using this(null), while the one on line 03 has an implicit call to super().

The class SubA has no explicit constructors; therefore, the compiler gives it an implicit constructor, which delegates using super().

From that outline, consider the flow of construction and initialization of an instance of SubA.

First, the invocation new SubA() begins by allocating and zeroing memory for the entire object, including the storage necessary for the SubA, SupA, and Object parts. There are no instance fields in the code you see in this question, but the principle is the same.

Next, the newly allocated object is passed as the implicit this argument into the implicit constructor for SubA. That constructor immediately delegates—using super()—to the zero-argument constructor for SupA. That constructor in turn immediately delegates to the one-argument constructor for SupA on line 03 using the explicit call this(null).

The body of the constructor on line 03 begins with an implicit call to super() that was generated by the compiler. That call passes control up to the zero-argument constructor of java.lang.Object. When control returns from the constructor, any instance initialization on the SupA class would be executed, but of course there is none in this case. So, execution continues with the explicit body of the constructor. The constructor body calls this.init();, which invokes the implementation on line 07 and prints the message SubA. At that point, the constructor on line 03 is finished and control returns to the constructor on line 02, which also is finished since there’s no more code after this(null).

After all the constructors for SupA have finished, control returns to the implicit constructor of SubA. The implicit constructor would then perform any instance initialization called for by the SubA class, but there is none. At this point, the construction and initialization process has been completed.

Notice that in the description above, the message SubA was printed exactly once, which makes option E the correct answer and options A, B, C, D, and F incorrect.

To dive deeper, here are some specific considerations for clarification and additional points.


A call of the form this() (with parentheses) is a call to an overloaded constructor in the same class. Contrast this with the reference this, which is an explicit reference to the current instance of the class. The second form (this without parentheses) is the prefix used explicitly to invoke the init() method. Also note that init() is an ordinary instance method that implements the abstract method declared in SupA; Java does not attach any special meaning to the name init.

The constructor on line 02 passes null to the constructor on line 03. There’s nothing tricky here. If the constructor on line 03 attempted to refer to the object, it would cause a null pointer exception, but because no such reference is made, there is no problem.

The call to this.init() on line 03 is entirely valid and safe. It’s not possible to enter a constructor except via a call to new, and new must be followed by a concrete class name. This in turn means that the object referred to by this on line 03 must have a proper implementation of the init() method.

Creating a new instance of SubA does not cause an instance of SupA to be created, so there is only one instance, and when you call this.<something>, it will be tried on SubA. If this element is missing, it will be looked up for an inherited element in superclasses. In the case of the init() method, it will be directly present in SubA, so it will be called when the this.init(); statement runs.

One more point regarding good coding practice: Although it’s done in this question, it’s a bad idea to call overridable methods during instance initialization, for two reasons.

◉ The code that’s executed might not be what the programmer intended when the parent class was written.
◉ If the invocation occurs during initialization of a parent class but invokes an implementation in a subclass, the subclass implementation might refer to fields in the subclass that have not yet been initialized. This can cause unexpected behavior and commonly causes null pointer exceptions.

Conclusion. The correct answer is option E.

Source: oracle.com

Friday, August 4, 2023

Curly Braces #11: Writing SOLID Java code

Oracle Java Certification, Java Preparation, Java Guides, Java Tutorial and Materials, Java Learning



In this article, I am going to talk about writing SOLID Java code. No, I’m not talking about the excellent, classic book by Steve Maguire, Writing Solid Code, now out in its 20th anniversary second edition. Rather, I’m talking about the SOLID principles of object-oriented design (OOD), as taught by Robert C. Martin.

When my own journey began, I was taught to write a lot of procedural code in assembly language and C. As a computer science student and at my first professional programming job, I wrote a mix of procedural and functional C. The object-oriented programming (OOP) movement was in full swing when I moved from C to C++, and I embraced OOP completely before moving to Java.

Then, I studied the OOP works of Grady Booch and The Unified Modeling Language User Guide by the “three amigos” (Booch, Ivar Jacobson, and James Rumbaugh) as both a software design and development paradigm and as a diagramming standard for OOP. It seemed the entire world was on board with OOP and procedural coding was in the past.

However, a few things happened: the growth of SQL databases, the emergence of the World Wide Web, and the growth of automation. With these came SQL, HTML, and bash, Python, Perl, PHP, JavaScript scripting languages, and others. These were all more functional than imperative—and certainly more functional than object oriented.

Functional programming is based on mathematical concepts, and it relies on clearly defining inputs and outputs, embracing principles such as the reduction of side effects, immutability, and referential transparency. Further, functional programming is often associated with declarative programming, where you describe what you want the computer to do, versus telling the computer exactly how to do it, by structuring your code around real-world objects and mimicking their behavior in an imperative way.

While Java is fundamentally an imperative language, Java has embraced functional programming and the declarative nature that often comes with it.

Just as you can do OOP with C, you can do functional programming in Java. For example, objects help define boundaries and perform grouping that can be useful in any type of programming; this concept works for functional programming as well. And just as objects and inheritance (or composition) allow you to divide and conquer, by building up from smaller units of implementation in OOP, you can build complex algorithms from many smaller functions.

Doing Java a SOLID


Maybe OOP concepts aren’t quite dead yet, and mature dogs such as Java are capable of new functional tricks. But SOLID is more about design than programming. For example, I would suggest that even pure functional programmers think in objects, giving entities and components names that describe what they do as much as what they are. For example, maybe you have a TradingEngine, an Orchestrator, or a UserManager component.

Regardless of the paradigm, everyone wants code to be understandable and maintainable. SOLID, which is made of the following five principles from which its name derives, fosters those very same goals:

  • SRP: The single-responsibility principle
  • OCP: The open-closed principle
  • LSP: The Liskov substitution principle
  • ISP: The interface segregation principle
  • DIP: The dependency inversion principle

Next, I’ll discuss these principles in the context of Java development.

Single-responsibility principle


SRP states that a class should have a single responsibility or purpose. Nothing else should require the class to change outside of this purpose. Take the Java Date class, for example. Notice there are no formatting methods available in that class. Additionally, older methods that came close to formatting, such as toLocaleString, have been deprecated. This is a good example of SRP.

With the Date class, there are concise methods available for creating objects that represent a date and for comparing two different Date objects (and their representative dates in terms of time). To format and display a Date object, you need to use the DateFormat class, which bears the responsibility to format a given Date according to a set of flexible criteria. For example, for this code,

Date d = new Date();
String s = DateFormat.getDateInstance().format(d);
System.out.println(s);

the output will be

July 4, 2023

If you need to change how dates are represented within the system, you change only the Date class. If you need to change the way dates are formatted for display, you change just the DateFormat class, not the Date class. Combining that functionality into one class would create a monolithic behemoth that would be susceptible to side effects and related bugs if you changed one area of responsibility within the single codebase. Following the SOLID principle of SRP helps avoid these problems.

Open-closed principle


Once a class is complete and has fulfilled its purpose, there may be a reason to extend that class but you should not modify it. Instead, you should use generalization in some form, such as inheritance or delegation, instead of modifying the source code.

Look at the DateFormat class’s Javadoc, and you’ll see that DateFormat is an abstract base class, effectively enforcing OCP. While DateFormat has methods to specify a time zone, indicate where to insert or append a formatted date into a given StringBuffer, or handle types of calendars, SimpleDateFormat extends DateFormat to add more elaborate pattern-based formatting. Moving from DateFormat to SimpleDateFormat gives you everything you had before—and a whole lot more. For example, for the following code,

Date d = new Date();
SimpleDateFormat sdf = 
    new SimpleDateFormat("YYYY-MM-dd HH:MM:SS (zzzz)");
String s = sdf.format(d);
System.out.println(s);

the output will be

2023-07-04 09:07:722 (Eastern Daylight Time)

The important point is that the DateFormat class is left untouched from a source-code perspective, eliminating the chance to adversely affect any dependent code. Instead, by extending the class, you can add new functionality by isolating changes in a new class, while the original class is still available and untouched for both existing and new code to use.

Liskov substitution principle


LSP, developed by Barbara Liskov, states that if code works with a given class, it must continue to work correctly with subclasses of that base class. Although LSP sounds simple, there are all sorts of examples that show how hard it is to enforce and test LSP.

One common example involves shapes with a Shape base class. Rectangle and Square subclasses behave differently enough that substituting subclasses and requesting the area may yield unexpected results.

Using the Java libraries as an example, the Queue family of Java collection classes looks promising for conforming to LSP. Starting with the abstract base class AbstractQueue, along with subclasses ArrayBlockingQueue and DelayQueue, I created the following simple test application, fully expecting it to conform to LSP:

public class LSPTest {
    static AbstractQueue<MyDataClass> q = 
            new ArrayBlockingQueue(100);
            //new DelayQueue();
    
    public static void main(String[] args) throws Exception {
        for ( int i = 0; i < 10; i++ ) {
            q.add( getData(i+1) );
        }

        MyDataClass first = q.element();
        System.out.println("First element data: " +first.val3);
        
        int i = 0;
        for ( MyDataClass data: q ) {
            if ( i++ == 0 ) {
                test(data, first);
            }

            System.out.println("Data element: " + data.val3);
        }
        
        MyDataClass data = q.peek();
        test(data, first);
        int elements = q.size();
        data = q.remove();
        test(data, first);
        if ( q.size() != elements-1 ) {
            throw new Exception("Failed LSP test!");
        }
        
        q.clear();
        if ( ! q.isEmpty() ) {
            throw new Exception("Failed LSP test!");
        }
    }
    
    public static MyDataClass getData(int i) {
        Random rand = new Random(i); 
        MyDataClass data = new MyDataClass();
        data.val1 = rand.nextInt(100000);
        data.val2 = rand.nextLong(100000);
        data.val3 = ""+data.val1+data.val2;
        return data;
    }
    
    public static void test(MyDataClass d1, MyDataClass d2) throws Exception{
        if ( ! d1.val3.equals(d2.val3) ) {
            throw new Exception("Failed LSP test!");
        }
    }
}

But my code doesn’t pass the LSP test! It fails for two reasons: The behavior of add in the DelayQueue class requires the elements to implement the Delayed interface, and even when that is fixed, the implementation of remove has been, well, removed. Both violate LSP.

I did find, however, that AbstractBlockingQueue and ConcurrentLinkedQueue passed the LSP tests. This is good, but I hoped there would have been more consistency.

Interface segregation principle


With OOP, it’s easy to get carried away. For example, you can create a Document interface and then define interfaces that represent other documents such as text documents, numeric documents (such as a spreadsheet), or presentation-style documents (such as slides). This is fine, but the temptation to add behavior to these already rich interfaces can add too much complexity.

For example, assume the Document interface defines basic methods to create, store, and edit documents. The next evolution of the interface might be to add formatting methods for a document and then to add methods to print a document (effectively telling a Document to “print itself”). On the surface, this makes sense, because all the behavior dealing with documents is associated with the Document interface.

However, when that’s implemented, it means all of the code to create documents, store documents, format documents, print documents, and so on—each an area of substantial complexity—comes together in a single implementation that can have drawbacks when it comes to maintenance, regression testing, and even build times.

ISP breaks these megainterfaces apart, acknowledging that creating a document is a very different implementation from formatting a document and even printing a document. Developing each of those areas of functionality requires its own center of expertise; therefore, each should be its own interface.

As a byproduct, this also means that other developers need not implement interfaces that they never intend to support. For example, you can create a slide deck and then format it to only be shared via email and never printed. Or you might decide to create a simple text-based readme file, as opposed to a richly formatted document used as marketing material.

A good example of ISP in practice in the JDK is the java.awt set of interfaces. Take Shape, for example. The interface is very focused and simple. It contains methods that check for intersection, containment, and support for path iteration of the shape’s points. However, the work of actually iterating the path for a Shape is defined in the PathIterator interface. Further, methods to draw Shape objects are defined in other interfaces and abstract base classes such as Graphics2D.

In this example, java.awt doesn’t tell a Shape to draw itself or to have a color (itself a rich area of implementation); it’s a good working example of ISP.

Dependency inversion principle


It’s natural to write higher-level code that uses lower-level utility code. Maybe you have a helper class to read files or process XML data. That’s fine, but in most cases, such as code that connects to databases, JMS servers, or other lower-level entities, it’s better not to code to them directly.

Instead, DIP says that both lower-level and higher-level constructs in code should never depend directly on one another. It’s best to place abstractions, in the form of more general interfaces, in between them. For example, in the JDK, look at the java.sql.* package or the JMS API and set of classes.

With JDBC, you can code to the Connection interface without knowing exactly which database you’re connecting to, while lower-level utility code, such as DriverManager or DataSource, hides the database-specific details from your application. Combine this with dependency injection frameworks such as Spring or Micronaut, and you can even late-bind and change the database type without changing any code.

In the JMS API, the same paradigm is used to connect to a JMS server, but it goes even further. Your application can use the Destination interface to send and receive messages without knowing whether those messages are delivered via a Topic or a Queue. Lower-level code can be written or configured to choose the message paradigm (Topic or Queue) with associated message delivery details and characteristics hidden, and your application code never needs to change. It’s abstracted by the Destination interface in between.

You should write solid SOLID code


SOLID is a rich and deep topic; there’s a lot more to learn about. You can start small. Find solace knowing that the JDK has embraced many of the OOD principles that make writing code more efficient and productive.

Source: oracle.com