In this post, you will learn to:
- State the use of assert statement.
- Explain implementation of assertion in code.
- Explain the use of internal invariants.
- State the use of control-flow invariants.
- Explain PreCondition, PostCondition, and Class Invariants
Using the ‘assert’ Statement
An assertion allows testing the correctness of any assumptions that have been made about the program. Assertion is achieved using the assert statement in Java. The assert statement is used with a boolean expression that is checked during runtime. The expression is believed to be true, but it generates an AssertError, if it is false.
There are two forms of the assert statement that are as follows:
- assert booleanExpression;
- assert booleanExpression : expression;
The first form, assert booleanExpression; evaluates the boolean expression and returns a true or false result.
The figure below shows the first form of declaring assertions.
The following is the syntax for providing the first form of assertion.
assert expression1;
where, expression1 is a boolean expression that is evaluated. If the expression evaluates to a false value, then an AssertionError exception is thrown with no detailed message. The following code demonstrates the first form of assertion with a boolean expression.
public class CalculateInterest{ public double calculateInterest(double principalAmount,double numberOfYears,double rateOfInterest){ //First form of assertion with only boolean expression assert (principalAmount>0.0); assert (numberOfYears>0.0); assert (rateOfInterest>0.0); return (principalAmount*numberOfYears*rateOfInterest)/100.0; }
If the method is invoked with the value in principalAmount less than 0.0, such as calculateInterest(-1200.5, 2.5, 3.45), then it throws AssertionError exception as java.lang.AssertionError. In this form, detailed information about the cause of assertion failure is not displayed.
The second form, assert booleanExpression : expression; acts just like the first form. In addition,if the boolean expression evaluates to false, the second expression is returned as a detailed message for the AssertionError. The message to be displayed is written in the second expression of the assert statement.
The figure below shows the second form of declaring assertions.
The following is the syntax for providing the second form of assertion.
assert expression1: expression2;
Where, expression1 is a boolean expression and expression2 is an expression having a value. The following code demonstrates the second form of assertion with a boolean expression and a detailed message.
public class CalculateInterest{ public double calculateInterest(double principalAmount,double numberOfYears,double rateOfInterest){ //Second form of assertion with boolean expression and value assert (principalAmount>0.0):"Invalid Entry"+principalAmount; assert (numberOfYears>0.0):"Invalid Entry"+numberOfYears); assert (rateOfInterest>0.0):"Invalid Entry"+rateOfInterest; return (principalAmount*numberOfYears*rateOfInterest)/100.0; } }
If the method is invoked with the value in principalAmount less than 0.0, such as calculateInterest(-1200.5, 2.5, 3.45), then it throws AssertionError exception as java.lang.AssertionError: Invalid Entry -1200.5. The second form displays an error message with the exact value that caused the assertion failure.
Enabling Assertions
By default, assertions are disabled. The syntax for enabling assertion statement in Java source code is:
java –ea CalculateInterest
Or
java –enableassertions CalculateInterest
By default, assertions are turned off in NetBeans, whereas they are on by default when you compile source code with javac. Hence, in NetBeans the following steps need to be taken to turn runtime assertion checking on. Follow the procedure to enable assertion in NetBeans:
- Right-click the Project icon in the Project panel at the left.
- Select Properties at the bottom of the drop-down menu.
- Click the Run choice in the left panel.
- In the VM Options text field at the right, enter -ea. ea stands for enable assertions. VM stands for Virtual Machine.
Disabling Assertions
The syntax for disabling assertion statement in Java source code is:
java –da CalculateInterest
Or
java –disableassertions CalculateInterest
Follow the procedure to disable assertion in NetBeans:
- Right-click the Project icon in the Project panel at the left.
- Select Properties at the bottom of the drop-down menu.
- Click the Run choice in the left panel.
- In the VM Options text field at the right, enter -da. da stands for disable assertions. VM stands for Virtual Machine.
Using Assertions
Assertion statements have advantages as well as disadvantages. Although, using assertion statements is recommended in most cases, in some cases, using these can be undesirable.
Appropriate uses of assertions are as follows:
Arguments to private methods
The following code snippet demonstrates the use of assertion on arguments to private methods.
private void setPrincipalAmount(double pAmount){ // Appropriate use assert pAmount > 0.0; // Setting member variable principalAmount=pAmount; }
Assertion is required in private method because it is not guaranteed that a private method’s requirements are checked in advance.
Conditions at the beginning of any method
The following code snippet demonstrates the use of assertion using related conditions at the beginning of any method.
private void calculateInterest(double pAmount, double nYears, double rInterest){ // appropriate use assert pAmount>0.0; assert nYears>0.0; assert rInterest>0.0; double interest=(pAmount*nYears*rInterest)/100.0; }
Conditional cases
The following code snippet demonstrates the use of assertions on arguments to public methods.
private int getVal() { if (x+y+z>10) { return 0; } else if (x+y>6) { return 1; } else { return 2; } } public void useGetVal() { int a = getValue(); // returns 0, 1, or 2 if (a==0) { } else if (a==1) { } else if (a==2) { } else { //Will never reach else condition assert false : "Invalid value of a"; }
Inappropriate uses of Assertions
Inappropriate uses of assertions are as follows:
Using assertions on arguments to public methods
The following code shows the inappropriate use of assertions on arguments.
public void setPrincipalAmount(double pAmount){ // Inappropriate use assert pAmount > 0.0; // Setting member variable principalAmount=pAmount; }
This use is inappropriate because a public method writer is bound to check requirements of the function.
Using assertions on command line arguments
The following code shows the inappropriate use of assertions on command line arguments.
public class CalculateInterest { static public void main( String args[] ) { // Inappropriate use assert args.length == 3; int principal = Integer.parseInt( args[0] ); int noOfYears = Integer.parseInt( args[1] ); int rateOfInterest = Integer.parseInt( args[2] ); } }
Use exceptions instead of assertion.
Reasons to use Assertions
Assertion statements can be used:
To replace comments
The following code shows the use of assertions to replace comments to test the correctness of the assumptions.
for (i=0;i<10;i++) { if (i%2 == 0) { } else if (i%2== 1) { } else { // Assumption that i%2 will be 2 assert i%2 == 2:i; // statements to be executed if i%2 == 2 evalutes to true. // It is an assumption that is generally placed in comments is // placed inside an assert statement to check the correctness //of the assumption } }
To replace code in default case
The following code demonstrates the given switch-case statement in which it is assumed that numberOfYear will never reach 4. Assertion statement has been placed with default to check the correctness of assumption.
switch(numberOfYear){ case 1: break; case 2: break; case 3: break; default: assert false: numberOfYear; break; }
To replace unreachable code
The following code demonstrates the use of assertions to identify the unreachable statements in a code.
for (i=0;i<5;i++){ if (i==3) return; // Unreachable code, an assertion statement has been placed below to // check correctness of assumption. assert false:i; }
In the beginning of the method
The following code demonstrates to use of assertions to check for some conditions that must be true, before the code in a method can be executed.
private double calculateInterest(double principalAmount,double numberOfYears,double rateOfInterest){ assert principalAmount>0.0 && principalAmount <20000.5 :"Invalid Entry"+principalAmount; return (principalAmount*numberOfYears*rateOfInterest)/100.0; }
After method invocation
Assertions can be used to check for conditions that must be true after the invoking a method. The following code checks the assumption that new value of length is greater than the old value after invoking a method.
newValue=changeLength(oldValue); assert (newValue> oldValue);
To check object’s state
Assertions can be used to consistency of an object’s state before and after invoking a method. The following code checks for inconsistency in object state after invoking changeLength() method.
Code Snippet:
newValue=changeLength(oldValue); assert hasValidState():newValue;
Use of Internal Invariants
An invariant is an assumption that is always true. An internal invariant is believed to be true within some part of the program. The following code snippet calculates the new value of principal amount depending on interest. The assumption in the program is that the interest value cannot be negative. This assumption can be tested for correctness using assertion.
calculatePrincipalAmount(double interest){ if (interest>5000.50) principalAmount = principalAmount + 500.5; else if (interest>10000.50) principalAmount = principalAmount + 1000.5; else // assumption: interest cannot be negative assert(interest>0.0):interest+"Invalid Interest entry"; }
Using Control-flow Invariants
A control-flow invariant is an assumption that one cannot execute some statement in certain area of code,that is, some part of the code is unreachable. An assertion statement can be placed to test such codes. The following code snippet shows an assertion statement that has been placed to test whether the code containing assertion is indeed unreachable.
public double calculatePrincipal(double rateOfInterest){ for (noOfYears=1.0; noOfYears<10.0; noOfYears++) { if(interest=5000.0) return interest; } //assumed to be unreachable. Assertion statement to test assumption assert false; } }
Design By Contract
The correctness of a program can be viewed as proof that the computation has been provided with correct input and has terminated with correct output. The method that invokes the computation has the responsibility of providing the correct input. This is known as precondition. If the computation is successful, then the computation is said to have satisfied the postcondition. Besides these two conditions, there is one more important invariant called class invariant.
The figure below shows the design by contract that comprises three constituents, also known as roles.
The following code demonstrates class invariants, precondition, and postcondition assertions.
public class HumanBeing{ // method for testing class invariant // checks if user entry of age falls within lifetime of human beings validateAge(int age) { // class invariant assert (person.age >= 0 && person.age = 0 && person.age 18): "Invalid age entry for vote"; public class HumanBeing{ // method for testing class invariant // checks if user entry of age falls within lifetime of human beings validateAge(int age) { // class invariant assert (person.age >= 0 && person.age 18): "Invalid age entry for vote"; // postcondition; assert(age<120):"Invalid age"; return true; } }
Precondition:
A condition that the invoker of an operation agrees to satisfy.
Postcondition:
A condition that the invoked method promises to achieve.
Class Invariant:
A class invariant is a type of internal invariant applicable to each instance of a class. This is not applicable when there is a change in state of an instance. A class invariant specifies the relationships among multiple attributes of a class that its instances must satisfy before and after each method call. For example, consider the assertion statement for person instance of HumanBeing class, assert person.age >= 0 && person.age < 120;
Class AssertionError
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 Excep 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 default constructor to print an exception message that includes a textual stack trace description.
The assertion error output from running class Excep is lacking. It is seen 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 earlier, 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 theresultofexpression2 to a String.
The following class Bar, 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 ); } }
Bar’s output with assertions enabled is as follows:
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.
The java.lang.Throwable class enables a cleaner formatting of stack trace information. The following code class FooBar, 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
Comparing Assertion and Exceptions
The frequent dilemma that new developers face is deciding when to use assertions and when to use exceptions. Both catch problems in the program, but the intended usage is very different. This is how exceptions differ in use from Assertions:
- An exception tells the user of the program that something went wrong.
- An assertion documents some assumption about the program.
- When an assertion fails, it points out a bug in the programming logic.
- Exceptions are created to deal with problems that might occur during the course of execution of the program.
- Assertions are written to state assumptions made by the program.
Exception Handling in Java
User-defined Exceptions in Java
Assertions in Java