Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

J2SE 1.4 premieres Java's assertion capabilities, Part 1

Understand the mechanics of Java's new assertion facility

  • Print
  • Feedback

Page 3 of 5

The following shows the result of running Foo with the command java Foo, in which the default runtime environment disables assertion checking:

foo.m1(  1 ): OK
foo.m1( -1 ): OK


With assertions disabled, neither m1(int) method call triggers an assertion. As previously described, enabling assertion requires the use of a command-line switch. Any of the following commands enables assertion checking in class Foo:

java -ea     Foo
java -ea:Foo Foo
java -ea:... Foo


The following shows the resulting output:

foo.m1(  1 ): OK
foo.m1( -1 ): Exception in thread "main" java.lang.AssertionError
        at Foo.m1(Foo.java:6)
        at Foo.main(Foo.java:17)


Calling method m1(int) with the parameter 1 fails to trigger the assertion. However, passing -1 violates the assertion that the parameter must be a positive integer. The Java runtime system reports the failed assertion through the use of a new class, java.lang.AssertionError.

Class AssertionError

The new assertion facility adds the class AssertionError to the java.lang package. AssertionError contains a default constructor and seven single-parameter constructors. The assert statement's single-expression form uses the default constructor, whereas the two-expression form uses one of the seven single-parameter constructors.

To understand which AssertionError constructor is used, consider how assertions are processed when enabled:

Evaluate expression1

  • If true
    • No further action
  • If false
    • And if expression2 exists
      • Evaluate expression2 and use the result in a single-parameter form of the AssertionError constructor
    • Else
      • Use the default AssertionError constructor


Since the assert statement in class Foo uses the single-expression form, the violation triggered in passing -1 to method m1(int) prompts the use of the default AssertionError constructor. The default constructor effectively uses java.lang.Throwable's default constructor to print an exception message that includes a textual stack trace description.

The assertion error output from running class Foo is lacking. We see that an AssertionError occurs in method m1 at line 6, but the output does not describe what went wrong. Fortunately, the assert statement's two-expression form provides this facility. As noted above, in the two-expression form, when expression1 evaluates as false, the assertion facility passes the result of evaluating expression2 to a single-parameter AssertionError constructor. Expression2 effectively acts as a String message carrier, meaning AssertionError's single-parameter constructors must convert the result of expression2 to a String. Covering all expression result-type possibilities requires a separate constructor for each of the seven string conversion rules detailed in The Java Language Specification, Section 15.18.1.1. Fortunately, developers can ignore this implementation detail and simply focus on using expression2 as a message carrier for describing the assertion error.

Class Bar, listed below, uses the two-parameter form for a simple assertion in method m1(int):

public class Bar
{
  public void m1( int value )
  {
    assert 0 <= value : "Value must be non-negative: value= " + value;
    System.out.println( "OK" );
  }
  public static void main( String[] args )
  {
    Bar bar = new Bar();
    System.out.print( "bar.m1(  1 ): " );
    bar.m1( 1 );
    System.out.print( "bar.m1( -1 ): " );
    bar.m1( -1 );
  }
}


The following shows Bar's output with assertions enabled:

bar.m1(  1 ): OK
bar.m1( -1 ): Exception in thread "main" java.lang.AssertionError: Value
must be non-negative: value= -1
        at Bar.m1(Bar.java:6)
        at Bar.main(Bar.java:17)


The output shows the String conversion of the expression2 result concatenated to the end of the exception message, before the textual stack trace. The detailed message certainly improves the exception message's usability. Since creating a reasonable error message is not difficult, developers should favor the assert statement's two-expression form.

As an aside, J2SE 1.4 also adds new capabilities to the java.lang.Throwable class that enable a cleaner formatting of stack trace information. The class FooBar listed below uses these new capabilities to format the exception message produced by the assertion error:

public class FooBar
{
  public void m1( int value )
  {
    assert 0 <= value : "Value must be non-negative: value= " + value;
    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
    {
      FooBar fooBar = new FooBar();
      System.out.print( "fooBar.m1(  1 ): " );
      fooBar.m1( 1 );
      System.out.print( "fooBar.m1( -1 ): " );
      fooBar.m1( -1 );
    }
    catch( AssertionError ae )
    {
      printAssertionError( ae );
    }
  }
}


The following output of running FooBar with assertions enabled displays the cleaner AssertionError reporting:

fooBar.m1(  1 ): OK
fooBar.m1( -1 ): AssertionError
   class=   FooBar
   method=  m1
   message= Value must be non-negative: value= -1


Assertions and inheritance

Assertions can also be disabled down to the class level. The switch -disableassertions, or -da for short, parallels the syntax of the assertion-enabling switch. A command line can contain as many enable- and disable-assertion switches as desired.

Since you can set assertion status at the class level, you can enable assertions for a particular class and disable them for that class's superclass. The classes Base and Derived, listed below, show the effect of differing assertion statuses in an inherited implementation:

// 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:

  • Print
  • Feedback

Resources
  • 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