.
.
Core Java
Technologies Technical Tips
.
   View this issue as simple text November 4, 2003    

In this Issue

Welcome to the Core Java Technologies Tech Tips for November 4, 2003. Here you'll get tips on using core Java technologies and APIs, such as those in Java 2 Platform, Standard Edition (J2SE).

This issue covers:

-Handling Exceptions
-Using the TimeZone Class

These tips were developed using Java 2 SDK, Standard Edition, v 1.4.

This issue of the Core Java Technologies Tech Tips is written by Daniel H. Steinberg, Director of Java Offerings for Dim Sum Thinking, Inc, and editor-in-chief for java.net.

See the Subscribe/Unsubscribe note at the end of this newsletter to subscribe to Tech Tips that focus on technologies and products in other Java platforms.

.
.

HANDLING EXCEPTIONS

Often programmers who are new to the Java programming language see exceptions as little more than annoyances to be ignored and dismissed. The advantage of handling exceptions is that you are forced to think about what could go wrong with your code at runtime. This Tech Tip will present techniques for handling exceptions. Here you'll learn why it's best to deal with exceptions close to their source and as specifically as possible. The goal of exception handling is to help users better run your software.

Consider what happens when a user tries to read from a file. The user specifies the name of the file as a command line parameter. In response, a File object is created from the name of the file (a String). A FileReader object is created to read the contents of the File. A BufferedReader is then chained to facilitate the reading of the text contents of the File.

What is not being taken into account are all of the things that could go wrong. For example, the user might not specify a file name when the program is run. Or the user might specify a file name, but no file exists that corresponds to that name. Taking account of things that could go wrong is what exception handling is about.

A good way to see the value of handling exceptions is to see what happens when you do not take any particular exception handling actions. Let's start with the following program, BadFileAccess:

   import java.io.File;
   import java.io.FileReader;
   import java.io.BufferedReader;

   public class BadFileAccess {

   // do NOT code like this

      public static void main(String[] args) {
        String fileName = args[0];
        File aFile = new File(fileName);
        FileReader fileReader = new FileReader(aFile);
        BufferedReader buffReader =
                       new BufferedReader(fileReader);
        while (true) {
          System.out.println(buffReader.readLine());
        }
      }
   }

BadFileAccess does not compile. If you try to compile the program, the compiler complains that there are two unreported exceptions: a java.io.FileNotFoundException and a java.io.IOException. FileNotFoundException extends IOException, which in turn, extends java.io.Exception. Instead of taking some specific actions to handle the exceptions, you might simply treat the exceptions generically, that is, by taking advantage of the exceptions hierarchy. In other words, you might consider both exceptions as instances of the Exception class like this:

   import java.io.File;
   import java.io.FileReader;
   import java.io.BufferedReader;

   public class BadFileAccess {

      // do NOT code like this

      public static void main(String[] args) {
        String fileName = args[0];
        File aFile = new File(fileName);
        try {
          FileReader fileReader = new FileReader(aFile);
          BufferedReader buffReader =
                         new BufferedReader(fileReader);
          while (true) {
            System.out.println(buffReader.readLine());
          }
        } catch (Exception e) {
          // especially avoid coding like this
        }
      }
   }

Compile the code. You should see that it now compiles. What you don't see yet is that it contains many runtime errors. To see one of these errors, run the code without specifying a parameter, like this:

   java BadFileAccess

When you try this, you should get an ArrayIndexOutOfBoundsException. The exception is thrown by the reference to args[0] in the program. The exception forces the program to quit. This might be what you want to happen if a user tries to run the program without specifying a file name. But seeing ArrayIndexOutOfBoundsException might not help an end user realize that they have to to enter the name of a legitimate file.

Now let's look at a what you could do to handle the exception. One tempting approach is to use the technique described in the January 09, 2001 Tech Tip titled Handling Uncaught Exceptions. Using that technique, you include the line

   String fileName = args[0];

in the try block. Here's what the BadFileAccess program looks like with the new line:

   import java.io.File;
   import java.io.FileReader;
   import java.io.BufferedReader;

   public class BadFileAccess {

      // do NOT code like this
      // this is the WORST version

      public static void main(String[] args) {
        try{
          String fileName = args[0];
          File aFile = new File(fileName);
          FileReader fileReader = new FileReader(aFile);
          BufferedReader buffReader =
                         new BufferedReader(fileReader);
          while (true) {
            System.out.println(buffReader.readLine());
          }
           } catch (Exception e) {
       // especially avoid coding like this
           }
      }
   }

Compile and run the code as before. The program compiles, but now there's no indication of the ArrayIndexOutOfBoundsException when you run the program. So this technique is not a good one for alerting the user to the problem. What you need to do here is trap the ArrayIndexOutOfBoundsException before the catch block for the generic Exception, and provide a helpful message to the user. Here's what the BadFileAccess program looks like with the addition of the trapping code and the helpful message:

   import java.io.File;
   import java.io.FileReader;
   import java.io.BufferedReader;

   public class BadFileAccess {

      // do NOT code like this

      public static void main(String[] args) {

        try {
          String fileName = args[0];
          File aFile = new File(fileName);
          FileReader fileReader = new FileReader(aFile);
          BufferedReader buffReader =
            new BufferedReader(fileReader);
          while (true) {
            System.out.println(buffReader.readLine());
          }
        } catch (ArrayIndexOutOfBoundsException e) {
          System.out.println("correct usage: " +
            "java BadFileAccess <pathToFile>");
        } catch (Exception e) {
          // especially avoid coding like this
        }
      }
   }

Compile and run the updated program as before. When you run it, you should see the message:

   correct usage: java BadFileAccess <pathToFile>

It's easy to imagine a main() method in the program where different Arrays are accessed. In that case, you might not want the same message issued for all ArrayIndexOutOfBoundsExceptions. In general, it's best to handle each exception as close to its cause as possible. To do this, you need to move the line

   String fileName = args[0];

and declare and initialize the variable outside of the try block like this:

   import java.io.File;
   import java.io.FileReader;
   import java.io.BufferedReader;

   public class BadFileAccess {

      // do NOT code like this

      public static void main(String[] args) {
        String fileName = null;
        try {
          fileName = args[0];
        } catch (ArrayIndexOutOfBoundsException e) {
          System.out.println("correct usage: " + "" +
            "java BadFileAccess <pathToFile>");
        }
        try {
          File aFile = new File(fileName);
          FileReader fileReader = new FileReader(aFile);
          BufferedReader buffReader =
            new BufferedReader(fileReader);
          while (true) {
            System.out.println(buffReader.readLine());
          }
        } catch (Exception e) {
          // especially avoid coding like this
        }
      }
   }

Now let's handle the case of a user specifying an invalid file name. Assume in this example that the file named noFile does not exist. Execute the following command:

   java BadFileAccess noFile.txt

The good news is that nothing seems to be amiss. When you execute the command, you should not see an error message. Of course, that is exactly the problem. You should get some indication that no file exists with the name noFile.txt. Recall that the FileReader constructor can throw a FileNotFoundException. To handle the invalid file type of exception, add another catch block:

   import java.io.File;
   import java.io.FileReader;
   import java.io.BufferedReader;
   import java.io.FileNotFoundException;

   public class BadFileAccess {

      // do NOT code like this

      public static void main(String[] args) {
        String fileName = null;
        try {
          fileName = args[0];
        } catch (ArrayIndexOutOfBoundsException e) {
          System.out.println("correct usage: " +
            "java BadFileAccess <pathToFile>");
        }
        try {
          File aFile = new File(fileName);
          FileReader fileReader = new FileReader(aFile);
          BufferedReader buffReader =
            new BufferedReader(fileReader);
          while (true) {
            System.out.println(buffReader.readLine());
          }
        }catch (FileNotFoundException e){
          System.out.println(" There is no file at " +
            fileName);
        } catch (Exception e) {
          // especially avoid coding like this
        }
      }
   }

Compile the program and run it, specifying noFile.txt as the file name. You should now see the message:

   There is no file at noFile.txt

At this point, you have added code that covers two exceptional cases: running the program without specifying a file name, and running the program specifying an invalid file name. But there's more that you can do to help the user. For example, you can indicate that not specifying a file name or specifying an invalid file name leads to a problem in locating the file. In addition, you can create your own exception depending on the situation. For example, if no file name is specified, you create a FileNotFoundException with an appropriate error message. You are no longer catching an ArrayIndexOutOfBoundsException. You test to see if there is one command line parameter. If there is, you try to open a file at that location. If there is not, you throw a new FileNotFoundException. If no file exists at the path the user specifies, you create a FileNotFoundException with an error message appropriate for that situation.

    import java.io.File;
    import java.io.FileReader;
    import java.io.BufferedReader;
    import java.io.FileNotFoundException;

    public class BadFileAccess {

     // do NOT code like this

     private static String fileName;

     public static void main(String[] args) {
         try {
           FileReader fileReader = new FileReader(
                                         getFile(args));
           BufferedReader buffReader =
             new BufferedReader(fileReader);
           while (true) {
             System.out.println(buffReader.readLine());
           }
         }catch (FileNotFoundException e){
           System.out.println(
                          "Could not locate a file: " +
                              e.getMessage());
         } catch (Exception e) {
           // especially avoid coding like this
         }
       }

       private static File getFile(String[] args)
                         throws FileNotFoundException {
         if(args.length == 1){
           fileName = args[0];
         } else {
           throw new FileNotFoundException(
                    "correct usage: " + 
                    "java BadFileAccess <pathToFile>");
         }
         File aFile = new File(fileName);
         if (aFile.exists()) {
           return aFile;
         } else {
           throw new FileNotFoundException(
             "There is no file at "  + fileName);
         }
       }
    }

Compile the program and run it without specifying a file name. The program throws a FileNotFoundException, and displays the message:

   Could not locate a file: 
   correct usage: java BadFileAccess <pathToFile>

Run the program with an invalid file name, for example:

   java BadFileAccess noFile.txt

The program throws a FileNotFoundException, and displays the message:

   Could not locate a file: 
   There is no file at noFile.txt

You can avoid creating multiple instances of a FileNotFoundException by following the advice of the April 22, 2003 Tech Tip on reusing exceptions. That tip shows how to use a Singleton, setting its properties for each instance of the same exception. Alternatively, you can handle the exception in the getFile() method without rethrowing.

In any case, if you read from an actual text file, the following block causes a problem that you have yet to address.

   while (true) {
            System.out.println(buffReader.readLine());
   }

Here's an example of the problem. Create a plain text file named realFile.txt. Fill it with sample text such as "This is a sample document." Now run your application like this.

   java BadFileAccess realFile.txt

You should see output that contains the line "This is a sample document." That line is followed by the word "null" on succeeding lines. The "null" lines continue to fill the output until you halt execution. This continuous result happens because there is nothing in your program to stop reading from the file when it reaches the end.

What can you do to help the user here? The readLine() method from the BufferedReader class can throw an IOException. There is not much users can do if they encounter an IOException at that point, but you can include a message that makes it clear to them what happened. For example, the following program, FileAccess, displays an explanatory message if there is an IOException:

import java.io.FileReader;
import java.io.FileNotFoundException;
import java.io.File;
import java.io.BufferedReader;
import java.io.IOException;

public class FileAccess {

   private static String fileName;

   public static void main(String[] args) {
     FileReader fileReader = null;
     try {
       fileReader = new FileReader(getFile(args));
       readFile(fileReader);

     } catch (FileNotFoundException e) {
       System.out.println("Could not locate a file: " +
         e.getMessage());
     }
   }

   private static File getFile(String[] args)
     throws FileNotFoundException {
     if(args.length == 1){
       fileName = args[0];
     }
      else {
       throw new FileNotFoundException("correct usage: "
         + "java FileAccess <pathToFile>");
     }
     File aFile = new File(fileName);
     if (aFile.exists()) {
       return aFile;
     } else {
       throw new FileNotFoundException(
         "There is no file at " + fileName);
     }
   }

   private static void readFile(FileReader fileReader) {
     BufferedReader buffReader =
                         new BufferedReader(fileReader);
     String temp = "";
     try {
       System.out.println("== Beginning of the fil" +
         fileName + " ==");
       while ((temp = buffReader.readLine()) != null) {
         System.out.println(temp);
       }
       System.out.println("==    End of the file: " +
         fileName + "    ==");
     } catch (IOException e) {
       System.out.println("There was a problem reading:"
         + fileName);
     }

   }
}

If you run the application like this:

      java FileAccess realFile.txt

You should see this:

   == Beginning of the file: realFile.txt ==
   This a sample document.
   ==    End of the file: realFile.txt    ==

For more information about handling exceptions, see Catching and Handling Exceptions in the Java Tutorial.

.
.

USING THE TIMEZONE CLASS

Often people need to communicate and coordinate across different time zones. This requirement also presents itself in software -- the code you write might need to communicate and coordinate across time zones. Because of this, your code might need to account for time zone differences in various locations. For example, it might need to determine whether a remote location does or does not observe Daylight Saving Time. This tip shows you how to use the TimeZone class to retrieve information about the local time in other locations.

As a first example, let's use the TimeZone class to list all of the available TimeZones. Given the twenty-four hours in a day, it might surprise you to find more than five hundred different TimeZones identified. The examples here are displayed with US formatting. For information on how to display the time zone information in a format that matches your locale see the June 24, 2003 Tech Tip Internationalizing Dates, Times, Months, and Days of the Week.

In the example you get an array of all of the available TimeZone IDs. It then iterates through the list and shows the ID with the longer display name and the standard acronym.

   import java.util.TimeZone;

   public class ZonesAndIDs {

      public static void main(String[] args) {

        String[] timeZoneIds = 
                            TimeZone.getAvailableIDs();
   
        for (int i = 0; i < timeZoneIds.length; i++) {
          TimeZone tz
                = TimeZone.getTimeZone(timeZoneIds[i]);
          String zone = tz.getDisplayName(true,
                                       TimeZone.SHORT);
          String zoneName = tz.getDisplayName();
   
          System.out.println(zone + " \t \t \t"
                                  + zoneName + "  ("
                                  + tz.getID() + ")");
        }
      }
   }

When you run the ZonesAndIDs program, you should see 557 entries corresponding to the many different TimeZone IDs. That might seem like a lot. You will see why you need more than a couple of dozen. Here is a sample listing from the output.

GMT+08:00                       GMT+08:00  (Etc/GMT-8)
HKT                     Hong Kong Time  (Hongkong)
CST                     China Standard Time  (PRC)
SGT                     Singapore Time  (Singapore)
CHOT                    Choibalsan Time  (Asia/Choibalsan)
TPT                     East Timor Time  (Asia/Dili)
EIT                     East Indonesia Time  (Asia/Jayapura)
KST                     Korea Standard Time  (Asia/Pyongyang)
KST                     Korea Standard Time  (Asia/Seoul)
JST                     Japan Standard Time  (Asia/Tokyo)
YAKST                   Yakutsk Time  (Asia/Yakutsk)
GMT+09:00                       GMT+09:00  (Etc/GMT-9)
JST                     Japan Standard Time  (JST)
JST                     Japan Standard Time  (Japan)
PWT                     Palau Time  (Pacific/Palau)
KST                     Korea Standard Time  (ROK)

If you did not include the actual IDs it would appear that there are duplicate entries. For instance there are two entries for Japan Standard Time -- one has the ID JST, the other has the ID Japan. Another entry for Japan Standard Time appears under GMT+08:00 -- it has the ID Asia/Tokyo. You can also see that the acronyms in the leftmost column are not unique for each ID, and not even unique for different time zones. The CST in this listing corresponds to China Standard Time. There is also a CST for Central Standard Time in the US.

Once you know a TimeZone ID for a particular location, you can use it to get the local time there. In the next example, a TimeZone object is created from an ID passed in as a command line argument. This TimeZone object is used by a DateFormat object to calculate the correct local time and to display that time with the correct short descriptive name for the TimeZone.

   import java.util.TimeZone;
   import java.util.Date;
   import java.text.DateFormat;

   public class WorldClock {
      public static void main(String[] args) {

        if (args.length == 1) {
          TimeZone zone = TimeZone.getTimeZone(args[0]);
          System.out.println(
                  "Time Zone for " + args[0] + " is " + 
                  zone.getDisplayName());
          getTimeInZone(zone);
        }  else {
          System.out.println(
                            "usage: java WorldClock " + 
                            "< Time Zone ID> ");
        }
      }

      private static void getTimeInZone(TimeZone zone){
        Date now = new Date();
        DateFormat dateFormat =
          DateFormat.getDateTimeInstance(DateFormat.FULL,
                                         DateFormat.FULL);
        dateFormat.setTimeZone(zone);
        System.out.println("Where currently it is "
                                + dateFormat.format(now));

      }
   }

Run the WorldClock program and specify an ID for a specific TimeZone. For example, if you specify the ID America/Los_Angeles, the program displays the current time in Los Angeles, California. Suppose the local time is 5:30 A.M. on October 25, 2003 on the East Coast of the United States, then:

   java WorldClock America/Los_Angeles

results in the following:

   Time Zone for America/Los_Angeles is Pacific 
   Standard Time
   Where currently it is Saturday, October 25, 2003 
   2:30:00 AM PDT

If you make an error entering the TimeZone ID, it will most likely not be caught. In this case, the TimeZone will default to GMT. For example:

   java WorldClock America/LosAngeles

   Time Zone for America/LosAngeles is Greenwich Mean 
   Time
   Where currently it is Saturday, October 25, 2003 
   9:30:00 AM GMT

You can also perform these calculations by first obtaining the TimeZone's offset from UTC (Coordinate Universal Time). You can do this by using either the getOffset() or the getRawOffset() methods.

Some locations further adjust their clocks based on the time of the year. For example, much of Europe and North America observe Daylight Saving Time. This means that clocks in those locations are set back an hour in the Autumn, and forward an hour in the Spring. The result for people is that it gets lighter an hour earlier in the Winter than it might if the clocks remain unchanged. It also gets darker an hour earlier. Programs that depend on knowing what time it is elsewhere must determine which locations adjust their clocks and when.

The next example program, DaylightSavingTime, explores the results of moving a date forward or backward in time using the add() method in the Calendar class. The TimeZone class maintains the integrity of the time information for such an event in localities you have not considered. In this example, you pass in two parameters. The first is the String corresponding to the TimeZone ID. The second is the number of hours that are added to the Calendar object.

   import java.util.TimeZone;
   import java.util.Calendar;
   import java.util.GregorianCalendar;
   import java.text.DateFormat;

   public class DaylightSavingTime {
      public static void main(String[] args) {
        if (args.length == 2) {
          TimeZone zone = TimeZone.getTimeZone(args[0]);
          System.out.println("Time Zone for " + args[0]
            + " is " + zone.getDisplayName());
          int timeLapse = Integer.parseInt(args[1]);
          changeTime(zone, timeLapse);
        } else {
          System.out.println(
                     "usage:java DaylightSavingTime" + 
                     " < Time Zone ID> <hours to add>");
        }
      }

      private static void changeTime(TimeZone zone,
                                     int timeLapse) {
        DateFormat dateFormat = 
          DateFormat.getDateTimeInstance(
                   DateFormat.FULL, DateFormat.FULL);
        dateFormat.setTimeZone(zone);

        System.out.println("The beginning time is ");
        Calendar cal = 
            new GregorianCalendar(2003, 9, 25, 5, 30);

        System.out.println("\t"
          + dateFormat.format(cal.getTime()));
        System.out.println("After " + timeLapse
                         + " hours it will be ");

        cal.add(Calendar.HOUR_OF_DAY, timeLapse);
        System.out.println("\t" + 
               dateFormat.format(cal.getTime()));

      }
   }

Run DaylightSavingTime first for TimeZone ID America/Louisville, and specify 4 as the number of hours to be added:

   java DaylightSavingTime America/Louisville 4

As expected, four hours are added to the TimeZone display:

   Time Zone for America/Louisville is Eastern Standard 
   Time
   The beginning time is Saturday, October 25, 2003 
   5:30:00 AM EDT
   After 4 hours it will be Saturday, October 25, 2003 
   9:30:00 AM EDT

You can easily adjust the time and see the results for a given TimeZone ID. You can roll the clock back as well. For example, if you pass in America/Louisville and -10:

   java DaylightSavingTime America/Louisville -10

You should see the following:

   Time Zone for America/Louisville is Eastern Standard 
   Time
   The beginning time is Saturday, October 25, 2003 
   5:30:00 AM EDT
   After -10 hours it will be Friday, October 24, 2003 
   7:30:00 PM EDT

Notice that the hours changed and so did the days. When you use the Calendar class add() method, the changes overflow to the larger fields. So you can imagine forwarding a mechanical watch that has a date display. If you forward the time twenty-four hours, then you expect the date to increment as well. If instead you want to set the hours without worrying about the effect on days, months, and years, use the roll() method. This is analogous to resetting a digital clock with separate controls for minutes and hours. There you might want to forward the minutes without worrying about accidentally incrementing the hours.

Run DaylightSavingTime again with the parameters America/Louisville and 24:

   java DaylightSavingTime America/Louisville 24

The results might surprise you:

   Time Zone for America/Louisville is Eastern Standard 
   Time
   The beginning time is Saturday, October 25, 2003 
   5:30:00 AM EDT
   After 24 hours it will be Sunday, October 26, 2003 
   4:30:00 AM EST

Notice that the day and date has incremented as you would expect, but the hours appear to increase only by twenty-three. The reason is that the clocks "fall back" an hour during the early hours of October 26, 2003 in Louisville. The time has indeed been incremented by twenty-four hours, and the clock change has also been accounted for. If instead you run DaylightSavingTime for US/East-Indiana and 24 hours:

   java DaylightSavingTime US/East-Indiana 24

you get the following:

   Time Zone for US/East-Indiana is Eastern Standard 
   Time
   The beginning time is Saturday, October 25, 2003 
   4:30:00 AM EST
   After 24 hours it will be Sunday, October 26, 2003 
   4:30:00 AM EST

Here the time is incremented twenty-four hours but the US/East-Indiana location does not observe Daylight Saving Time.

.
.
.

Reader Feedback

  Very worth reading    Worth reading    Not worth reading 

If you have other comments or ideas for future technical tips, please type them here:

 

Have a question about Java programming? Use Java Online Support.

.
.

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


Comments? Send your feedback on the Core Java Technologies Tech Tips to: http://developers.sun.com/contact/feedback.jsp?category=newslet

Subscribe to other Java developer Tech Tips:

- Enterprise Java Technologies Tech Tips. Get tips on using enterprise Java technologies and APIs, such as those in the Java 2 Platform, Enterprise Edition (J2EE).
- Wireless Developer Tech Tips. Get tips on using wireless Java technologies and APIs, such as those in the Java 2 Platform, Micro Edition (J2ME).

To subscribe to these and other JDC publications:
- Go to the JDC Newsletters and Publications 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 Core Java Technologies Tech Tips archives at:
http://java.sun.com/jdc/TechTips/index.html


Copyright 2003 Sun Microsystems, Inc. All rights reserved.
4150 Network Circle, Santa Clara, CA 95054 USA.


This document is protected by copyright. For more information, see:
http://java.sun.com/jdc/copyright.html


Java, J2SE, J2EE, J2ME, and all Java-based marks are trademarks or registered trademarks (http://www.sun.com/suntrademarks/) of Sun Microsystems, Inc. in the United States and other countries.

Sun Microsystems,
Inc.
.
.