Wednesday, June 29, 2022

Reflection Means Hidden Coupling

Reflective programming (or reflection) happens when your code changes itself on the fly. For example, a method of a class, when we call it, among other things adds a new method to the class (also known as monkey patching). Java, Python, PHP, JavaScript, you name it—they all have this “powerful” feature. What’s wrong with it? Well, it’s slow, dangerous, and hard to read and debug. But all that is nothing compared with the coupling it introduces to the code.

Core Java, Oracle Java Certification, Java Preparation, Java Certification, Java Exam Prep, Java Learning, Java Skills, Java Guides, Java Preparation

There are many situations when reflection can “help” you. Let’s go through all of them and see why the coupling they add to the code is unnecessary and harmful.

Type Checking and Casting

Here is the code:

public int sizeOf(Iterable items) {

  return ((Collection) items).size();

}

I’m not sure everybody would agree that this is reflection, but I believe it is: we check the structure of the class at runtime and then make a call to the method size() which doesn’t exist in the Iterable. This method only “shows up” at runtime, when we make a dynamic shortcut to it in the bytecode.

Why is this bad, aside from the fact that 1) it’s slow, 2) it’s more verbose and so less readable, and 3) it introduces a new point of failure since the object items may not be an instance of class Collection, leading to MethodNotFoundException?

The biggest problem the code above causes to the entire program is the coupling it introduces between itself and its clients, for example:

public void calc(Iterable<?> list) {

  int s = sizeOf(list);

  System.out.println("The size is " + s);

}

This method may work or it may not. It will depend on the actual class of list. If it is Collection, the call to sizeOf will succeed. Otherwise, there will be a runtime failure. By looking at the method calc we can’t tell what is the right way to handle list in order to avoid runtime failure. We need to read the body of sizeOf and only then can we change calc to something like this:

public void calc(Iterable<?> list) {

  if (list instanceof Collection) {

    int s = sizeOf(list);

    System.out.println("The size is " + s);

  } else {

    System.out.println("The size is unknown");

  }

}

This code seems to be OK so far. However, what will happen when sizeOf changes its implementation to something like this (I took it from this article about casting):

public int sizeOf(Iterable items) {

  int size = 0;

  if (items instanceof Collection) {

    size = ((Collection) items).size();

  } else {

    for (Object item : items) {

      ++size;

    }

  }

  return size;

}

Now, sizeOf perfectly handles any type that’s coming in, whether it’s an instance of Collection or not. However, the method calc doesn’t know about the changes made in the method sizeOf. Instead, it still believes that sizeOf will break if it gets anything aside from Collection. To keep them in sync we have to remember that calc knows too much about sizeOf and will have to modify it when sizeOf changes. Thus, it’s valid to say that calc is coupled with sizeOf and this coupling is hidden: most probably, we will forget to modify calc when sizeOf gets a better implementation. Moreover, there could be many other places in the program similar to calc, which we must remember to modify when the method sizeOf changes. Obviously, we will forget about most of them.

This coupling, which is a big maintainability issue, was introduced thanks to the very existence of reflection in Java. If we had not been able to use instanceof operator and class casting (or did not even have them), the coupling would not be possible in the first place.

Forceful Testing

Consider this code:

class Book {

  private String author;

  private String title;

  Book(String a, String t) {

    this.author = a;

    this.title = t;

  }

  public void print() {

    System.out.println(

      "The book is: " + this.name()

    );

  }

  private String name() {

    return this.title + " by " + this.author;

  }

}

How would you write a unit test for this class and for its method print()? Obviously, it’s almost impossible without refactoring the class. The method print sends text to the console, which we can’t easily mock since it’s “static.” The right way would be to make System.out injectable as a dependency, but some of us believe that reflection is a better option, which would allow us to test the private method name directly, without calling print first:

class BookTest {

  @Test

  void testNamingWorks() {

    Book b = new Book(

      "David West", "Object Thinking"

    );

    Method m = book.getClass().getDeclaredMethod("name");

    m.setAccessible(true);

    assertThat(

      (String) m.invoke(book),

      equalTo("Object Thinking by David West")

    );

  }

}

You can also use PowerMock Java library to do many “beautiful” things with private methods.

The problem with this test is that it is tightly coupled with the object it tests: the test knows too much about the class Book. The test knows that the class contains a private method name. The test also knows that the method name will at some point be called by the method print. Instead of testing print the test tests what it’s not supposed to be aware of: the internals of the class Book.

The main purpose of a unit test is to be a “safety net” for us programmers trying to modify the code that was written earlier or much much earlier: if we break anything, the tests give us a timely signal, “highlighting” the place where the code was broken. If nothing is highlighted and the tests are green I can continue modifying the code. I rely on the information from my tests. I trust them.

I take the class Book and want to modify it, simply making the method name return StringBuilder instead of String. It’s a pretty innocent modification, which may be necessary for performance considerations. Before I start making any changes, I run all tests (it’s a good practice) and they all pass. Then I make my changes, expecting no tests to fail:

class Book {

  // ...

  public void print() {

    System.out.println(

      "The book is: " + this.name().toString()

    );

  }

  private StringBuilder name() {

    return new StringBuilder()

      .append(this.title)

      .append(" by ")

      .append(this.author);

  }

}

However, the test BookTest will fail, because it expects my class Book to have method name which returns String. If it’s not my test or I wrote it a long time ago, I would be frustrated to learn this fact: the test expects me to write my private methods only one specific way. Why? What’s wrong with returning StringBuilder? I would think that there is some hidden reason for this. Otherwise, why would a test demand anything from a private implementation of a class? Very soon, after some investigation I would find out that there is no reason. It’s just an assumption the test made about the internals of Book and this assumption has no reasons aside from “We didn’t have time to refactor the class and make System.out injectable.”

By the way, this testing approach is known as the “Inspector” test anti-pattern.

What would I do next? I would have to roll back my changes and then start refactoring the test and the class, in order to get rid of this assumption. However, changing the test and at the same time changing main code is, I believe, a dangerous practice: most probably I will introduce some new bugs.

The tests are not a “safety net” for me anymore. I can’t trust them. I modify the code and I know that I didn’t break anything. However, the test gives me a red signal. How can I trust it if it lies in such a simple scenario?

This coupling between the unit test BookTest and the class Book would not happen if it was not possible to use reflection in the first place. If nobody had the ability to reach the private method in any way, the Inspector anti-pattern in unit tests would not be possible.

Of course, life would be even better if we also didn’t have private methods.

Factories

Here is how a typical factory may work:

interface Operator {

  int calc(int a, int b);

}

// This is a Factory Method:

Operator make(String name) {

  try {

    return Class.forName("Op" + name);

  } catch (ClassNotFoundException ex) {

    throw new IllegalStateException(ex);

  }

}

The factory method is make. It expects the name of the “operator” to be provided and then, using Class.forName() from the Java Reflection API, constructs the name of the class, finds it in the classpath, and makes an instance of it. Now, say there are two classes both implementing the interface Operator:

class OpPlus implements Operator {

  int calc(int a, int b) {

    return a + b;

  }

}

class OpMinus implements Operator {

  int calc(int a, int b) {

    return a - b;

  }

}

Then we use them, first asking our factory method to make objects from operator names:

int result = make("Plus").calc(

  make("Minus").calc(15, 3),

  make("Minus").calc(8, 7)

);

The result will be 13.

We would not be able to do this without reflection. We would have to do this instead:

int result = new OpPlus().calc(

  new OpMinus().calc(15, 3),

  new OpMinus().calc(8, 7)

);

If you ask me, this code looks much more readable and maintainable. First of all, because in any IDE that enables code navigation it would be possible to click on OpMinus or OpPlus and immediately jump to the body of the class. Second, the logic of class finding is provided out-of-the-box by JVM: I don’t need to guess what happens when make("Plus") is called.

There are a few reasons why people love static factories. I don’t agree with them. Without reflection it wouldn’t be possible to have static factories at all and the code would be better and more maintainable.

Annotations

In Java you can attach an annotation (an instance of a DTO-ish interface) to a class (or an element of it like a method or an argument). The information from the annotation can then be read at runtime or compile time. In modern frameworks like Spring this feature is frequently used in order to automate objects wiring: you just attach some annotations to your classes and the framework will find them, instantiate them, place them into a DI container, and assign to other objects’ attributes.

I’ve said it earlier that this very mechanism of discovering objects and automatically wiring them together is an anti-pattern. I’ve also said earlier that annotations are an anti-pattern. Neither dependency injection containers, not auto-wiring, nor annotations would exist if there was no reflection. Life would be much better and Java/OOP much cleaner.

The clients of annotated objects/classes are coupled with them, and this coupling is hidden. An annotated object can change its interface or modify annotations and the code will compile just fine. The problem will surface only later at runtime, when the expectations of other objects won’t be satisfied.

Serialization

When programmers don’t understand object-oriented paradigm, they make DTOs instead of proper objects. Then, in order to transfer a DTO over a network or save it to a file, they serialize or marshall them. It’s usually done by a special serialization engine, which takes a DTO, breaks all possible encapsulation barriers, reads the values of all of its fields, and packages them into, say, a piece of JSON.

In order to let the serialization engine break encapsulation barriers, a programming language has to have reflection. First, because some fields of a DTO may be private and thus accessible only through reflection. Second, even if a DTO is designed “right” with all necessary getters for the private fields, still reflection is required in order to understand which getters are present and can be called.

The attitude serialization expresses towards objects is very similar to what ORM does. Neither of them talk to objects, but instead they pretty “offensively” tear them apart, taking away what’s necessary, and leaving the poor objects unconscious. If in the future an object decides to change its structure, rename some fields, or change the types of returned values—other objects, which actually are coupled with the object through serialization, won’t notice anything. They will notice, but only at runtime, when “invalid data format” exceptions start floating up. The developers of the object won’t have a chance to notice that their changes to the interface of the object affect some other places in the code base.

We can say that serialization is a “perfect” method of coupling two objects such that neither one will know about it.

The very idea of object-oriented programming is centered around the principle that an object is king. An object and only an object may decide what to do with the data it encapsulates. The existence of this principle and adherence to it helps avoid runtime errors usually caused by a simple scenario: A uses the data coming from B without telling B how it’s being used, then B changes the format or semantics of the data, and A fails to understand it.

Obviously, serialization in such an “abusive” way would not be possible, if there was no reflection in the first place. A more careful serialization would be possible and would be used, not through reflection but via printers implemented by objects.

Source: javacodegeeks.com

Monday, June 27, 2022

Programming lightweight IoT messaging with MQTT in Java

MQTT is an open message protocol perfect for communicating telemetry-style data from distributed devices and sensors over unreliable or constrained networks.

MQTT is an open message protocol for Internet of Things (IoT) communication. It enables the transfer of telemetry-style data in the form of messages from distributed devices and sensors over unreliable or constrained networks to on-premises servers or the cloud. Andy Stanford-Clark of IBM and Arlen Nipper of Cirrus Link Solutions invented the protocol, which is now a standard maintained by OASIS.

In case you were wondering, MQTT originally meant “message queuing telemetry transport.” However, that name has been deprecated, and there’s no official meaning now to the acronym. (The same is true of OASIS, the name of the open standards consortium, which similarly doesn’t spell out anything today, but which used to mean “Organization for the Advancement of Structured Information Standards.”)

MQTT’s strengths include simplicity and a compact binary packet payload (the compressed headers are much less verbose than those of HTTP). The protocol is a good fit for simple push messaging scenarios such as temperature updates, stock price tickers, oil pressure feeds, or mobile notifications. It also works well connecting constrained or smaller devices and sensors to the enterprise, such as connecting an Arduino device to a web service.

The MQTT message types and headers

MQTT defines a small set of message types, such as those shown in Table 1.

Table 1. MQTT message types

Message Type Value Meaning
CONNECT  Client request to connect to server 
CONNACK  Connect acknowledgment
PUBLISH  Publish message 
PUBACK  Publish acknowledgment 
PUBREC  Publish received (assured delivery, part 1) 
PUBREL  Publish release (assured delivery, part 2) 
PUBCOMP  Publish complete (assured delivery, part 3) 
SUBSCRIBE  Client subscribe request 
SUBACK  Subscribe acknowledgment 
UNSUBSCRIBE  10  Client unsubscribe request 
UNSUBACK  11  Unsubscribe acknowledgment 
PINGREQ  12  PING request 
PINGRESP  13  PING response 
DISCONNECT  14  Client is disconnecting 
AUTH  15  Authentication exchange 

Note that message type values 0 and 15 are reserved.

The message type is set as part of the MQTT message fixed header, shown in Table 2, which includes flags in the first byte indicating the message type (four bits), whether the message is being resent (one bit), a quality of service (QoS) flag (two bits), and a message retention flag (one bit). The remaining portion of the fixed header indicates the length of the rest of the message, which includes a header that varies by message type, and the message payload.

Table 2. MQTT message header

bit 7 │ 6 │ 5 │ 4 3 2 1 0 
byte 1   Message type DUP QoS level RETAIN 
byte 2  Message length (one to four bytes)
byte 3  … if needed to encode message length 
byte 4  … if needed to encode message length 
byte 5  … if needed to encode message length 

As for the header fields, the message types were described in Table 1. The DUP flag is set to 1 when a message is resent. The QoS level will either be 0 (meaning no guarantee of delivery), 1 (meaning the message will never be lost but duplicate delivery may occur), or the strictest level of 2 (meaning exactly one delivery and the message is never lost and never duplicated).

The RETAIN flag, which applies only to published messages, indicates that the server should hold on to that message even after it has been delivered. When a new client subscribes to that message’s topic, the message will be sent immediately. Subsequent messages for the same topic with the RETAIN flag set will replace the previously retained message. This is useful for cases where you don’t want a client to wait for a published message (say, due to a change in value) before it has an existing value.

The length of the fixed header can be up to five bytes long depending on the total length of the message. An algorithm is used, whereby the last bit in each byte of the message length is used as a flag to indicate if another byte follows that should be used to calculate the total length.

For more details on the algorithm, see the MQTT specification, which is encoded in Listing 1. (Also see the sample application code I used in this article for the sender https://github.com/ericjbruno/mqtt_sender and the listener https://github.com/ericjbruno/mqtt_listener.)

Listing 1.  The fixed header length algorithm explained in Java

public class VariableLengthEncoding {
    public static void main(String[] args) {
        int value = 321;
        // Encode it
        ArrayList<Integer> digits = encodeValue(value);
        System.out.println(value + " encodes to "  + digits);
        // Derive original from encoded digits
        int value1 = decodeValue(digits);
        System.out.println("Original value was " + value1);
    }

    public static ArrayList<Integer> encodeValue(int value) {
        ArrayList<Integer> digits = new ArrayList();
        do {
            int digit = value % 128;
            value = value / 128;

            // if there are more digits to encode
            // then set the top bit of this digit
            if ( value > 0 ) {
                digit = digit | 0x80;
            }
            digits.add(digit);
        }
        while ( value > 0 );
        return digits;
    }

    public static int decodeValue(ArrayList<Integer> digits) {
        int value = 0;
        int multiplier = 1;
        Iterator<Integer> iter = digits.iterator();
        while ( iter.hasNext() ) {
            int digit = iter.next();
            value += (digit & 127) * multiplier;
            multiplier *= 128;
        }
        return value;
    }
}

The length applies to the remaining portion of the message and does not include the length of the fixed header, which can be between two and five bytes.

Each message type contains its own header with applicable flags, called a variable header because it varies depending on message type.

For instance, the CONNECT message, used when a new client application connects to the MQTT server, contains the protocol name and version it will use for communication, a username and password, flags that indicate whether messages are retained if the client session disconnects, and a keep-alive interval.

By contrast, a PUBLISH message variable header contains the topic name and message ID only. The header components for the remaining message types are covered in the MQTT specification.

Publishing messages and subscriptions


The PUBLISH message has two use cases. First, the PUBLISH message type is sent by a client when that client wishes to publish a message to a topic. Second, the MQTT server sends a PUBLISH message to each subscriber when a message is available, such as when a message has been published by another client.

When a client sends a PUBLISH message (to send a message), the message must contain the applicable topic name and the message payload, as well as a QoS level for that particular message. The response to the sender will depend on the QoS level for the message sent, as follows:

◉ QoS 0: Make the message available to all interested parties; there is no response.
◉ QoS 1: Store the message in persistent storage, send to all interested parties, and then send a PUBACK back to the sender (in that order).
◉ QoS 2: Store the message in persistent storage (but don’t send it to interested parties yet), and then send a PUBREC message to the sender (in that order).

When an MQTT client application wishes to subscribe to a topic, it first connects, and then it sends a SUBSCRIBE message. This message contains a variable length list of one or more topics (each specified by name) along with a QoS value for each topic that the client is subscribing to. The server will send a SUBACK message in response to the SUBSCRIBE request message.

In terms of the QoS level for a topic, a client receives messages at less than or equal to this level, depending on the QoS level of the original message from the publisher. For example, if a client has a QoS level 1 subscription to a particular topic, a QoS level 0 PUBLISH message to that topic stays at that QoS level when it’s delivered to the client. However, a QoS level 2 PUBLISH message to the same topic will be downgraded to QoS level 1 for delivery to that client.

Message flow per MQTT QoS level


It’s time to examine the message flow per QoS level in more detail.

QoS level 0 messages. For QoS level 0 messages, the server simply makes the published message available to all parties and never sends a message in response to the sender, as shown in Figure 1.

Oracle Java, Core Java, Java Certification, Oracle Java Preparation, Java Learning, Java Guides, Java Certified, Java Exam Prep

Figure 1. Message flow for a QoS level 0 published message

Since there are no guarantees with QoS level 0 messages, neither the sender nor the server persist the messages.

QoS level 1 messages. For QoS level 1 messages, the message flow is very different from QoS level 0 messages. See Figure 2.

Oracle Java, Core Java, Java Certification, Oracle Java Preparation, Java Learning, Java Guides, Java Certified, Java Exam Prep

Figure 2. Message flow for a QoS level 1 published message

When a client publishes a message, the MQTT server first persists the message to ensure delivery. Next, it delivers the message to each subscriber by sending them PUBLISH messages with the message payload. After each subscriber acknowledges the message, the server deletes the message from the persisted store and sends the original sender an acknowledgment.

QoS level 2 messages. QoS level 2 messages are the strictest in terms of message delivery, guaranteeing once-and-only-once message delivery per client. The message flow is shown in Figure 3.

Oracle Java, Core Java, Java Certification, Oracle Java Preparation, Java Learning, Java Guides, Java Certified, Java Exam Prep

Figure 3. Message flow for a QoS level 2 published message

When a client publishes a QoS level 2 message, the MQTT server first persists the message to ensure delivery. Next, it sends a PUBREC (publish received) message to the sender, who then replies with a PUBREL (publish release) message.

When the server responds with a PUBCOMP (publish complete) message, the initial sender exchange is complete, and the sender can delete the message from its persistent store.

After that, the MQTT server delivers the message to each subscriber by sending them PUBLISH messages with the message payload. After message receipt, each subscriber sends a PUBREC message in acknowledgment, which the server responds to with a PUBREL message.

Finally, after each subscriber acknowledges with a PUBCOMP message, the server deletes the message from the persisted store, and the QoS level 2 message send sequence is complete.

Setting up an MQTT broker


For the sample code below, I chose to use the Eclipse Mosquitto open source MQTT broker. Downloads are available for Windows, Linux (via aptitude or apt, if your distribution supports it), and macOS (via Brew if you choose to use it). Simply download binaries for Windows and macOS (via Brew), or install it for Linux using the following command:

>sudo apt install mosquitto

This works on a Raspberry Pi running Raspbian Linux as well. Using apt, Mosquitto will be installed in /usr/bin, and you can start it directly from there. However, it’s best to start Mosquitto on Linux as a System V init service with the following command:

>sudo service mosquitto start

Subsequently you can stop Mosquitto with the following command:

>sudo service mosquitto stop

Alternatively, you can then start Mosquitto on a UNIX-based system with the following command:

>/usr/local/sbin/mosquitto

The default listen port is 1883, but you can specify a different port via the –p command-line parameter when you start Mosquitto. You can also supply an optional configuration file via the –c command-line parameter. Use the configuration file to specify alternative ports, certificate-based encryption, an access control list file, logging directives, and more, as described in the documentation.

The Eclipse Paho project contains MQTT libraries for Java and other languages. For Java, download the JAR files, or you can build from the source. For this article, I used the MQTTv5-compatible Paho library.

To build the Paho JAR files, you’ll need Apache Maven. Be sure to define your JAVA_HOME environment variable or Maven will fail. The command would be the same on Linux; use the correct path to the JDK on your local system. Once you’ve done this, execute the following command to build Paho:

> mvn package –DskipTests

Sending and receiving MQTT messages


I’ll begin by showing you how to create a sample application that simulates reading the current temperature from a temperature sensor, published as an MQTT message.

Create a Java project and add the Paho client library JAR file to the classpath, which is needed to connect to and communicate with an MQTT broker. The code is written for version 5 of the MQTT specification, so be sure to use version 5 of the Paho client library with the following Maven entry in the POM file:

<dependency>
      <groupId>org.eclipse.paho</groupId>
      <artifactId>org.eclipse.paho.mqttv5.client</artifactId>
      <version>1.2.5</version>
    </dependency>

Connect to the MQTT broker. To connect to the MQTT message broker, use the code in Listing 2.

Listing 2. The Java code to connect to an MQTT broker

MqttClient mqttClient= null;
// ...
try {
   MqttConnectOptions connOpts = new MqttConnectOptions();
   connOpts.setCleanStart(false);

   mqttClient =
     new MqttClient(
       "tcp://localhost:1883",    // broker URL
       "my_sender",               // a unique client id
       new MemoryPersistence() ); // or file-based, or omit

   mqttClient.connect(connOpts);
   System.out.println("Connected");
}
catch ( MqttException me ) {
   // ...
}

You can connect to any MQTT broker by changing the address in the URL parameter of the MqttClient class constructor. For instance, instead of tcp://localhost:1883, you can specify a different address and even the port, such as tcp://192.168.1.10:1880. The URL must begin with tcp://.

The second parameter is the client ID that must be unique for each client connecting to a specific broker. With the third parameter, you specify the way you want messages persisted at the client for guaranteed delivery. Keep in mind this does not affect message persistence at the broker; it affects only messages in flight to and from the client application.

This example uses MemoryPersistence, meaning messages will be stored in memory (which is fast) while awaiting notification from the broker that the message was received and/or delivered, depending on the QoS level specified. If the message needs to be resent to the broker for some reason, it’s retrieved from the memory store.

In this case, message delivery is guaranteed only if the client application isn’t shut down before the broker receives and stores the message. For additional reliability, choose FilePersistence instead (which can be slower). This guarantees message delivery even in cases where the client is shut down or crashes while messages are in flight and the client is subsequently restarted.

Finally, call MqttClient.connect to connect to the specified broker. You can provide connection options via a parameter, such as minimum MQTT protocol version requirements (via setMqttVersion), whether or not to maintain state across restarts of the client (via setCleanStart), authentication details, and timeout intervals.

Publishing MQTT messages. Once your application has connected to a broker, it can begin to send and receive messages. The payloads of MQTT messages are sent as byte arrays, making it easier to avoid cross-platform or cross-language issues. Listing 3 sends the current temperature, originally formatted as a String, to the topic currentTemp. You can see how the message is formatted as a byte array and then sent with the highest level of message QoS (once-and-only-once delivery).

Listing 3 . Java code to publish an MQTT message

private void publishTemp(Long temp) {
   try {
       String content = "Current temp: " + temp.toString();
       MqttMessage message = new MqttMessage( content.getBytes("UTF-8") );
       message.setQos(2); // once and only once delivery
       mqttClient.publish("currentTemp", message);
   }
   catch ( MqttException me ) {
       // . . .
   }
}

Receiving MQTT messages. To receive messages, first connect to an MQTT broker, as shown in Listing 2. Next, subscribe to a topic (or multiple topics) by calling the subscribe() method on the MqttClient class, providing the topic and requested QoS level as parameters.

MqttSubscription subscription =
    new MqttSubscription(topic, qos);
         
IMqttToken token =
    client.subscribe(
        new MqttSubscription[] { subscription });

Messages published at a lower QoS will be received at the published QoS, while messages published at a higher QoS will be received using the QoS specified in the call to subscribe. Messages are delivered asynchronously to a callback, which in this case is a class that implements the IMqttMessageListener interface.

IMqttToken token =
    client.subscribe(
        new MqttSubscription[] { sub },
        new Mqtt MessageListener[] { this } );

The Java class supplied must implement the notification method, messageArrived(), as shown in Listing 4.

Listing 4 . Implementing the IMqttMessageListener interface to receive message notifications

@Override
public void messageArrived(String topic, MqttMessage message)
 throws Exception {
   String messageTxt = new String( message.getPayload(), "UTF-8" );
   System.out.println(
       "Message on " + topic + ": '" + messageTxt + "'");
}

The messageArrived() method is called when a message is available from the broker for the supplied topic name. Providing the topic name is helpful in cases where you subscribe to multiple topics with a single callback. Optionally, you can set a separate callback for each topic, or you can specify some other arbitrary grouping.

Topic filter hierarchy and wildcards. MQTT supports hierarchical topic names, with subscriptions at any level within the hierarchy. For instance, say you define a topic hierarchy based on publishing temperature and other data for rooms across floors in a set of buildings, as shown below.

building/10/floor/1/room100/temperature
building/10/floor/1/room101/temperature
. . .
building/10/floor/20/room2001/temperature
. . .

To receive temperature readings for room 201 in building 2, subscribe to the topic building/2/floor/2/room201/temperature. Data can be published and subscribed to at any depth in the hierarchical tree (not just the leaves). For instance, you can publish/subscribe to occupancy data at building/10/floor/1 as well as temperature data at building/10/floor/1/room100/temperature.

Leveraging this format, you can subscribe to sensor readings across entire floors and even entire buildings by using the multilevel wildcard string: #. For example, subscribing to building/10/floor/1/room101/# subscribes to all topics (assuming there are more devices than temperature sensors) for room 101. Further, subscribing to building/10/floor/1/# subscribes to all topics (and related sensors) for all rooms on the first floor of building 10. You can even subscribe to all topics in the system with just # as the topic name.

However, the following shows another scenario where each room supports temperature and humidity data readings with topics:

building/10/floor/1/room101/temperature
building/10/floor/1/room101/humidity

If your application needs to get all temperature data (but not humidity) for all rooms on the first floor, you can insert the single-level wildcard character + (not the multilevel #) within the topic hierarchy to subscribe to, as follows:

building/10/floor/1/+/temperature

Note that the single-level wildcard subscribes to all topic names in that one level of the hierarchy only. For example, if you subscribe to temperature/average/state/+, you would receive temperature data for every state in the country. However, if you subscribe to temperature/average/state/#, you also receive temperature data for each major city within each state, as follows:

temperature/average/state/NY/Albany
temperature/average/state/NY/Kingston
. . .
temperature/average/state/CA/Sacramento
temperature/average/state/CA/Pasadena
. . .

You can use the single-level wildcard more than once within the hierarchy. For example, to receive temperature data for all rooms across all floors in building 10, subscribe to the following topic:

building/10/floor/+/+/temperature

Request/response messaging. When a message is published, the publisher can request a reply by including a response topic name in the message properties, as shown below.

MqttProperties props = new MqttProperties();
props.setResponseTopic("RespondTopic");
props.setCorrelationData( ("" + (correlationId++)).getBytes("UTF-8") );
message.setProperties(props);
client.publish("MyTopic", message);

When a subscriber receives this message, it can check for the existence of the response topic and then send a reply message, as shown in Listing 5.

Listing 5.  Implementing request/response messaging with MQTT

@Override
    public void messageArrived(String topic, MqttMessage mqttMessage)
        throws Exception
    {
        String messageTxt = new String( mqttMessage.getPayload(), "UTF-8" );
        // ...

        MqttProperties props = mqttMessage.getProperties();
        String responseTopic = props.getResponseTopic();
        if ( responseTopic != null ) {
            String corrData = new String(props.getCorrelationData(), "UTF-8");

            MqttMessage response = new MqttMessage();
            props = new MqttProperties();
            props.setCorrelationData(corrData.getBytes("UTF-8"));
            response.setProperties(props);

            String content = "Received. Correlation data=" + corrData;
            response.setPayload(content.getBytes("UTF-8"));

            client.publish(responseTopic, response);
        }
    }

Of course, this means that the publisher needs to have already subscribed to the response topic prior to sending the request. The correlation ID can be used to match responses to requests, if the receiver sets it in the response message.

MqttSubscription sub = new MqttSubscription("response", 0);
    IMqttToken token =
        client.subscribe(
            new MqttSubscription[] { sub },
            new IMqttMessageListener[] { this });

    //...

    MqttProperties props = new MqttProperties();
    correlationId++;
    props.setResponseTopic("response");
    props.setCorrelationData( (""+correlationId).getBytes("UTF-8") );
    message.setProperties(props);

This way you can have one subscriber for all request/response messages, yet you will still be able to match them with the correlation data or the topic name.

Selecting MQTT broker implementations


There are quite a few MQTT brokers available under varying license models. Additionally, there are cloud services that support MQTT integration, such as Oracle Internet of Things Production Monitoring Cloud Service and Oracle Internet of Things Cloud Service.

The following are a few open source MQTT implementations:

◉ Paho

Source: oracle.com

Friday, June 24, 2022

Bruce Eckel on Java modules, text blocks, and more

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

Modules

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

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

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

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

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

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

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

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

java --list-modules

That command produces output like the following:

java.base@11

java.compiler@11

java.datatransfer@11

java.desktop@11

java.instrument@11

java.logging@11

java.management@11

java.management.rmi@11

java.naming@11

java.net.http@11

...

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

java --describe-module java.base

You’ll see the following:

java.base@11

exports java.io

exports java.lang

exports java.lang.annotation

exports java.lang.invoke

exports java.lang.module

exports java.lang.ref

exports java.lang.reflect

exports java.math

exports java.net

exports java.net.spi

exports java.nio

...

uses java.text.spi.DateFormatSymbolsProvider

uses sun.util.locale.provider.LocaleDataMetaInfo

uses java.time.chrono.Chronology

uses java.nio.channels.spi.AsynchronousChannelProvider

uses sun.text.spi.JavaTimeDateTimePatternProvider

...

provides java.nio.file.spi.FileSystemProvider with

  jdk.internal.jrtfs.JrtFileSystemProvider

...

qualified exports sun.security.timestamp to jdk.jartool

qualified exports sun.security.validator to jdk.jartool

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

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

qualified exports sun.security.tools to jdk.jartool

...

contains sun.text

contains sun.text.bidi

contains sun.text.normalizer

contains sun.text.resources

contains sun.text.resources.cldr

contains sun.text.spi

contains sun.util

contains sun.util.calendar

contains sun.util.locale

contains sun.util.resources.cldr

contains sun.util.spi

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

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

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

Text blocks

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

// strings/TextBlocks.java

// {NewFeature} Since JDK 15

// Poem: Antigonish by Hughes Mearns

public class TextBlocks {

  public static final String OLD =

    "Yesterday, upon the stair,\n" +

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

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

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

    "\n" +

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

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

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

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

  public static final String NEW = """

    Yesterday, upon the stair,

    I met a man who wasn't there

    He wasn't there again today

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


    When I came home last night at three

    The man was waiting there for me

    But when I looked around the hall

    I couldn't see him there at all!

    """;

  public static void main(String[] args) {

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

  }

}

/* Output:

true

*/

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

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

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

// strings/Indentation.java

// {NewFeature} Since JDK 15

public class Indentation {

  public static final String NONE = """

          XXX

          YYY

          """; // No indentation

  public static final String TWO = """

          XXX

          YYY

        """;   // Produces indent of 2

  public static final String EIGHT = """

          XXX

          YYY

  """;         // Produces indent of 8

  public static void main(String[] args) {

    System.out.print(NONE);

    System.out.print(TWO);

    System.out.print(EIGHT);

  }

}

/* Output:

XXX

YYY

  XXX

  YYY

        XXX

        YYY

*/

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

// strings/DataPoint.java

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

// Since JDK 16: record

record DataPoint(String location, Double temperature) {

  @Override public String toString() {

    return """

    Location: %s

    Temperature: %.2f

    """.formatted(location, temperature);

  }

  public static void main(String[] args) {

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

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

    System.out.print(hill);

    System.out.print(dale);

  }

}

/* Output:

Location: Hill

Temperature: 45.20

Location: Dale

Temperature: 65.20

*/

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

Better NullPointerException reporting

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

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

// exceptions/BetterNullPointerReports.java

// {NewFeature} Since JDK 15

// Since JDK 16: record

record A(String s) {}

record B(A a) {}

record C(B b) {}

public class BetterNullPointerReports {

  public static void main(String[] args) {

    C[] ca = {

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

      new C(new B(null)),

      new C(null),

    };

    for(C c: ca) {

      try {

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

      } catch(NullPointerException npe) {

        System.out.println(npe);

      }

    }

  }

}

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

null

java.lang.NullPointerException

java.lang.NullPointerException

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

null

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

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

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

Effectively final variables in try-with-resources

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

// exceptions/EffectivelyFinalTWR.java

// {NewFeature} Since JDK 9

import java.io.*;

public class EffectivelyFinalTWR {

  static void old() {

    try (

      InputStream r1 = new FileInputStream(

        new File("TryWithResources.java"));

      InputStream r2 = new FileInputStream(

        new File("EffectivelyFinalTWR.java"));

    ) {

      r1.read();

      r2.read();

    } catch(IOException e) {

      // Handle exceptions

    }

  }

  static void jdk9() throws IOException {

    final InputStream r1 = new FileInputStream(

      new File("TryWithResources.java"));

    // Effectively final:

    InputStream r2 = new FileInputStream(

      new File("EffectivelyFinalTWR.java"));

    try (r1; r2) {

      r1.read();

      r2.read();

    }

    // r1 and r2 are still in scope. Accessing

    // either one throws an exception:

    r1.read();

    r2.read();

  }

  public static void main(String[] args) {

    old();

    try {

      jdk9();

    } catch(IOException e) {

      System.out.println(e);

    }

  }

}

/* Output:

java.io.IOException: Stream Closed

*/

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

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

Source: oracle.com

Friday, June 10, 2022

Java Hashtable, HashMap, ConcurrentHashMap – Performance impact

Java Hashtable, Java HashMap, Java ConcurrentHashMap, Core Java, Java Exam Prep, Oracle Java Certification, Java Career, Java Skills, Java Jobs, Java News, Oracle Java, Oracle Java Preparation, Oracle Java Certified

There are a good number of articles that articulate functional differences between HashMap, HashTable and ConcurrentHashMap. This post compares the performance behavior of these data structures through practical examples. If you don’t have patience to read the entire post, here is bottom line: When you confront with the decision of whether to use HashMap or HashTable or ConcurrentHashMap, you can consider using ConcurrentHashMap since it’s thread-safe implementation, without compromise in performance.

Performance Study

To study the performance characteristics, I have put-together this sample program

public class HashMapPerformance {

   public static int ITERATION_COUNT = 10000000;

   private static AtomicInteger exitThreadCount = new AtomicInteger(0); 

   public static HashMap<Integer, Integer> myHashMap;

   public static void initData() {

      myHashMap = new HashMap<>(1000);

      for (int counter = 0; counter < 1000; ++counter) {

         myHashMap.put(counter, counter);

      }      

   }

   private static class Writer extends Thread{

      public void run() {

         Random random = new Random();

         for (int iteration = 0; iteration < ITERATION_COUNT; ++iteration) {

            int counter = random.nextInt(1000 - 1); 

            myHashMap.put(counter, counter);

         }

         exitThreadCount.incrementAndGet();

      }

   }

   private static class Reader extends Thread{

      public void run() {

         Random random = new Random();

         for (int iteration = 0; iteration < ITERATION_COUNT; ++iteration) {

            int counter = random.nextInt(1000 - 1); 

            myHashMap.get(counter);

         }

         exitThreadCount.incrementAndGet();

      }

   }      

   public static void main (String args[]) throws Exception {

      initData();

      long start = System.currentTimeMillis();

      // Create 10 Writer Threads

      for (int counter = 0; counter < 10; ++counter) {

         new Writer().start();

      }

      // Create 10 Reader Threads

      for (int counter = 0; counter < 10; ++counter) {

         new Reader().start();

      }

      // Wait for all threads to complete

      while (exitThreadCount.get() < 20) {

         Thread.sleep(100);

      }

      System.out.println("Total execution Time(ms): " + (System.currentTimeMillis() - start) );

   }   

 }

This program triggers multiple threads to do concurrent read and write to the ‘java.util.HashMap’.

Let’s walk through this code. Primary object in this program is ‘myHashMap’ which is defined in line #7. This object is of type ‘java.util.HashMap’ and it’s initialized with 1000 records in the method ‘initData()’, which is defined in line #9. Both key and value in the HashMap have the same integer value. Thus this HashMap will look like as shown in the below diagram:

Key Value
1000  1000 

Fig: Data in the HashMap

‘Writer’ Thread is defined in line #19. This thread generates a random number between 0 to 1000 and inserts the generated number into the HashMap, repeatedly for 10 million times. We are randomly generating numbers so that records can be inserted into different parts of the HashMap data structure. Similarly, there is a ‘Reader’ Thread defined in line #35. This thread generates a random number between 0 to 1000 and reads the generated number from the HashMap. 

You can also notice ‘main()’ method defined in line #51. In this method you will see 10 ‘Writer’ threads are created & launched. Similarly, 10 ‘Reader’ threads are created & launched. Then in line 70, there is code logic which will prevent the program from terminating until all the Reader and Writer threads complete their work. 

HashMap Performance


We executed the above program several times. Average execution time of the program was 3.16 seconds

Hashtable Performance


In order to study the Hashtable performance, we replaced the line #7 with ‘java.util.Hashtable’ and modified the ‘Reader’ and ‘Writer’ threads to read and write from the ‘HashTable’. We then executed the program several times. Average execution time of the program was 56.27 seconds.

ConcurrentHashMap Performance


In order to study the HashTable performance, we basically replaced the line #7 with ‘java.util.concurrent.ConcurrentHashMap’ and modified the ‘Reader’ and ‘Writer’ threads to read and write from the ‘ConcurrentHashMap’. We then executed the program several times. Average execution time of the program was 4.26 seconds.

HashMap, Hashtable, ConcurrentHashMap performance comparison


Below table summarizes the execution time of each data structure: 

Data structure Execution Time (secs)
HashMap  3.16 
ConcurrentHashMap  4.26 
Hashtable  56.27 

If you notice HashMap has the best performance, however it’s not thread-safe. It has a scary problem that can cause the threads to go on an infinite loop, which would ultimately cause the application’s CPU to spike up

If you notice ConcurrentHashMap is slightly slower performing than HashMap, however it’s a 100% thread safe implementation. 

On the other hand Hashtable is also thread safe implementation, but it’s 18 times slower than HashMap for this test scenario. 

Why is Hashtable so slow?


Hashtable is so slow because both the ‘get()’ and ‘put()’ methods on this object are synchronized (if you are interested, you can see the Hashtable source code here). When a method is synchronized, at any given point in time, only one thread will be allowed to invoke it. 

In our sample program there are 20 threads. 10 threads are invoking ‘get()’ method, another 10 threads are invoking ‘put()’ method. In these 20 threads when one thread is executing, the remaining 19 threads will be in BLOCKED state. Only after the initial thread exits ‘get()’, ‘put()’ method, remaining threads would be able to progress forward. Thus, there is going to be a significant degradation in the performance.

To confirm this behavior, we executed the above program and captured the thread dump and analyzed it with fastThread( a thread dump analysis tool). Tool generated this interesting analysis report. Below is the excerpt from the report that shows the transitive dependency graph of BLOCKED threads

Java Hashtable, Java HashMap, Java ConcurrentHashMap, Core Java, Java Exam Prep, Oracle Java Certification, Java Career, Java Skills, Java Jobs, Java News, Oracle Java, Oracle Java Preparation, Oracle Java Certified
Fig: Threads BLOCKED on Hashtable (generated by fastThread)

Report was showing that 19 threads were in BLOCKED state, while one of the threads (i.e., ‘Thread-15’) is executing the ‘get()’ method in the Hashtable. Thus only after ‘Thread-15’ exits the ‘get()’ method, other threads would be able to progress forward and execute the ‘get()’, ‘put()’ method. This will cause considerable slowdown in the application performance.

Source: javacodegeeks.com

Wednesday, June 8, 2022

Bruce Eckel on Java pattern matching guards and dominance

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

Pattern matching guards let you refine the matching condition beyond simply matching on the type.

The previous article in this series, “Bruce Eckel on pattern matching in Java,” introduced pattern matching and this article delves into the details. Keep in mind that, at the time of this writing, pattern matching for switch is a second preview feature in JDK 18 as JEP 420, and the Java team intends to add additional features.

Guards

A guard allows you to refine the matching condition beyond simply matching on the type. It is a test that appears after the type and &&. The guard can be any Boolean expression. If the selector expression is the same as the type for the case and the guard evaluates to true, the pattern matches, as follows:

// enumerations/Shapes.java

// {NewFeature} Preview in JDK 17

// Compile with javac flags:

//   --enable-preview --source 17

// Run with java flag: --enable-preview

import java.util.*;

sealed interface Shape {

  double area();

}

record Circle(double radius) implements Shape {

  @Override public double area() {

    return Math.PI * radius * radius;

  }

}

record Rectangle(double side1, double side2)

  implements Shape {

  @Override public double area() {

    return side1 * side2;

  }

}

public class Shapes {

  static void classify(Shape s) {

    System.out.println(switch(s) {

      case Circle c && c.area() < 100.0

        -> "Small Circle: " + c;

      case Circle c -> "Large Circle: " + c;

      case Rectangle r && r.side1() == r.side2()

        -> "Square: " + r;

      case Rectangle r -> "Rectangle: " + r;

    });

  }

  public static void main(String[] args) {

    List.of(

      new Circle(5.0),

      new Circle(25.0),

      new Rectangle(12.0, 12.0),

      new Rectangle(12.0, 15.0)

    ).forEach(t -> classify(t));

  }

}

/* Output:

Small Circle: Circle[radius=5.0]

Large Circle: Circle[radius=25.0]

Square: Rectangle[side1=12.0, side2=12.0]

Rectangle: Rectangle[side1=12.0, side2=15.0]

*/

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

The first guard for Circle determines whether that Circle is small. The first guard for Rectangle determines whether that Rectangle is a square.

Here’s a more complex example: A Tank can hold different types of liquids, and the Level of the tank must be between zero and 100%.

// enumerations/Tanks.java

// {NewFeature} Preview in JDK 17

// Compile with javac flags:

//   --enable-preview --source 17

// Run with java flag: --enable-preview

import java.util.*;

enum Type { TOXIC, FLAMMABLE, NEUTRAL }

record Level(int percent) {

  Level {

    if(percent < 0 || percent > 100)

      throw new IndexOutOfBoundsException(

        percent + " percent");

  }

}

record Tank(Type type, Level level) {}

public class Tanks {

  static String check(Tank tank) {

    return switch(tank) {

      case Tank t && t.type() == Type.TOXIC

        -> "Toxic: " + t;

      case Tank t && (                 // [1]

          t.type() == Type.TOXIC &&

          t.level().percent() < 50

        ) -> "Toxic, low: " + t;

      case Tank t && t.type() == Type.FLAMMABLE

        -> "Flammable: " + t;

      // Equivalent to "default":

      case Tank t -> "Other Tank: " + t;

    };

  }

  public static void main(String[] args) {

    List.of(

      new Tank(Type.TOXIC, new Level(49)),

      new Tank(Type.FLAMMABLE, new Level(52)),

      new Tank(Type.NEUTRAL, new Level(75))

    ).forEach(

      t -> System.out.println(check(t))

    );

  }

}

The record Level includes a compact constructor that ensures that percent is valid. Records were introduced in a previous article, “Bruce Eckel on Java records,” in this series.

Here’s a note for line [1]: If a guard contains multiple expressions, simply enclose it in parentheses.

Since the code switches on Tank rather than Object, the final case Tank acts the same as a default because it catches all Tank cases that don’t match any of the other patterns.

Dominance

The order of the case statements in a switch can be important because if the base type appears first, it dominates anything appearing afterwards.

// enumerations/Dominance.java

// {NewFeature} Preview in JDK 17

// Compile with javac flags:

//   --enable-preview --source 17

import java.util.*;

sealed interface Base {}

record Derived() implements Base {}

public class Dominance {

  static String test(Base base) {

    return switch(base) {

      case Derived d -> "Derived";

      case Base b -> "B";            // [1]

    };

  }

}

The base type Base is in last place, at line [1]—and that’s where it should be. But if you move that line up, the base type will appear before case Derived, which would mean that the switch would never be able to test for Derived because any derived class would then be captured by case Base. If you try this experiment, the compiler reports an error: this case label is dominated by a preceding case label.

Order sensitivity often appears when you use guards. Moving the final case in Tanks.java to a higher position in the switch produces that same domination error message. When you have multiple guards on the same pattern, more-specific patterns must appear before more-general patterns. Otherwise, a more-general pattern will match before more-specific patterns, and the latter will never be checked. Fortunately, the compiler reports dominance problems.

The compiler can detect dominance problems only when the type in a pattern dominates the type in another pattern. The compiler cannot know whether the logic in guards produces problems.

// enumerations/People.java

// {NewFeature} Preview in JDK 17

// Compile with javac flags:

//   --enable-preview --source 17

// Run with java flag: --enable-preview

import java.util.*;

record Person(String name, int age) {}

public class People {

  static String categorize(Person person) {

    return switch(person) {

      case Person p && p.age() > 40          // [1]

        -> p + " is middle aged";

      case Person p &&

        (p.name().contains("D") || p.age() == 14)

        -> p + " D or 14";

      case Person p && !(p.age() >= 100)     // [2]

        -> p + " is not a centenarian";

      case Person p -> p + " Everyone else";

    };

  }

  public static void main(String[] args) {

    List.of(

      new Person("Dorothy", 15),

      new Person("John Bigboote", 42),

      new Person("Morty", 14),

      new Person("Morty Jr.", 1),

      new Person("Jose", 39),

      new Person("Kane", 118)

    ).forEach(

      p -> System.out.println(categorize(p))

    );

  }

}

/* Output:

Person[name=Dorothy, age=15] D or 14

Person[name=John Bigboote, age=42] is middle aged

Person[name=Morty, age=14] D or 14

Person[name=Morty Jr., age=1] is not a centenarian

Person[name=Jose, age=39] is not a centenarian

Person[name=Kane, age=118] is middle aged

*/

The guard in pattern line [2] seems like it would match Kane at age 118, but instead Kane matches with the pattern at line [1]. You cannot rely on the compiler to help with the logic of your guard expressions.

Without the last case Person p, the compiler complains that the switch expression does not cover all possible input values. With that case, a default is still not required, so the most general case becomes the default. Because the argument to the switch is a Person, all cases are covered (except for null).

Coverage

Pattern matching naturally guides you toward using the sealed keyword. This helps ensure that you’ve covered all possible types passed into the selector expression. See how that works in practice with the following example:

// enumerations/SealedPatternMatch.java

// {NewFeature} Preview in JDK 17

// Compile with javac flags:

//   --enable-preview --source 17

// Run with java flag: --enable-preview

import java.util.*;

sealed interface Transport {};

record Bicycle(String id) implements Transport {};

record Glider(int size) implements Transport {};

record Surfboard(double weight) implements Transport {};

// If you uncomment this:

// record Skis(int length) implements Transport {};

// You get an error: "the switch expression

// does not cover all possible input values"

public class SealedPatternMatch {

  static String exhaustive(Transport t) {

    return switch(t) {

      case Bicycle b -> "Bicycle " + b.id();

      case Glider g -> "Glider " + g.size();

      case Surfboard s -> "Surfboard " + s.weight();

    };

  }

  public static void main(String[] args) {

    List.of(

      new Bicycle("Bob"),

      new Glider(65),

      new Surfboard(6.4)

    ).forEach(

      t -> System.out.println(exhaustive(t))

    );

    try {

      exhaustive(null); // Always possible!  // [1]

    } catch(NullPointerException e) {

      System.out.println("Not exhaustive: " + e);

    }

  }

}

/* Output:

Bicycle Bob

Glider 65

Surfboard 6.4

Not exhaustive: java.lang.NullPointerException

*/

The sealed interface Transport is implemented using record objects, which are automatically final. The switch covers all possible types of Transport, and if you add a new type, the compiler detects it and tells you that you haven’t exhaustively covered all possible patterns. But line [1] shows that there’s still one case that the compiler doesn’t insist you cover: null.

If you remember to explicitly add a case null, you’ll prevent the exception. But the compiler doesn’t help you here, possibly because that would affect too much existing switch code.

Source: oracle.com