16 March 2001 Simon Gronlund is earning his Master of Science in Computer Science at the Royal Institute of Technology, Sweden, as an adult student. He also teaches Java and computer-related courses at the college. When he isn't tampering with his Warp 4 PC, he spends his spare time with his two boys and his wife. If you have a comment about the content of this article, please feel free to vent in the OS/2 eZine discussion forums |
|
Into Java, Part 15
Streams may take several forms, we will think of what we need the stream to do
do for us and then we will pick the best choice. Java gives us a lot of choices, as you saw from the veritable zoo
we briefly touched on in the last installment. Today we will see how to create error logs in a convenient way. |
Throwable
, since anytime an error occurs Java will throw
a notification about it. The means to do that is to instantiate an object and send that towards the statement
being executed at the time. This object will contain information on what type of error happened.
You will not try to catch Throwable
.Throwable
is subclassed by Error
and Exception
. The former class encapsulates those errors that we do not like to do much
about, the CPU, hard disk or some other hardware may have gone very weird. That gives us the class
Exception
to work with, and it encapsulates both "external"
exceptions, for example IOException
that
is thrown when an error happens to a stream, and logical errors, like an IndexOutOfBoundsException
which is thrown when you did not check for the real length
of an array or vector. In fact, the latter type of exceptions are in turn subclassing
RuntimeException
, the class Java subclasses when finding
your bugs while running your code. I will come back to this in a moment.
A NumberFormatException
may of course be the result of erroneous user input and need to be handled
by you. If you have special needs you simply subclass Exception
and stuff your class with whatever you need, this is mostly a way to gather
and deliver information on what happened.
The constructor can simply use the two constructors of Throwable
, one that does not take parameters
and one that takes a String object that will be the message of the exception. For example (assuming there is
a method getCheck() at hand)
|
Furthermore, the class Exception does not convey any methods at
all, but Throwable will give you some that are most useful, especially when you are debugging or want to get error
reports to a file. Two of the methods are
Finally, Java divides exceptions into checked and unchecked. That is, upon compilation every checked exception has to be taken care of by your code, or you must add a term to the header of the method that can be hit by an exception. An unchecked exception is nothing you explicitly need to catch, but consider if anything can go wrong, then you need to play it safe and be defensive.
String getMessage()
- Returns the message that was given as a parameter to the constructor.
void printStackTrace()
- A backward print of the stack from the point the exception occurred. It is printed to
System.err
that in turn may be redirected to aPrintStream
that can be a file of your choice, or you can feed this method with a stream, see the Java API for more information.
An example of a checked exception is
IOException
which you really have to take care of, the
developers behind Java knew their lesson and foretold that errors on streams are so common that the question is
not if? But when?
Unchecked exceptions are RuntimeException
that javac
does not bother itself with. You are supposed to realize that they may occur.
Hence, exceptions may be thrown even though javac
does
not demand a catch on them and compiles without errors or warnings. The place to find out is the Java API that
tells whether an exception will be thrown by a method or not.
Whenever you come to a code line or a block of code that might
raise an exception, you simply encompass that code with a try
block. That is simply as it sounds. You will try to
do this and if everything works fine, then smile. If anything goes wrong, the execution will stop at that point,
nothing more within the scope of the try block will be executed. In Java the JVM immediately heads to the end
of the try block.
Directly after the try block there must be a catch
block. This block will usually
have some code to execute, but that is not necessary as we have seen in former installments. Simple applications
that are more like toys than for professional use may do nothing, or very little, but sooner or later you will
have to make a try to correct the situation.
The catch block can tell different types of exceptions apart and
hence you may have several catch blocks in a row. For example, look at IOException
, it is subclassed by several other exceptions, which
ObjectStreamException
is subclassed from as well. Depending
on which exception that was raised you want to resort to different backup plans, so you use several catch
blocks. If so, start with the most descriptive ones, that are the lowest classes in the hierarchy. Among them,
the most commonly hit exceptions should be first, to gain some CPU time as after the first one is found no more catch blocks
will be searched for. At the bottom you may catch the most general one,
Exception
, as kind of a default catcher that catches
the errors that slipped through so far.
After an exception is raised there is no easy way to start over.
How you will solve the situation depends heavily on the circumstances and I will avoid any tips at this point.
Mainly you will not call the actual method from itself, as a recursive call. You have to see to it that a perfect rollback
is performed, if necessary. Have you written to a file, is there stuff that you must erase? Have you put text to a text
area that is now outdated? Often it is a good idea to signal an error occurred and then have the application go
on from that point, but as I said, there are many situations, and you have to examine yours.
As we have seen so far we have found three advantages with
Java exception handling:
throws XxxException
to your method head, were Xxx is the type of, or family of, exception that
method may throw. finally
clause. Many programmers do
not see the need for it, perhaps since C++ does not have this feature. Most of the time there will be a few lines
you always would like to do, no matter what happened within the scope of the try block. For example, in a try
block you read from a stream, and maybe at the same time you add to a database. Both actions can go wrong for
any reason, but you know that regardless of the outcome of your code you will have to close the stream. Then you
can put that action within a finally block and it will be done in any case. You can only have one finally block,
immediately following the last catch block. For example
|
Mastering exception handling is an art in itself and no one will
do it perfect the first time. Although I have seen many mistakes and many, ahem, not that smart implementations,
I will take a few words to explain my view of this topic, well aware there are other opinions as well.
Quite a few methods of the JDK throw exceptions, checked or unchecked,
RuntimeException
or not. Catch the exception objects and do something
intelligent within your code.
You do not necessarily have to test everything, for null pointers
as an example, if there will be a RuntimeException
thrown by the JVM, catch that instead.
Do not create your own exception classes unless you will only use
them signaling an exception. I have seen students throwing exceptions instead of doing simple tests such as if a digit
was within the proper range, or if certain buttons were pressed. If an event is considered regular you will not
throw an exception but act accordingly to the event. Even though some input was erroneous, there might be better
ways to get the attention of the user than using error handling, I mean, erroneous input might be so common
sometimes that it might be considered more normal than getting proper input. Exception handling is somewhat costly
to the system and you do not wish to waste unnecessary CPU power, do you?
Do not overlook the abilities you have at hand, and do not take
exception handling as the final part of your project. On the contrary, plan for it from the very start. Do you
think architects plan for fire escapes and sprinklers when everything else is set up? No. And if you plan for
exceptions from the very beginning you will end up with a stable and robust application.
Remember that the more pencil you use and the less code hacking, the better the result.
This code will take care of the output you explicitly send to the
System.err stream. You can do that in any catch block and you will get a nice output, lengthy, but clearly readable
upon debugging.
Remember that System.err is a PrintStream and from the beginning
in parallel with System.out which is targeted to the screen. Nothing prevents us from redirecting that output
to anything else, we chose a file.
|
Within the constructor of your driver class, the one with the main
method, you write the code above.
First, instantiate a PrintStream
wrapping
a FileOutputStream
named
'error.log'. The innermost false
says
that if the file exists we will not append to it but overwrite. I find that good when I debug code, but of course,
a stable application will not do much logging and you might want to read it much later. Perhaps adding
today's date and time would be good for such times.
The true
says that the output stream will always be flushed upon writing to it. Finally we
call the System
class and
tell it to use this new PrintStream
as
the error output stream. If this does not work, then the default output stream will work, but
we are notified by the statement within the catch block. So far we are done. How do we use this redirection?
|
As mentioned, an exception can occur at many places in an application.
If you want to really catch everything you might want to enclose every line in your
main
method with a try / catch block and the
suggestion in the image above.
You may encompass rather large parts of code with only one try
block, piling catch blocks at the end of the try block and maybe a finally block too. For clarity, or the need
for it, you may use a few try blocks one after another. Remember that once an exception is thrown every
statement after that one is not executed, perhaps leaving the current state messed up.
Be sparse with throwing exceptions yourself but plentiful with
catching possible errors.
Use file logging as a tool to debug your software, and after letting
it out, as a tool to get user input. You know, the best debugging tool is letting a future user start playing
with it. If there are no bugs at all, they will pop up at the release exhibition, most of them. <grin>
Next time we will explore a convenient way to store and retrieve
your application data, something that can be really tiresome in other programming languages, but is a breeze with
Java. CU next month.
Previous Article |
|
Next Article |