Return-Path: Received: from pacific-carrier-annex.mit.edu by po10.mit.edu (8.9.2/4.7) id SAA07443; Tue, 9 Apr 2002 18:46:22 -0400 (EDT) Received: from hermes.sun.com (hermes.sun.com [64.124.140.169]) by pacific-carrier-annex.mit.edu (8.9.2/8.9.2) with SMTP id SAA07371 for ; Tue, 9 Apr 2002 18:46:22 -0400 (EDT) Date: Tue, 9 Apr 2002 15:46:22 PDT From: "JDC Tech Tips" To: alexp@mit.edu Message-Id: <13489390542828235@hermes.sun.com> Subject: JDC Tech Tips, April 9, 2002 (Assertions, Currencies) Precedence: junk Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: 7bit X-Mailer: Beyond Email J D C T E C H T I P S TIPS, TECHNIQUES, AND SAMPLE CODE WELCOME to the Java Developer Connection(sm) (JDC) Tech Tips, April 9, 2002. This issue covers: * Using Assertions * Representing Currencies These tips were developed using Java(tm) 2 SDK, Standard Edition, v 1.4. You can view this issue of the Tech Tips on the Web at http://java.sun.com/jdc/JDCTechTips/2002/tt0409.html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - USING ASSERTIONS Assertions are a new feature in Java(tm) 2 Platform, Standard Edition (J2SE(tm)) 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 (http://java.sun.com/j2se/1.4/docs/guide/lang/assert.html). Also see section 14.20 Unreachable Statements, in "The Java Language Specification Second Edition" (http://java.sun.com/docs/books/jls/). - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 (http://www.bsi-global.com/ Import+Export+Advice/Useful+Publications/Technical/TH42090.xalter). 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 (http://java.sun.com/j2se/1.4/docs/relnotes/features.html#i18n). . . . . . . . . . . . . . . . . . . . . . . . IMPORTANT: Please read our Terms of Use, Privacy, and Licensing 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, (http://developer.java.sun.com/subscription/), choose the newsletters you want to subscribe to and click "Update". - To unsubscribe, go to the subscriptions page, (http://developer.java.sun.com/subscription/), uncheck the appropriate checkbox, and click "Update". - To use our one-click unsubscribe facility, see the link at the end of this email: - 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 This issue of the JDC Tech Tips is written by Glen McCluskey. 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. To use our one-click unsubscribe facility, select the following URL: http://bulkmail.sun.com/unsubscribe?13489390542828235