J2SE 1.4 premieres Java's assertion capabilities, Part 1

Understand the mechanics of Java's new assertion facility

1 2 Page 2
Page 2 of 2
// Base.java
//
package tmp;
public class Base
{
  public void m1( boolean test )
  {
    assert test : "Assertion failed: test is " + test;
    System.out.println( "OK" );
  }
}
// Derived.java
//
package tmp.sub;
import tmp.Base;
public class Derived
  extends Base
{
  public void m2( boolean test )
  {
    assert test : "Assertion failed: test is " + test;
    System.out.println( "OK" );
  }
  public static void printAssertionError( AssertionError ae )
  {
    StackTraceElement[] stackTraceElements = ae.getStackTrace();
    StackTraceElement stackTraceElement = stackTraceElements[ 0 ];
    System.err.println( "AssertionError" );
    System.err.println( "   class=   " + stackTraceElement.getClassName() );
    System.err.println( "   method=  " + stackTraceElement.getMethodName() );
    System.err.println( "   message= " + ae.getMessage() );
  }
  
  public static void main( String[] args )
  {
    try
    {
      Derived derived = new Derived();
      System.out.print( "derived.m1( false ): " );
      derived.m1( false );
      System.out.print( "derived.m2( false ): " );
      derived.m2( false );
    }
    catch( AssertionError ae )
    {
      printAssertionError( ae );
    }
  }
}

Each class contains a simple method with an assertion statement. Note that class Derived inherits m1(boolean)'s Base class implementation.

First, I run tmp.sub.Derived with assertions enabled for all classes:

java -ea tmp.sub.Derived
derived.m1( false ): AssertionError
   class=   tmp.Base
   method=  m1
   message= Assertion failed: test is false

The AssertionError message reports an exception in the implementation inherited from class tmp.Base. This is, of course, the basis of implementation inheritance.

Next, I run tmp.sub.Derived with assertions enabled by default, but specifically disabled for the class tmp.sub.Base:

java -ea -da:tmp.Base tmp.sub.Derived
derived.m1( false ): OK
derived.m2( false ): AssertionError
   class=   tmp.sub.Derived
   method=  m2
   message= Assertion failed: test is false

Now the method call derived.m1(false) fails to trigger the assertion, since the implementation resides in class tmp.Base and assertions are disabled for that class. Although this proves quite sensible, it could be surprising at first.

As a final comment on the command-line switches for enabling and disabling assertions, note in the command above that the class-specific switch -da:tmp.Base overrides the global switch -ea. Assertion status for each class is determined by the most specific setting. Furthermore, command-line switches are processed in order, with later switches taking precedence over earlier switches. To understand how class assertion status is maintained, I next investigate the assertion-enabling additions to java.lang.ClassLoader.

Additions to java.lang.ClassLoader

To maintain assertion status at the class and package levels, J2SE 1.4 includes several additions to class java.lang.ClassLoader. The new public methods are listed below, along with a brief description of their role in the new assertion facility:

public void setClassAssertionStatus(String className, boolean enabled)
ClassLoader maintains a map of class names to assertion status. This method corresponds to the command-line switches -ea:<class name> and -da:<class name>, which enable and disable assertions for specifically named classes.
public void setPackageAssertionStatus(String packageName, boolean enabled)
ClassLoader also maintains a map of package names to assertion status. This method corresponds to the command-line switches -ea:<package name>... and -da:<package name>..., which enable and disable assertions for named packages and all subpackages.
public void setDefaultAssertionStatus(boolean)
ClassLoader maintains a default assertion status. This method corresponds to the command-line switches -ea and -da for enabling and disabling assertions for all nonsystem classes, and the switches -esa and -dsa for enabling and disabling assertions in the system class loader.

A new, special assertion-status flag is set in each class loaded by a ClassLoader subclass. The simple assertion-status resolution scheme used by ClassLoader is as follows:

  • Check the class name map for a class-specific assertion-status mapping.
  • Check the package name map for a mapping of the class's most specific package name. Repeat for each next-most specific package name of the class.
  • Use the default assertion-status setting.

The resolution scheme stops on first match or when the default assertion status is reached. As an example, when loading class tmp.sub.Derived, the scheme completes the following steps:

  • Checks the class name map for entry tmp.sub.Derived.
  • Checks the package name map for entry tmp.sub.
  • Checks the package name map for entry tmp.sub.
  • Uses the default assertion-status setting.

This level of implementation detail demystifies the determination of class assertion status and explains several previously made comments:

  • The command-line switches don't verify the existence of class and package names. They simply register entries into the maps maintained by ClassLoader.
  • The most specific mapping takes precedence, since the assertion-status resolution scheme stops on the first map entry match and the scheme checks from most specific to least specific.
  • Later command-line switches take precedence over earlier switches, since map entries in ClassLoader are simply being overwritten.

As a final note on the implementation detail, ClassLoader subclasses set a class's assertion-status flag when loading the class. Adding or modifying class or package name mappings using ClassLoader methods does not affect already loaded classes. For example, suppose myClassLoader loads class tmp.sub.Derived, no class- or package-specific mappings exist, and the default assertion status is false. A programmatic call to myClassLoader.setClassAssertionStatus("tmp.sub.Derived",true) does not enable assertion in the already loaded class tmp.sub.Derived. If myClassLoader later reloads that class, then the assertion status will reflect the programmatic change. Fortunately, this is only of concern for programmatic changes and does not affect the more common usage of setting assertion statuses using command-line switches.

Miscellaneous details

The assertion facility does not provide a direct means of conditionally preventing the inclusion of assertion code in a class file. Section 14.20 of The Java Language Specification, however, provides a standard idiom for achieving conditional compilation in Java. Applied to assertions, the idiom looks something like this:

static final boolean assertionsEnabled = <true | false>;
if( assertionsEnabled )
  assert expression1;

Since the variable assertionsEnabled is static final, when the value is false, a compiler can detect that the assert statement is unreachable and can remove the assert statement code.

The assertion facility also does not provide a means of querying the assertion-status flag in a class. As a slight abuse to the assertion facility, however, the following idiom sets variable assertionsEnabled to the current class assertion status:

boolean assertionsEnabled = false;
assert assertionsEnabled = true;

This idiom abuses the assertion facility by using an assertion to achieve a desired side effect. Expressions within an assert statement should not produce side effects, since doing so exposes program execution to potentially different behavior with and without assertions enabled. You should use assertions to produce more reliable programs, not less reliable ones.

Finally, caution must guide the development of the expressions used in assert statements. In addition to not producing side effects, assertions should not alter normal program execution. As a pathological example, the following class enters an infinite recursion when assertions are enabled and only stops when a StackOverflowError brings the program to a crashing halt:

public class Pathological
{
  private String ping( boolean test )
  {
    System.err.print( "ping ... " );
    assert test : pong( test );
    return null;
  }
  private String pong( boolean test )
  {
    System.err.print( "pong ... " );
    assert test : ping( test );
    return null;
  }
  public static void main( String[] args )
  {
    Pathological pathological = new Pathological();
    System.err.println( "Pathological.ping( false ): " );
    pathological.ping( false );
  }
}

Though highly stylized, this example hints at the dangers of writing complex and oblique assert statements.

Be assertive

Assertions are an important addition to the Java programming language. By using assertions, developers can clearly demark the boundaries of acceptable program behavior.

This article exhaustively details the mechanics of using J2SE 1.4's assertions. Fortunately, you only need to digest most of these sticky details once. Using the simple assertion facility is relatively straightforward, so you can quickly add assertions to your development routine. Regrettably, neither the J2SE 1.4 compiler nor its runtime system enable the assertion facility by default. Each requires new command-line switches.

Part 2 of this article will discuss the methodological issues regarding assertions and how the Java assertion facility compares to Design by Contract. It also challenges the convention of using the Java exception mechanism for handling argument checking in public methods. Until then, remember to be assertive with your assertions.

Wm. Paul Rogers is a Java/object-oriented architect whose interests include teaching an understanding of Java design and implementation through stressing the first principles of fundamental object-oriented programming. He began using Java in the fall of 1995 in support of oceanographic studies conducted at the Monterey Bay Aquarium Research Institute, where he led the charge in utilizing new technologies to expand the possibilities of ocean science research. Paul has been using object-oriented methods for 10 years and works as an independent consultant in Bellingham, Wash., where he also teaches computer science at Western Washington University.

Learn more about this topic

  • Browse our Topical Index for more stories on the Java 2 Platform, Standard Edition: http://www.javaworld.com/channel_content/jw-j2se-index.shtml
  • Read more JavaWorld articles by Wm. Paul Rogers

1 2 Page 2
Page 2 of 2