J2SE 1.4 Assertion Facility

J2SE 1.4 Assertion Facility

By Rob Smith, OCI Software Engineer

April 2002


Introduction

With the recent release of J2SE 1.4, the Java language now includes a simple assertion facility.

What is an assertion?

Simply put, an assertion is a boolean expression that a developer declares to be true during program execution.

The new assertion facility is simple to use and can greatly increase software reliability by allowing developers to easily declare acceptable program behavior to help detect bugs early in the development process.

Syntax

Assertions are declared by using a new Java language keyword, assert. An assert statement takes one of two forms.

  1. assert expression1 ;
  2. assert expression1 : expression2

In both forms, expression1 is the boolean expression that the developer declares to be true during program execution. If expression1 is not a boolean expression, a compile-time error will occur.

In the second form expression2 is an expression that will be evaluated into a String expression and passed as a message to the assertion facility.

Here are a few examples of using the assert keyword:

  1. assert size() > 0;
  2. assert ref != null;
  3. assert age > 0.0 && age < 130.00: "Invalid age";

If assertions are enabled and expression1 evaluates to true, program execution will continue as usual.

If it's false, an AssertionError will be thrown.

If expression2 is specified in the statement, it will be passed as the message to the AssertionError.

Assertions may be disabled at runtime, effectively removing any performance hit due to their use.

AssertionError class

AssertionError is a subclass of Error rather than RuntimeException, because according to JSR 41, it will "discourage programmers from attempting to recover from assertion failures."

Also, by not extending from RuntimeException, it is not necessary for a developer to document the circumstances that may cause a method to throw an AssertionError with the javadoc @throws tag, since this is typically an implementation detail.

Compiling

Once assert statements have been added to your code, it will no longer compile under any pre-J2SE 1.4 compiler; by default a J2SE 1.4 compiler will not compile code that contains assert statements.

To compile a source file that contains assert statements, one must pass the -source 1.4 switch to the compiler.

The following compiles a file named Foo.java, which contains assert statements:

javac -source 1.4 Foo.java

By disabling assertions at runtime, it will not cause the assertion bytecode to be removed from class files.

For those who are concerned about reducing class loading time, the "conditional compilation" idiom can be used to remove all traces of assert statements from bytecode.

This can be done as follows:

  1. public class SomeClass {
  2. public static final boolean ASSERTS_ENABLED = false;
  3.  
  4. . . .
  5.  
  6. public void foo() {
  7. if (ASSERTIONS_ENABLED) {
  8. assert someCondition();
  9. }
  10. }
  11. }

Enabling and Disabling Assertions at Runtime

By default, the Java runtime system also does not support the assertion facility.

Two command line switches exist to allow one to enable or disable assertions at runtime.

  1. The -enableassertions (or -ea) switch will enable assertions.
  2. The -disableassertions (or -da) will disable assertions.

Assertions can be selectively enabled or disabled at the package or class level.

Another set of command line switches exists to enable or disable system-class assertions (i.e., classes within rt.jar) at runtime.

The -enablesystemassertions (or -esa) switch exists to enable asserts in all system classes, and the -disablesystemassertions (or -dsa) switch exists to disable asserts in all system classes.

If you wanted to enable assertions in all classes except system classes you would write:

java -ea <MainApp>

If you wanted to enable assertions for all classes except those in the com.ociweb.app.common package, you would write:

java -ea -da:com.ociweb.app.common <MainApp>

If you wanted to enable assertions in all classes except com.ociweb.app.common.Foo you would write:

java -ea -da:com.ociweb.app.common.Foo <MainApp>

Enabling and Disabling Assertions Programmatically

The following methods have been added to java.lang.ClassLoader:

public void clearAssertionStatus();

Clears any assertion settings associated with a ClassLoader.

This sets the default assert status for the ClassLoader to false and discards any current assertion settings. This may be useful to clear any assert status that was set from the command line.

public void setClassAssertionStatus(String className, boolean enabled);

Allows setting of assertion status on a per-class basis.

public void setDefaultAssertionStatus(boolean enabled);

Sets the default assertion status for all classes loaded by a ClassLoader.

public void setPackageAssertionStatus(String packageName, boolean enabled);

Allows setting of assertion status on a per-package basis.

It is worth noting that although methods can be invoked at any time, it will only affect classes loaded after the invocation. In other words, a class's assertion status is set once and for all when that class is loaded.

Usage Tips

Disable Assertions at Deployment

One of the most significant benefits of Java's new assertion facility is the ability to disable them at runtime.

By disabling assertions at deployment, the program will get a performance boost due to the assert statements not having to be evaluated. While it is a good idea to disable assertions at deployment, it is possible to enable assertions at any time by restarting the application with the appropriate command line switch.

By not removing assert statements from code before deployment, bugs that could not be duplicated in a testing environment can be found.

Assertions should not contain side effects

Since assertions may be disabled at runtime, assert statements should never call any method that may affect the state of the system. Assert statements should only invoke methods that test a condition.

Take a look at the following code:

  1. public class Mail {
  2.  
  3. private Address sendToAddress;
  4.  
  5. private Address senderAddress;
  6.  
  7. . . .
  8.  
  9. public void send() {
  10. assert validateAddress();
  11.  
  12. // Code which sends the mail follows
  13. . . .
  14. }
  15.  
  16. private boolean validateAddress() {
  17. if (!sendToAddress.isValid()) {
  18. // This code causes a side effect if assertions are enabled.
  19. if (senderAddress.isValid()) {
  20. sendToAddress = senderAddress;
  21. } else {
  22. return false;
  23. }
  24. } else {
  25. return true;
  26. }
  27. }
  28.  
  29. . . .
  30.  
  31. }

The code above contains side effects due to the assert statement, because the validateAddress() method may change the value of the sendToAddress variable. If this code is run with assertions disabled, it may cause undesired side effects.

Do not use assertions for argument checking in public methods

Valid arguments that may be passed to a public method are considered to be part of that method's contract. Therefore, it is not proper to use assertions to validate the arguments passed to a method.

Unfortunately, I believe this will be where most developers will use assertions. Lets face it; it's much easier to write:

  1. public void setName(String name) {
  2. assert name != null : "name cannot be null";
  3. this.name = name;
  4. }

than:

  1. public void setName(String name) {
  2. if (name == null) {
  3. throw new NullPointerException("name cannot be null");
  4. }
  5. this.name = name;
  6. }

I suggest developing a utility class like the following that will enable you to write one-liner checks without improperly using the assertion facility.

  1. public class ArgChecker {
  2. private ArgChecker() { } // Prevent instantiation.
  3.  
  4. public static void checkNotNull(Object ref, String message)
  5. throws NullPointerException {
  6. if (ref == null) {
  7. throw new NullPointerException(message);
  8. }
  9. }
  10.  
  11. public static void checkCondition(boolean expression, String message)
  12. throws IllegalArgumentException {
  13. if (!expression) {
  14. throw new IllegalArgumentException(message);
  15. }
  16. }
  17. }

Now one can simply write:

  1. public void setName(String name) {
  2. ArgChecker.checkNotNull(name, "name cannot be null");
  3. this.name = name;
  4. }

Use Assertions for Preconditions and Postconditions

A precondition is something that a method assumes to be true on invocation. As mentioned above, it is improper use of the assertion facility to validate arguments passed to public methods, but it is perfectly valid to use assertions to test other conditions before execution.

A postcondition is a condition that is assumed to be true after a method is finished executing.

  1. public class Car {
  2. private boolean started;
  3. private boolean hasFuel;
  4. private boolean parked;
  5.  
  6. public void drive() {
  7. assert started : "Must start car before driving it";
  8. assert hasFuel : "Must have fuel to drive car";
  9.  
  10. // Code to drive a car follows
  11. }
  12.  
  13. public void exitCar() {
  14. // Do work necessary to exit car
  15.  
  16. assert parked : "Cannot exit car until you put it in park";
  17. }
  18. }

In the above example, a precondition to the drive() method would be the started and hasFuel conditions. Without being started and without fuel, it is going to be impossible to drive a car.

In the above example, a postcondition to the exitCar() method would be parked condition. If a car is exited before being put in park, trouble is bound to happen.

Use assertions rather than making assumptions

A good use of assertions is placing them in your code where assumptions about program behavior exist. If the execution of an else clause in an else-if condition implies a condition, it should be changed to use an assert statement to test the condition.

For example:

  1. if (x == 1) {
  2. . . .
  3. } else if (x == 2) {
  4. . . .
  5. } else { // x == 3
  6. . . .
  7. }

The last comment should be replaced with:

assert x == 3;

Another good use of assertions is placing them in switch statements without a default case.

For example:

  1. switch(genderType) {
  2. case Gender.FEMALE:
  3. . . .
  4. break;
  5. case Gender.MALE:
  6. . . .
  7. break;
  8. }

It is assumed that one of the two case statements will always be executed.

To add more confidence to this assumption, simply add the following default case to the switch construct:

default:
    assert false : "Invalid genderType";

One more good use of assertions is placing them in parts of code assumed to be unreachable.

For example:

  1. for (. . .) {
  2. if (. . .) {
  3. return;
  4. }
  5. }
  6. // Execution will never reach this point.

The final comment should be replaced with:

assert false;

Summary

Assertions are a welcome addition to the Java language. Although simple to use, they can have a major impact on software quality.

By using assertions properly, you're more likely to catch bugs early in the development cycle, and you provide documentation about acceptable program behavior for other developers that read your code.

Since assertions may be disabled at runtime, there is no concern about taking a performance hit due to their use. So use them early and often in the development process and remember to always be assertive.

References



Software Engineering Tech Trends (SETT) is a regular publication featuring emerging trends in software engineering.


secret