| Creating our own Exception Class | An alternative way of Throwing Exceptions | Using Java's own Exception Class |
| Summary | Practice | Exercise |
On completion of this section you will know
In almost all programming environments error situations occur. This generally happens when the programme is presented with data that it is incapable of processing. An example is a programme code that adds two integers together being presented with two pieces of text. Most programming languages handle this situation by throwing exceptions. Java, being an object oriented language, throws exceptions by creating exception objects. These objects can vary from very simple ones which merely prints out details of the error situation that caused the error in the first place to more sophisticated objects that may attempt to correct the same situation.
In this section we shall look at both exception classes that we create ourselves and at a small selection of the exception classes that Java itself provides. We shall begin with our own exception classes.
Once more we return to our old friend, the payroll application, for an example of how to create and use our own exception classes. In Listing 6.3 we made the method setHours validate the incoming data to ensure that it was within the range 5 – 60. In that listing an if..else construct was used where the valid data was processed inside the body of the if while the else body performed the necessary processing in the case of the data being erroneous. In our case we shall modify the same example so that instead of the if..else construct we shall use exception handling to validate the data.
In order to validate the hours worked as being in the range 5 – 60 we shall create two exception classes: HighHours and LowHours. One will be thrown if the value is greater than 60 and the other will be thrown if the value is less than 5. The code for the HighHours class is in Listing 10.1.
Listing 10.1
|
1 |
public class HighHours
extends Exception |
|
2 |
{ |
|
3 |
private double hours; |
|
4 |
public HighHours() |
|
5 |
{ |
|
6 |
hours = 0; |
|
7 |
} |
|
8 |
public HighHours(double h) |
|
9 |
{ |
|
10 |
hours = h; |
|
11 |
} |
|
12 |
public String toString() |
|
13 |
{ |
|
14 |
String msg; |
|
15 |
msg = "The value given for hours was
"; |
|
16 |
msg = msg.concat(Double.toString(hours)); |
|
17 |
msg = msg.concat("\nThe highest
allowee value is 60"); |
|
18 |
return msg; |
|
19 |
} |
|
20 |
} |
The first thing we notice about this class is in its definition at line 1. It contains the extra piece of code extends Exception. This is using polymorphism, a concept very central to object oriented programming. We shall be dealing with this concept in great detail later in the course, but for the moment let us give a very brief explanation of it as it applied to our current application.
The class Exception is one of Java’s native classes. It can be either used on its own, or if we wish to create our own exception class, then it must be inherited by that class. Our class HighHours inherits Exception through the keyword extends. Due to this inheritance HighHours can be used for exception handling as we shall see later in the section.
The class itself is very simple. It simply contains an overloaded constructor, the second of which updates the private variable hours. The other method is a toString which composes a message describing the error situation that caused the exception to be thrown in the first place.
Our second class, LowHours is shown in Listing 10.2. Since this is logically identical to HighHours we shall not explain it.
Listing 10.2
|
1 |
public class LowHours
extends Exception |
|
2 |
{ |
|
3 |
private double hours; |
|
4 |
public LowHours() |
|
5 |
{ |
|
6 |
} |
|
7 |
public LowHours(double h) |
|
8 |
{ |
|
9 |
hours = h; |
|
10 |
} |
|
11 |
public String toString() |
|
12 |
{ |
|
13 |
String msg; |
|
14 |
msg = "The value given for hours was
"; |
|
15 |
msg = msg.concat(Double.toString(hours)); |
|
16 |
msg = msg.concat("\nThe lowest
allowed value is 5"); |
|
17 |
return msg; |
|
18 |
} |
|
19 |
} |
Finally let us look at our modification of the class Pay. Only a portion of it appears in listing 10.3 below, which includes the setHours method which we have changed.
Listing 10.3
|
1 |
public class Pay |
|
2 |
{ |
|
3 |
private double hours, rate, gross, tax,
nett; |
|
4 |
private boolean validHours, validRate,
calculateOK; |
|
5 |
public void setHours(double h) |
|
6 |
{ |
|
7 |
try |
|
8 |
{ |
|
9 |
if(h > 60) |
|
10 |
throw new HighHours(h); |
|
11 |
if(h < 5) |
|
12 |
throw new LowHours(h); |
|
13 |
hours = h; |
|
14 |
validHours = true; |
|
15 |
} |
|
16 |
catch(HighHours hh) |
|
17 |
{ |
|
18 |
System.out.println(hh.toString()); |
|
19 |
validHours = false; |
|
20 |
} |
|
21 |
catch(LowHours l) |
|
22 |
{ |
|
23 |
System.out.println(l.toString()); |
|
24 |
validHours = false; |
|
25 |
} |
|
26 |
catch(Exception e) |
|
27 |
{ |
|
28 |
System.out.println(e.toString()); |
|
29 |
} |
|
30 |
} |
|
31 |
public void setRate(double r) |
|
32 |
{ |
|
33 |
if((r>=8) && (r<=50)) |
|
34 |
{ |
|
35 |
rate = r; |
|
36 |
validRate = true; |
|
37 |
} |
|
38 |
else |
|
39 |
validRate = false; |
|
40 |
} |
As the method setHours is the only part of the class that has changed it is the only part of it that we shall discuss here. As before the class is passed a value which is stored in the argument h. The body of the class however consists of a try..catch construct instead of the if..else of the previous version of it. The idea of the new construct is that we place the code where we expect an exception to occur inside the body of the try, which in this case extends from line 9 to 14.
In line 9 h is tested for being more than 60. If it is the line 10 is executed which throws an object of HighHours. Here the keyword new tells us that an object is being created and that the erroneous value of h is passed to it as an argument. Having looked at Listing 10.1 we know that this value is simply stored in the private variable hours of that object. Normally when we create an object its address is stored in some pointer, which does not seem to be the case here. The reason for this is that the keyword throw tells the system, once the object is created to skip the rest of the try block and to search among the various catch bodies for a pointer to HighHours. It finds such a pointer in the argument hh at line 16. Control therefore passes to the catch construct that begins at this line. The body of this construct is simply lines 18 and 19. Line 18 simply prints out the text returned by the toString method of the object while line 19 sets validHours to false. This is approximately what the body of the else did in the earlier version.
With the body of the catch block executed all of the other catch blocks at 21 – 25 and 26 – 29 are skipped. In this case it means that the method setHours ceases execution.
On the other hand if the value of the argument h at line5 had a value of 3 then the sequence of execution of the code would be 9, 11, 12, 21, 22, 23, 24, 25, 30.
Listing 10.4 below show an example where we create an object
of the new version of Pay, and pass erroneous data to setHours.
Listing 10.4
|
1 |
public class Untitled1 |
|
2 |
{ |
|
3 |
public static void main(String[] args) |
|
4 |
{ |
|
5 |
Pay p = new Pay(); |
|
6 |
p.setHours(80); |
|
7 |
p.setRate(20); |
|
8 |
p.calculate(); |
|
9 |
System.out.println("Hours " +
p.getHours()); |
|
10 |
System.out.println("Rate " +
p.getRate()); |
|
11 |
System.out.println("Gross " +
p.getGross()); |
|
12 |
System.out.println("Tax " +
p.getTax()); |
|
13 |
System.out.println("Nett " +
p.getNett()); |
|
14 |
} |
|
15 |
} |
In the way we implemented exception handling in the method setHours in Listing 10.3, the method itself both threw and caught the exceptions and the method main of Listing 10.4 was unaware of any changes occurring in the class. This is an indication of good encapsulation in a class. We can make fairly extensive modifications inside the class, but as long as the external interface remains constant, outside classes are unaware of the changes.
In order to demonstrate another way of implementing exception handling we shall make a few alterations to our setHours method which will unfortunately break the rules of encapsulation. The modified version of setHours is in Listing 10.5
Listing 10.5
|
1 |
public void setHours(double h) throws
HighHours, LowHours |
|
2 |
{ |
|
3 |
if(h > 60) |
|
4 |
throw new HighHours(h); |
|
5 |
if(h < 5) |
|
6 |
throw new LowHours(h); |
|
7 |
hours = h; |
|
8 |
validHours = true; |
|
9 |
} |
Here we have only included the method setHours itself. The first change is that throws HighHours, LowHours now appears after the method definition. This simply tells other code that may use this method that it throws exceptions and thus must be used inside a try block. In the body of the method there is no try..catch construct, it simply throws HighHours at line 4 and LowHours at line 6.
Whenever an exception is thrown, it must be caught. In Listing 10.3 any exceptions thrown were caught by the catch blocks. Since no catch blocks exist in the current version of method setHours then the method itself must be called inside a try block and its exceptions must be caught in the corresponding catch blocks. In order to examine this we look at Listing 10.6 below.
Listing 10.6
|
1 |
public class Untitled1 |
|
2 |
{ |
|
3 |
public static void main(String[] args) |
|
4 |
{ |
|
5 |
Pay p = new Pay(); |
|
6 |
try |
|
7 |
{ |
|
8 |
p.setHours(80); |
|
9 |
p.setRate(20); |
|
10 |
p.calculate(); |
|
11 |
System.out.println("Hours " +
p.getHours()); |
|
12 |
System.out.println("Rate " +
p.getRate()); |
|
13 |
System.out.println("Gross " +
p.getGross()); |
|
14 |
System.out.println("Tax " +
p.getTax()); |
|
15 |
System.out.println("Nett " +
p.getNett()); |
|
16 |
} |
|
17 |
catch(HighHours h) |
|
18 |
{ |
|
19 |
System.out.println(h.toString()); |
|
20 |
} |
|
21 |
catch(LowHours l) |
|
22 |
{ |
|
23 |
System.out.println(l.toString()); |
|
24 |
} |
|
25 |
} |
|
26 |
} |
Here is our altered version of the method main. Notice that all of the actual processing is now inside a try block, including calling the method setHours. In this case the value 80 will cause an exception HighHours to be thrown at line 4 of Listing 10.5. This will cause setHours to cease execution and control to return to main. Here the rest of the body of the try block will cease execution and the exception thrown will be caught at the catch block that begins at line 17.
In our exception handling by far the most exception classes we will be using will be Java’s own exception classes – of which there is a myriad. Think of an error situation an Java will have an exception class to fit that situation.
Before examining those exception classes, let us look at the very silly piece of code below.
Listing 10.7
|
1 |
public class Untitled2 |
|
2 |
{ |
|
3 |
public static void main(String[] args) |
|
4 |
{ |
|
5 |
String s = “Hello”; |
|
6 |
double d; |
|
7 |
d = Double.parseDouble(s); |
|
8 |
System.out.println(d); |
|
9 |
} |
|
10 |
} |
If you think that this is nonsense, then is because it is. Line 7 tries to convert a piece of text “Hello” into a double precision real number. Obviously this is not going to work, but what happens if we run it. What happens is in fact the output shown below.
java.lang.NumberFormatException: For input string: "Hello"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
at java.lang.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1207)
at java.lang.Double.parseDouble(Double.java:220)
at exceptions.Untitled2.main(Untitled2.java:10)
Exception in thread "main"
The first line of this output tells us that a NumberFormatException has been thrown while the fourth line tells us it has been thrown by the parseDouble method. Of course the reason it has been thrown is that parseDouble has not been able to convert “Hello” into a number, and for that reason threw the exception.
Below is the official documentation for the method parseDouble
parseDouble
public static double parseDouble(java.lang.String s)
Returns a new double initialized to the value represented by the specified String, as performed by the valueOf method of class Double.
Parameters:s - the string to be parsed.
Returns:the double value represented by the string argument
Throws:NumberFormatException - if the string does not contain a parsable string
At the last line notice that it says that the methods throws NumberFormatException if the string passed to it is not parsable. In order to accommodate this feature in our programme we can modify our code as in Listing 10.6
Listing 10.6
|
1 |
public class Untitled2 |
|
2 |
{ |
|
3 |
public static void main(String[] args) |
|
4 |
{ |
|
5 |
String s; |
|
6 |
double d; |
|
7 |
s = "Hello"; |
|
8 |
try |
|
9 |
{ |
|
10 |
d = Double.parseDouble(s); |
|
11 |
} |
|
12 |
catch (NumberFormatException n) |
|
13 |
{ |
|
14 |
d = 10; |
|
15 |
} |
|
16 |
catch (Exception e) |
|
17 |
{ |
|
18 |
System.out.println(e.toString()); |
|
19 |
d = -1; |
|
20 |
} |
|
21 |
System.out.println(d); |
|
22 |
} |
|
23 |
} |
Here the method parseDouble is inside a try block. If an exception is thrown then it is either caught at line 12 – the most likely case, or else at line 16. Running this version would give us an output of 10.
This is a very simple example of using Java’s own exceptions. From now on we shall be meeting more of them and thus we will not elaborate further on them here as we shall have plenty of practice later.
Exception classes are used by Java to signal that an error has occurred in the processing of some data item. We can either create our own special exception classes that can deal with a particular situation or we can rely on the extensive collection of exception classes that Java has already. In 95% of cases we use the latter.
The code that is likely to throw an exception is put inside a try block and then the exceptions thrown are caught in catch blocks that follow the try block.
Copy listings 10.1, 10.2, 10.3 and 10.4 into your computer. Of course since the latter is incomplete it may be better to copy Listing 6.3 and modify the setHours method to reflect that in Listing 10.3. Now compile and run your application. Now modify line 6 of Listing 10.4 so that too low a number is passed to setHours and then check the output of the application.
Next create two more exception classes, HighRate and LowRate. These can be based on their hours equivalents. Now in the class Pay modify the method setRate so that it validates the incoming data using exceptions. Compile and test your code
Finally overload both the setHours and setRate methods so that they will accept a String object as well as a double value. The versions that accept a String argument will simply convert that argument to double inside a try and then pass that to the method that accepts a double as its argument. If when the data is being converted from String and exception is thrown then in the catch block the appropriate boolean variable is set to false.
1. What is an exception class?
2. Describe the structure of the try..catch costruct.
3. What are the similarities between the try..catch construct and the if..else construct?
4. What are the differences between the try..catch construct and the if..else construct?
Modify Exercise 7.2 as follows: