|
Tech Tips Archive
April 9, 2002
WELCOME to the Java Developer Connection (JDC) Tech Tips, April 9, 2002. This issue covers:
These tips were developed using Java 2 SDK, Standard Edition, v 1.4.
This issue of the JDC Tech Tips is written by Glen McCluskey.
USING ASSERTIONS
Assertions are a new feature in Java 2 Platform, Standard
Edition J2SE version 1.4. An assertion is a program
statement containing a boolean expression, that a programmer
believes to be true at the time the statement is executed.
Assertions are used during development as a sort of internal
sanity check, and typically are disabled when an application is
deployed. The Java implementation of assertions involves both
a language change (the assert keyword) and support in the library
(java.lang.AssertionError).
This tip looks at some of the details around using assertions.
It also discusses where you might want to employ assertions in
your Java programs. Let's start with a simple example:
public class AssertDemo1 {
static void f() {
assert false;
}
public static void main(String args[]) {
try {
f();
}
catch (AssertionError e) {
System.out.println("exception: " + e);
}
}
}
|
If you compile and run this program by saying:
javac -source 1.4 AssertDemo1.java
java -ea AssertDemo1
the result is:
exception: java.lang.AssertionError
The -source option is new for J2SE v 1.4. It's a special
compilation switch that is required because assert is now
a keyword rather than an identifier. Programs that use assert as
an identifier will break if assert is assumed to be a keyword.
The switch is used as a transition mechanism.
The -ea switch is used to enable assertions at application run
time. Without this switch, assert statements are ignored, but are
still present in the bytecodes generated by the compiler.
If assertions are enabled, and the Java interpreter encounters
a statement of the form:
assert boolean-expression;
it evaluates the expression. If the expression is false, the Java
interpreter throws an AssertionError exception. Because
assertions can be disabled, the expression that's evaluated
should be free of side effects, that is, it should not change any
state that is visible after evaluation is complete.
This example demonstrates catching an AssertionError exception.
An assertion failure typically represents a serious problem, not
one that is necessarily recoverable. The fact that AssertionError
is a subclass of Error reinforces this idea.
How can you tell from within a program whether assertions are
enabled? Here's a simple way to do that:
public class AssertDemo2 {
public static void main(String args[]) {
boolean enabled = false;
assert enabled = true;
System.out.println("Assertions are " +
(enabled ? "enabled" : "disabled"));
}
}
|
If you compile this program:
javac -source 1.4 AssertDemo2.java
and then say:
java -ea AssertDemo2
the result is:
Assertions are enabled
If you say:
java AssertDemo2
the result is:
Assertions are disabled
The following line in the program:
assert enabled = true;
is executed only if assertions are enabled, and has the effect of
setting enabled to true. If assertions are not enabled, the
enabled variable keeps its false value.
Let's look at another example:
class LocalClass {
static void f(int x) {
assert x < 0 : "x is not < 0";
}
}
public class assertdemo3 {
public static void main(string args[]) {
assert false;
localclass.f(59);
}
}
|
If you run this program by saying:
javac -source 1.4 AssertDemo3.java
java -ea:LocalClass AssertDemo3
the result is:
java.lang.AssertionError: x is not < 0
at localclass.f(assertdemo3.java:3)
at assertdemo3.main(assertdemo3.java:10)
It is possible to selectively enable assertions for specific
classes or packages, using the special form of the -ea switch. In
the AssertDemo3 example, the assertion at the beginning of the
main method is ignored. Note also that there are new features in
java.lang.ClassLoader for manipulating assertion status.
Previous examples illustrated assert statements that look like
this:
assert boolean-expression;
Notice that the AssertDemo3 example illustrates an alternative
form of the statement:
assert boolean-expression : expression;
The boolean expression is evaluated in either form. If the
expression is false, the Java interpreter throws an
AssertionError exception. In the:
assert boolean-expression;
case, the interpreter invokes the default (parameterless)
AssertionError constructor. In the:
assert boolean-expression : expression;
case, the second expression is evaluated, and the appropriate
AssertionError constructor is invoked. The AssertionError
constructor is overloaded to accept an argument of any primitive
or object type.
What about performance? What is the cost of assertions? If
assertions are disabled, then the cost is what is required to
check a global state flag. This is a flag that indicates
whether assertions are in force. If you want to see what this
check looks like in the bytecodes, you can say:
javap -c -classpath . LocalClass
for the last example above. Here are the bytecodes for the f
method:
Method void f(int)
0 getstatic #7
3 ifne 20
6 iload_0
7 iflt 20
10 new #8
13 dup
14 ldc #9
16 invokespecial #10
19 athrow
20 return
|
The first line of the bytecode is used to evaluate the assertion
status. If assertions are disabled, the method returns.
Otherwise, the assertion expression is evaluated. If the
expression is false, an AssertionError is thrown.
If assertions are enabled, the cost is what is required to check
the status, evaluate the boolean expression, and throw the
exception.
What about code size? What if assertions are disabled, and you
want to totally remove the assertion checking logic from your
code? There is no direct mechanism for doing this, but you can
use what is called the "conditional compilation idiom". Here's
an example:
class Globals {
private Globals() {}
public static final boolean DEBUG = true;
}
public class AssertDemo4 {
public static void main(String args[]) {
int x = 59;
if (Globals.DEBUG) {
assert x < 0 : "x is not < 0";
}
}
}
|
If you change DEBUG in this example to false, and then say:
javac -source 1.4 AssertDemo4.java
javap -c -classpath . AssertDemo4
the bytecodes for the main method are:
Method void main(java.lang.String[])
0 bipush 59
2 istore_1
3 return
There's no trace of assertion code. Note that this technique
depends on a Java compiler optimizing away the contents of a
never-executed block:
if (false) {
// block contents
}
Where are assertions useful? Let's consider a couple of examples.
The first one implements a method that computes the number of
minutes between two calendar dates:
import java.text.*;
import java.util.*;
public class AssertDemo5 {
/**
* Computes the number of minutes between
* two Dates.
*
* @param d1 the first Date
* @param d2 the second Date
*
* @return the absolute value of the number
* of minutes
*
* @exception NullPointerException if either
* Date null
*/
public static long getDateDiff(
Date d1, Date d2) {
//assert d1 != null && d2 != null;
if (d1 == null || d2 == null) {
throw new NullPointerException(
"d1 or d2 null");
}
// get the difference in milliseconds and
// take the absolute value
long diff = d1.getTime() - d2.getTime();
//diff = (diff < 0 ? -diff : diff);
// convert milliseconds to seconds
// and then to minutes
long res = diff / (1000 * 60);
// sanity check on the result
// it should be >= 0
assert res >= 0 :
"date difference is negative";
return res;
}
public static void main(String args[])
throws ParseException {
// set up a DateFormat for parsing dates
DateFormat df =
DateFormat.getDateInstance();
// set up a couple of dates
// and compute difference
Date d1 = df.parse("February 12, 2002");
Date d2 = df.parse("March 12, 2002");
long diff = getDateDiff(d1, d2);
System.out.println(
"difference = " + diff + " minutes");
}
}
|
Compile this program and run it with assertions enabled:
javac -source 1.4 AssertDemo5.java
java -ea AssertDemo5
the result is:
java.lang.AssertionError:
date difference is negative
at AssertDemo5.getDateDiff(AssertDemo5.java:29)
at AssertDemo5.main(AssertDemo5.java:44)
The getDateDiff method computes the number of minutes between
dates, and returns the absolute value of the result.
Unfortunately, the programmer failed to take the absolute value
after computing the number of minutes. But the programmer was
smart enough to add an assertion to the code. The assertion
captures the requirement that the result of the calculation is
a zero or positive value.
You can fix the programmer's mistake by uncommenting the
following line, which takes the absolute value of the diff
variable:
//diff = (diff < 0 ? -diff : diff);
If you recompile the program and run it with assertions enabled,
you get:
difference = 40320 minutes
There's another issue with this example. Why not use assertions
to check the passed-in parameter values d1/d2 for the getDateDiff
method? The d1/d2 references might be null. There's some commented
code showing how such assertion checking could be done:
assert d1 != null && d2 != null;
In this case, however, using an assertion isn't appropriate. The
reason is that checking non-null object references is part of the
public contract of the getDateDiff method. In other words, a check
is made at the top of the method to see if d1 or d2 are null. If
this logic is replaced by assertions, and assertion checking is
disabled, then the public contract will not be enforced.
Even without the null reference checking code, there's an
implicit contract within getDateDiff, in that object accesses
through a null reference are guaranteed to throw a
NullPointerException. Using assertions in this situation is not
dependable, and even if it was, the resulting exception would not
be a NullPointerException. Instead, it would be an AssertionError.
Here's a final example that illustrates another situation where
assertions are useful:
import java.util.Random;
public class AssertDemo6 {
static Random rn = new Random(0);
// original method, using random numbers
// and a switch statement
static void f1() {
switch (rn.nextInt(3)) {
case 0:
System.out.println("0");
break;
case 1:
System.out.println("1");
break;
case 2:
System.out.println("2");
break;
}
}
// method changed to use a wider range of
// random numbers, but switch statement
// not updated
static void f2() {
switch (rn.nextInt(4)) {
case 0:
System.out.println("0");
break;
case 1:
System.out.println("1");
break;
case 2:
System.out.println("2");
break;
//default:
// assert false;
}
}
public static void main(String args[]) {
for (int i = 1; i <= 10; i++) {
f1();
}
system.out.println("======");
for (int i = 1; i <= 10; i++) {
f2();
}
}
}
|
The AssertDemo6 code uses random numbers as part of a simulation
scheme. The f1 method generates a random number between 0 and 2,
and uses it to dispatch within a switch statement. Each case in
the switch is called approximately one third of the time. The f2
method is similar to f1, but the range of random numbers is
widened to 0-3. This means that each case should be called one
fourth of the time.
When you compile and run this program, the result is:
0
1
1
2
2
2
2
0
0
2
======
1
1
1
2
0
0
|
Ten values are printed from f1, but only six from f2, even though
f2 is called ten times. The problem is that the f2 method was not
completely updated when the random number range was widened from
0-2 to 0-3. There's a missing case in the switch statement.
Because there's no default case specified, the situation where
the random number is 3 is silently ignored.
The solution to this problem is of course to add the missing
case. But it's also important to add a default case with an
assertion to catch situations like this in the future.
Other situations where you might want to use assertions include
enforcing class invariants, or checking the arguments at the top
of a private method.
For more information about assertions, see Programming With Assertions. Also see section 14.20 Unreachable Statements, in The Java Language Specification Second Edition.
REPRESENTING CURRENCIES
java.util.Currency is a new class in J2SE v 1.4. It is used to
represent currencies, for example US dollars or Japanese yen.
Objects of the java.util.Currency class do not represent a
particular amount of currency, but the currency unit itself.
Let's look at a simple example of how you use the Currency class:
import java.util.*;
public class CurrDemo1 {
public static void main(String args[]) {
// get Currency instance for US
// locale and display symbol
Currency c1 = Currency.getInstance(
Locale.US);
System.out.println("US Dollar symbol = " +
c1.getSymbol());
// get symbol for US Dollar
// when used in Canada
System.out.println(
"US Dollar symbol in Canada = " +
c1.getSymbol(Locale.CANADA));
// get Currency instance for Japan
// based on locale
Currency c2 = Currency.getInstance(
Locale.JAPAN);
System.out.println(
"Currency code for Japan = " +
c2.getCurrencyCode());
// get Currency instance for Japan
// based on code
Currency c3 = Currency.getInstance("JPY");
// compare currency objects
if (c2 != c3) {
System.out.println(
"objects are unequal");
}
}
}
|
You designate a specific currency based on its ISO 4217 currency
code, for example "USD" or "JPY". Or you can specify a currency
based on a locale, for example "Locale.JAPAN". A list of currency
codes can be found at the BSI Currency Code Service ISO 4217 Maintenance Agency site.
The Currency.getInstance method returns a Currency object that is
instance-controlled. This means that there is only one instance
for each currency. So Currency objects can be compared using the
== operator (reference identity).
The Currency.getSymbol method returns the symbol for a currency,
for example "$" for the US dollar. If a symbol cannot be
determined, then the currency code is returned. The symbol for
a specific currency can vary based on the locale specified to
getSymbol. For example, in the United States, the US dollar
symbol is "$", but in Canada, the symbol is "USD".
Compile and run the CurrDemo1 program. The result is:
US Dollar symbol = $
US Dollar symbol in Canada = USD
Currency code for Japan = JPY
Here's another example. This program uses the Currency class to
perform financial calculations:
import java.math.*;
import java.util.*;
public class CurrDemo2 {
static void calculate(
Currency curr, String num, String denom) {
// set up BigDecimal values for
// numerator and denominator
BigDecimal d1 = new BigDecimal(num);
BigDecimal d2 = new BigDecimal(denom);
// get fraction digits and divide
int fracdig =
curr.getDefaultFractionDigits();
BigDecimal d3 = d1.divide(d2, fracdig,
BigDecimal.ROUND_DOWN);
// display result
System.out.println(
curr.getSymbol() + d1 + " / " +
d2 + " = " + curr.getSymbol() + d3);
}
public static void main(String args[]) {
Currency curr;
String num = "147";
String denom = "12";
// do calculation for US locale
curr = Currency.getInstance(Locale.US);
calculate(curr, num, denom);
// do calculation for Japanese locale
curr = Currency.getInstance(Locale.JAPAN);
calculate(curr, num, denom);
}
}
|
In the CurrDemo2 example, there are 147 units of some currency to
be divided into 12 parts. The BigDecimal class is used for the
calculations. The calculate method is passed a Currency object
along with the quantities 147 and 12.
The Currency object is queried to obtain the default fraction
digits for the currency. Then this number is used in the
BigDecimal division to scale the result. The default digits value
is 2 for the US dollar, and 0 for the Japanese yen.
The output of the the CurrDemo2 example is:
$147 / 12 = $12.25
JPY147 / 12 = JPY12
The Currency class gives you a standard way to represent
currencies in your applications.
For more information on the Currency class, see the
Internationalization section in the J2SE version 1.4 Summary of New Features and Enhancements.
IMPORTANT: Please read our Terms of Use and Privacy policies:
http://www.sun.com/share/text/termsofuse.html
http://www.sun.com/privacy/
http://developer.java.sun.com/berkeley_license.html
FEEDBACK
Comments? Send your feedback on the JDC Tech Tips to:
jdc-webmaster@sun.com
SUBSCRIBE/UNSUBSCRIBE
- To subscribe, go to the subscriptions page, choose the newsletters you want to subscribe to and click "Update".
- To unsubscribe, go to the subscriptions page, uncheck the appropriate checkbox, and click "Update".
- ARCHIVES
You'll find the JDC Tech Tips archives at:
http://java.sun.com/jdc/TechTips/index.html
- COPYRIGHT
Copyright 2002 Sun Microsystems, Inc. All rights reserved.
901 San Antonio Road, Palo Alto, California 94303 USA.
This document is protected by copyright. For more information, see:
http://java.sun.com/jdc/copyright.html
JDC Tech Tips
April 9, 2002
Sun, Sun Microsystems, Java, Java Developer Connection, and J2SE are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries.
|