Into Java - Part XV

From EDM2
Jump to: navigation, search
Into Java / Part
I II III IV V VI VII VIII IX X XI XII
XIII IV XV XVI XVII XVIII XIX XX XXI XXII XXIII

By Simon Gronlund

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.

First we will look into the splendid exception handling that Java gives you. Using it is simple, but nevertheless it is powerful, and in a real application you will most certainly make use of it in numerous ways.

Exceptions are thrown at any moment, when something strange occurs within a computer, and when normal events take place, although then we handle them in a more regular manner. We are more interested in errors.

There are at least three major kinds of errors: serious errors that you will not try to do much about since doing anything may not be possible or will make the situation worse, or errors in hardware or network processes that we can not do much about but pick them up and resort to "plan B", and finally logical errors that you should have thought of.

Throwable et al.

The topmost class of errors is called 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 hierarchy

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)

Self made exception class

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

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 a PrintStream that can be a file of your choice, or you can feed this method with a stream, see the Java API for more information.

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.

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.

try and catch

We have already used try and catch a few times, but now I will explain what is going on. A computer always executes statements one after another and it tracks the path to the actual statement. If an error occurs, the computer immediately quits the execution at the statement that caused the error and goes backwards along the track to find a place that can handle the error. Sooner or later we will end up in your application, either at a sound code line or at a logical mistake somewhere. Anyway, if there was no exception handling the application would quit, and in older days on Windows, the system would go corrupt and a blue screen would slam you in the face. But not necessarily in Java.

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:

  1. It is easy to separate error handling code from your regular code.
  2. You can easily propagate exceptions "upwards" to a main method capable of dealing with errors, simply add throws XxxException to your method head, were Xxx is the type of, or family of, exception that method may throw.
  3. Grouping errors and differentiate between them becomes easy, using one try block followed by several catch blocks.

Java is elegant in that it gives you a way to develop code fast and efficiently that will be less prone to errors, and when errors do occur, you may easily take care of them.

finally

There is another thing you can use if you want to, the 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

Example of try / catch / catch / finally

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.

Error log files

Since error files seems to have close connections to exception handling I will touch on them here. Today we will not make a fancy application, but only view some code you might put into any application. That is, the code will be kind of a toolbox that you will save somewhere close to your projects.

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.

Error logging

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?

Error output handling

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.

Now you can safely play with the small applications we have made ourselves in earlier installments, but inject false file names. Try to set a file to read only, using the file object's settings notebook, and see what happens when you try to write to it.

Summary

When Java first was thought of, exception handling was one major part of the architecture, and that is clearly visible. Acquaint yourself with the exceptions that are thrown from different methods of the Java class library, and figure out how to take care of different situations that might blow up in your face.

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.