Error Handling and the NumberField class


We have already seen an example of how Java handles errors that can occur in the course of a running program - the try-catch block. As it turns out, this is a very general mechanism that Java employs:

Try-Catch Error Handling

If a method, or a class, can produce incorrect results, for whatever reason, such a method (or class) can be designed to "throw an error". If a method throws an error, everyone using that method must "catch" the error and decide what exactly to do with it. Sometimes it may be possible to recover from that error gracefully. At other times, a program may need to stop entirely if an error occurs. The syntax for a "try-catch" block looks like this:

try
{
   // code including a method that throws an error
}
catch(SpecificException e)
{
   // alternate code if an error occurs in above try block
}

The specific exception that is thrown depends on the method inside the "try" block. You can use, as usual, the Java API to find out the details; if you are lazy, you can always use the class "Exception" in place of the specific exception. Every exception extends the class Exception, so you can always use that superclass to catch an exception.

Example:

Suppose we want to read a number from a text field, but that number should represent an integer. We have already seen the Integer.parseInt(String s) method that can convert a string to an integer. So, we might write out code containing a line like this:

int i = Integer.parseInt(input.getText());

That, of course, assumes that the object input is a TextField. The TextField method getText returns a String, which in turn is used as input for the parseInt routine of the Integer class. That would work just fine, if the string indeed represents an integer. If not, the parseInt method throws an exception, and since, in this example, the exeption is not caught, it might lead to unpredictable results. A better way to use this routine would be as follows:

int i;
try
{
   i = Integer.parseInt(input.getText());
}
catch(NumberFormatException e)
{
   i = 0;
}

That means that if the input field contains a string that does represent an integer, the pareInt will correctly convert it and store it in the variable i. Otherwise, the method will throw a NumberFormatException, and the "catch" block will execute instead. It will set the variable i = 0. So, we have some code that will return the correct integer representation of a string, if possible, or otherwise return the integer 0; that, of course, means that the method will not cause your program to simply 'crash', or otherwise produce unwanted results.

Instead of catching the specific exception "NumberFormatException e" we could also have used the generic "Exception e" - in general, however, the exact exception is preferred.

The parseInt method is somewhat unique, because you don't have to catch the exception. Almost all other methods that throw an exception force you to catch the potential error.

Example:

Let's try to convert a string (say, from a TextField) to a double number. Looking at the Java API, we find no "parseDouble" method, but instead a "valueOf" method in the "Double" class. Our code, therefore, might look as follows:

double x
try
{
   x = Double.valueOf(input.getText());
}
catch (NumberFormatException e)
{
   x = 0.0;
}

However, this will not compile, because the valueOf method returns a Double type, instead of a double. The Double class is, actually, called a wrapper class: it is essentially a double number, but enhanced with several methods. It "wraps" the basic type "double" around several methods to make it potentially more usable.

However, in our case we do want a double, not a Double, so we look again at the Java API to find the method doubleValue of the Double class, which turns a Double into a double. Confusing?  Probably, but once you know how to convert a string to a double, you can always use a variation of that code...

Now, the correct code to convert a string into a double number might contain the following lines:

double x;
try
{
   x = Double.valueOf(input.getText()).doubleValue();
}
catch (NumberFormatException e)
{
   x = 0.0;
}

That method will try to convert a string into a double number. If successful, x will contain that double number. If not, x will be set to 0.0 (which may or may not be what you need).

The point of Java's execption-handling mechanism - which we will call "try-catch" block - is two-fold:

It forces you to catch a potential error when using methods that throw an error (except the parseInt method) - therefore trying to stop you from creating programs that can "crash" easily.
It allows you to provide the code for what exactly you want to have happen in case an error occur - therefore trying to give you the flexibility you need to create "good" programs.

A similar "try-catch" block must be used when putting a thread to sleep, as we have seen in several prior examples. In those cases, the "catch" block actually does not contain any code. That means that we chose to handle the potential error by doing nothing. That, generally speaking, is not a good way to handle an exception, but in case an error is rare, or difficult to imaging, it is an easy way out for quick, but most likely not optimal, programs.

We could now use our knowledge of a "try-catch" block to create a class that looks like a TextField, but will produce only numbers: we shall call such a class "NumberField". Since it is supposed to look like a text field, that means, in our programming lingo, that our class is supposed to extend the TextField class. Thus, our class would start out with:

public class NumberField extends TextField
{
   // whatever
}

Next, we need to - very carefully - decide what type of methods of the TextField we should override, which one we should leave alone, and which new ones to add. In addition, we need to determine what type of constructor or constructors might be useful for this class. This is all "non-programming" related, and you simply need to think about all - or at least most - potential situations that your new class might be used in, and try to provide those methods that woud be most useful in that situation.

Here is a very simple version of a NumberField class:

We decide that the most important methods of a TextField are "setText" and "getText". We decide to leave those methods alone, but instead enhance our class with new methods "getInteger" and "getDouble".
As constructors, we decide that one constructor should create a number field such that if a user makes an error, the number returned will be 0. As another constructor, we decide that we want to set the initial value of the field to some number, and return a "user-defined" number in case an error occurs.
Since the second constructor allows for the possibility of a "default number" to be returned in case of an error, we also add two methods to our class: setDefault(double _default) and getDefault() to deal with that default number, if necessary.

Here is the possible code for such a - rather simple - number class:

import java.awt.*;

public class NumberField extends TextField
{  // Fields
   private double def = 0.0;
   // Constructors
   public NumberField()
   {
      super();
   }
   public NumberField(double _def)
   {
      def = _def;
   }
   // public Methods
   public int getInteger()
   {
      try
      {
         return Integer.parseInt(getText());
      }
      catch (NumberFormatException e)
      {
         return (int)def;
      }
   }
   public double getDouble()
   {
      try
      {
         return (Double.valueOf(getText())).doubleValue();
      }
      catch (NumberFormatException e)    
      {
        return def;
      }
   }
}

 Here is a simple Test applet to test this class:

Click here for the source code of NumberFormatTest.java


 Try it out: the first input field on the left is a NumberField that will try to convert any input to an integer. If that's not possible, it will return the default integer 0. The left input field in the second row will convert any input to a double. If that's not possible, it will return the default double 1.0. Enter, for example, 1.2 and 2.4 in both fields on the left. They will be converted to 0 and 2.4. Next, enter 2 and "bert" - it will be converted to 2 and 1.0.

So, now you have a NumberField that you could use in any application where a user needs to enter a number. That new class - supposedly - will deal with the conversion by itself, without you having to worry about it every again.

If you suitably modify this class, this could very well be used as another portfolio piece. Some modifications could be:

when an invalid number is entered, the "getInteger" and "getDouble" methods return the default number as before, but they also set the text field to "error"
add constructors and methods to make sure that the number entered is within a certain range. If not, the default number is returned (which, of course, should be inside the legal range)


(bgw)