|
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));
} catch (FileNotFoundException e) {
System.out.println(
"Could not locate a file: " +
e.getMessage());
}
readFile(fileReader);
}
private static File getFile(String[] args)
throws FileNotFoundException {
try {
fileName = args[0];
} catch (ArrayIndexOutOfBoundsException e) {
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 file: " +
fileName + " ==");
while ((temp = buffReader.readLine()) != null) {
System.out.println(temp);
}
} catch (IOException e) {
System.out.println("There was a problem reading:"
+ fileName);
}
System.out.println("== End of the file: " +
fileName + " ==");
}
}
If you run the application like this: java BadFileAccess 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.
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.
|