Writing programs that work correctly at runtime can be challenging. This is because our assumptions about how our code will behave when executed are often wrong. Using Java’s assertions feature is one way to verify that your programming logic is sound.

This tutorial introduces Java assertions. You’ll first learn what assertions are and how to specify and use them in your code. Next, you’ll discover how to use assertions to enforce preconditions and postconditions. Finally, you will compare assertions with exceptions, and find out why you need both in your code.

download

Download the source code for examples in this tutorial. Created by Jeff Friesen for JavaWorld.

What are Java assertions?

Before JDK 1.4, developers often used comments to document assumptions about program correctness. Comments are useless as a mechanism for testing and debugging assumptions, however. The compiler ignores comments, so there is no way to use them for bug detection. Developers also frequently do not update comments when changing code.  

In JDK 1.4, assertions were introduced as a new mechanism for testing and debugging assumptions about our code. In essence, assertions are compilable entities that execute at runtime, assuming you’ve enabled them for program testing. You can program assertions to notify you of bugs where the bugs occur, greatly reducing the amount of time you would otherwise spend debugging a failing program.

Assertions are used to codify the requirements that render a program correct or not by testing conditions (Boolean expressions) for true values, and notifying the developer when such conditions are false. Using assertions can greatly increase your confidence in the correctness of your code.

How to write an assertion in Java

Assertions are implemented via the assert statement and java.lang.AssertionError class. This statement begins with the keyword assert and continues with a Boolean expression. It is expressed syntactically as follows:

assert BooleanExpr;

If BooleanExpr evaluates to true, nothing happens and execution continues. If the expression evaluates to false, however, AssertionError is instantiated and thrown, as demonstrated in Listing 1.

Listing 1: AssertDemo.java (version 1)

public class AssertDemo
{
   public static void main(String[] args)
   {
      int x = -1;
      assert x >= 0;
   }
}

The assertion in Listing 1 indicates the developer’s belief that variable x contains a value that is greater than or equal to 0. However, this is clearly not the case; the assert statement’s execution results in a thrown AssertionError.

Compile Listing 1 (javac AssertDemo.java) and run it with assertions enabled (java -ea
AssertDemo
). You should observe the following output:

Exception in thread "main" java.lang.AssertionError
        at AssertDemo.main(AssertDemo.java:6)

This message is somewhat cryptic in that it doesn’t identify what caused the AssertionError to be thrown. If you want a more informative message, use the assert statement expressed below:

assert BooleanExpr : expr;

Here, expr is any expression (including a method invocation) that can return a value — you cannot invoke a method with a void return type. A useful expression is a string literal that describes the reason for failure, as demonstrated in Listing 2.

Listing 2: AssertDemo.java (version 2)

public class AssertDemo
{
   public static void main(String[] args)
   {
      int x = -1;
      assert x >= 0: "x < 0";
   }
}

Compile Listing 2 (javac AssertDemo.java) and run it with assertions enabled (java -ea
AssertDemo
). This time, you should observe the following slightly expanded output, which includes the reason for the thrown AssertionError:

Exception in thread "main" java.lang.AssertionError: x < 0
        at AssertDemo.main(AssertDemo.java:6)

For either example, running AssertDemo without the -ea (enable assertions) option results in no output. When assertions are not enabled, they are not executed, although they are still present in the class file.

Preconditions and postconditions

Assertions test a program’s assumptions by verifying that its various preconditions and postconditions aren’t violated, alerting the developer when a violation occurs:

  • A precondition is a condition that must evaluate to true before the execution of some code sequence. Preconditions ensure that callers keep their contracts with callees.
  • A postcondition is a condition that must evaluate to true after the execution of some code sequence. Postconditions ensure that callees keep their contracts with callers.

Preconditions

You can enforce preconditions on public constructors and methods by making explicit checks and throwing exceptions when necessary. For private helper methods, you can enforce preconditions by specifying assertions. Consider Listing 3.

Listing 3: AssertDemo.java (version 3)

import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;

class PNG
{
   /**
    *  Create a PNG instance, read specified PNG file, and decode
    *  it into suitable structures.
    *
    *  @param filespec path and name of PNG file to read
    *
    *  @throws NullPointerException when <code>filespec</code> is
    *          <code>null</code>
    */
   PNG(String filespec) throws IOException
   {
      // Enforce preconditions in non-private constructors and
      // methods.

      if (filespec == null)
         throw new NullPointerException("filespec is null");
      try (FileInputStream fis = new FileInputStream(filespec))
      {
         readHeader(fis);
      }
   }

   private void readHeader(InputStream is) throws IOException
   {
      // Confirm that precondition is satisfied in private
      // helper methods.

      assert is != null : "null passed to is";
   }
}

public class AssertDemo
{
   public static void main(String[] args) throws IOException
   {
      PNG png = new PNG((args.length == 0) ? null : args[0]);
   }
}

The PNG class in Listing 3 is the minimal beginning of a library for reading and decoding PNG (portable network graphics) image files. The constructor explicitly compares filespec with null, throwing NullPointerException when this parameter contains null. The point is to enforce the precondition that filespec not contain null.

It’s not appropriate to specify assert filespec != null; because the precondition mentioned in the constructor’s Javadoc would not (technically) be honored when assertions were disabled. (In fact, it would be honored because FileInputStream() would throw NullPointerException

, but you shouldn’t depend on undocumented behavior.)

However, assert is appropriate in the context of the private readHeader() helper method, which will be completed eventually to read and decode a PNG file’s 8-byte header. The precondition that is always be passed a non-null value will always hold.

Postconditions

Postconditions are typically specified via assertions, regardless of whether or not the method (or constructor) is public. Consider Listing 4.

Listing 4: AssertDemo.java (version 4)

public class AssertDemo
{
   public static void main(String[] args)
   {
      int[] array = { 20, 91, -6, 16, 0, 7, 51, 42, 3, 1 };
      sort(array);
      for (int element: array)
         System.out.printf("%d ", element);
      System.out.println();
   }

   private static boolean isSorted(int[] x)
   {
      for (int i = 0; i < x.length - 1; i++)
         if (x[i] > x[i + 1])
            return false;
      return true;
   }

   private static void sort(int[] x)
   {
      int j, a;
      // For all integer values except the leftmost value ...
      for (int i = 1; i < x.length; i++)
      {
         // Get integer value a.
         a = x[i];
         // Get index of a. This is the initial insert position, which is
         // used if a is larger than all values in the sorted section.
         j = i;
         // While values exist to the left of a's insert position and the
         // value immediately to the left of that insert position is
         // numerically greater than a's value ...
         while (j > 0 && x[j - 1] > a)
         {
            // Shift left value -- x[j - 1] -- one position to its right --
            // x[j].
            x[j] = x[j - 1];
            // Update insert position to shifted value's original position
            // (one position to the left).
            j--;
         }
         // Insert a at insert position (which is either the initial insert
         // position or the final insert position), where a is greater than
         // or equal to all values to its left.
         x[j] = a;
      }

      assert isSorted(x): "array not sorted";
   }
}

Listing 4 presents a sort() helper method that uses the insertion sort algorithm to sort an array of integer values. I’ve used assert to check the postcondition of x being sorted before sort() returns to its caller.

The example in Listing 4 demonstrates an important characteristic of assertions, which is that they’re typically expensive to execute. For this reason, assertions are usually disabled in production code. In Listing 4, isSorted() must scan through the entire array, which can be time-consuming in the case of a lengthy array.

Assertions vs. exceptions in Java

Developers use assertions to document logically impossible situations and detect errors in their programming logic. At runtime, an enabled assertion alerts a developer to a logic error. The developer refactors the source code to fix the logic error and then recompiles this code.

Developers use Java’s exception mechanism to respond to non-fatal (e.g., running out of memory) runtime errors, which may be caused by environmental factors, such as a file not existing, or by poorly written code, such as an attempt to divide by 0. An exception handler is often written to gracefully recover from the error so that the program can continue to run.

Assertions are no substitute for exceptions. Unlike exceptions, assertions don’t support error recovery (assertions typically halt program execution immediately — AssertionError isn’t meant to be caught); they are often disabled in production code; and they typically don’t display user-friendly error messages (although this isn’t an issue with assert). It’s important to know when to use exceptions rather than assertions.

When to use exceptions

Suppose you’ve written a sqrt() method that calculates the square root of its argument. In a non-complex number context, it’s impossible to take the square root of a negative number. Therefore, you use an assertion to fail the method if the argument is negative. Consider the following code fragment:

public double sqrt(double x)
{
   assert x >= 0 : "x is negative";
   // ...
}

It’s inappropriate to use an assertion to validate an argument in this public method. An assertion is intended to detect errors in programming logic and not to safeguard a method from erroneous arguments. Besides, if assertions are disabled, there is no way to deal with the problem of a negative argument. It’s better to throw an exception, as follows:

public double sqrt(double x)
{
   if (x < 0)
      throw new IllegalArgumentException("x is negative");
   // ...
}

The developer might choose to have the program handle the illegal argument exception, or simply propagate it out of the program where an error message is displayed by the tool that runs the program. Upon reading the error message, the developer can fix whatever code led to the exception.

You might have noticed a subtle difference between the assertion and the error-detection logic. The assertion tests x >= 0, whereas the error-detection logic tests x < 0. The assertion is optimistic: We assume that the argument is OK. In contrast, the error-detection logic is pessimistic: We assume that the argument is not OK. Assertions document correct logic, whereas exceptions document incorrect runtime behavior.

In this tutorial you’ve learned how to use assertions to document correct program logic. You’ve also learned why assertions are no replacement for exceptions, and you’ve seen an example where using an exception would be more effective.

This story, “How to use assertions in Java” was originally published by

JavaWorld.

Copyright © 2020 IDG Communications, Inc.

Source link