Section 8.1. Exceptions

   

8.1 Exceptions

From our work in ColdFusion, we know what exceptions are, when to use them, and what's important about them. They help us write user -friendly code. They keep us honest about what sorts of things can happen in a production environment, and they help us handle such eventualities gracefully. This section should be pretty easy to grasp, as it is one of the places where ColdFusion and Java sort of see eye to eye.

We often talk about "handling exceptions" and "catching exceptions." While these are accurate terms, it is probably not a bad idea to shift our thinking a bit. Exception handling is a form of flow control, like a <cflocation> , <cfabort> , or even a switch statement. A switch statement says, "if this is the case at this time, then go here." Making this kind of shift in thinking will help us to think of exception handling as an integral part of our programs, and it will help us consider, and make provisions for, the kinds of events that could be triggered in our systems. The idea is that if the program enters a problematic state, you can reroute control to a part of the program that can recover from the injury .

You can only reroute program flow to specific blocks of code, which are designated to handle exceptions. If you have no such blocks, program execution will terminate, and the runtime will deliver its exception message.

You are likely familiar with <cftry> , <cfthrow> , and <cfcatch> . For instance, we might have a custom tag that performs some database lookup. We could wrap this action in a try / catch block and specify the kind of exception that we want to look for, like this:

 <cftry>    <cfquery name= "#attributes.queryname# datasource =      "#request.dsn#">       SELECT something       FROM somewhere;   </cfquery>   <cfcatch type="Database">      <cfthrow message="Something bad happened during query                 execution.">   </cfcatch> </cftry> 

We should be able to cover working with exceptions in Java rather quickly, as both the concepts and the syntax are very similar. Here we go.

8.1.1 throw

In Java, as you might guess by now, we work with an Exception object. The Exception object is created as an object of any class that extends the java.lang.Exception class.

Note

We will talk about this more in the next chapter. For now, extends is a Java keyword that means "inherits from." In our overloading example earlier, the Child class extends the Parent class.


There are 24 exceptions defined in the java.lang package, some of which we have come across already. You may have encountered the ArrayIndexOutOfBounds exception, which indicates that you've tried to access an array cell that doesn't exist.

There are two ways to cause an exception:

  • The program attempts to perform an illegal action, inadvertently causing an exception.

  • The program explicitly creates an exception when it encounters a throw statement.

We are unfortunately familiar with the first cause. The second cause works much as it does in ColdFusion. We explicitly generate exception s by using a throw statement, which can be handled by a catch statement. It takes the following form:

 public static void main(String[] a) {     public void myMethod() throws Exception {         try {             throw new MyException();         }         catch (RuntimeException re) {             // handling code         }         finally {             // do this whether exception is thrown             // or not         }     } } 

There are a number of things to remark on in this structure. First, the throw statement throws an exception that must be an object of the Throwable class or one of its subclasses. Above, we create and throw our exception in the same statement.

Methods can throw any Throwable object. You can create your own exception class or choose one from the Java API. This is a matter of program design and should be determined on a case-by-case basis.

When an exception is thrown, the Java Virtual Machine stops execution and retraces backward through the methods that the thread has called, trying to find a point at which the exception can be handled. If it cannot find a handler block, then the JVM prints the best error message it can and the thread is killed .

The Throwable class defines the basic behaviors for exceptions and errors. A created Throwable object contains a stack trace with information about every method that the current thread has executed through. As with the ColdFusion tag, <cfthrow message="my message"> , Throwable allows you to specify a string containing the message to send the user when the exception is thrown.

There are around 70 direct subclasses of java.lang.Exception . These include common favorites such as ClassNotFoundException , IOException , NoSuchMethodException , and others that are more specialized, such as those for working with MIDI data. You can (and should) take a moment to look in the API to peruse the kinds of exceptions and errors that are defined there. Doing so now will help you to get familiar with the kinds of exceptions that are available to you, thus encouraging their use in your code. Moreover, it will help you see what kinds of custom exceptions you may need.

8.1.2 try and catch

It is often rather difficult to actually recover from an exception situation. Depending on what your program is doing, you are likely to want to simply try for a useful message, as in the ColdFusion example above. Here's how you do it:

8.1.3 DemoException.java

 package chp8;  /**  * Purpose: demonstrate throwing an catching exceptions  * @author  eh  * @version 1.0  */ public class DemoException {             // declare that this method throws an Exception             // and state the kind of exception        public void myMethod() throws Exception {             // this method does nothing but throw the             // Exception object             // ~= <cfthrow message = "This is a custom error">            throw new Exception("This is a custom error");         }     public static void main(String[] a) {         DemoException de = new DemoException();         try {             // this method just throws the error             de.myMethod();         }             // you catch the Exception object,             // because that is what the myMethod throws         catch (Exception e) {             // once you've got the exception object             // you can call its methods like any other obejct.             e.printStackTrace();         }     } } 

The output of running this program is

 1 java.lang.Exception: This is a custom error 2        at chp7.DemoException.myMethod(DemoException.java:22) 3        at chp7.DemoException.main(DemoException.java:31) 

It is unfortunately common for us to see messages like this one. Such is our lot in life. And Java messages ” especially those generated by servlet containers ”can look really daunting to ColdFusion developers. But Java exception messages are as helpful as ColdFusion messages if you know how to read them. They're often very simple. Let's take the above message apart to see what it's made of.

First, we see the Exception type that was thrown, followed by our custom message. The following two lines are related . In the second line, we are looking at this structure:

 packageName.ClassName.methodName(SourceFileName.java: line number). 

The line number here indicates where the error was thrown from. In the DemoException.java source file, we shouldn't be surprised to discover that line number 22 consists of the following code:

 22 throw new Exception("This is a custom error"); 

The third and final line of the exception message has the same structure (remember main is a method). This line indicates that line 31 in the DemoException.java file contains another culprit. And it is indeed the method that was invoked when the exception got thrown:

 31 de.myMethod(); 

Here are a few notes before we move on:

  • Your catch statement always needs to declare a parameter that provides the kind of exception it is prepared to handle and the name you'll use to refer to the Exception object in your catch code block. The scope of this variable is obviously only the catch block, as that is where it gets defined.

  • The Exception constructor is overloaded. That is, there are two constructors for exception: one that takes a String argument to display a custom text message and another no-arg constructor.

  • Your Exception message can be a variable.

8.1.4 causes

Java version 1.4 made the cause available, and there is no real equivalent for this in ColdFusion. As previously mentioned, when an exception is thrown, it contains a snapshot of the execution stack. It can also contain a cause , which is another Throwable object that caused this exception to get thrown. As you can quickly see, there are many regresses possible here, as a cause can not only be a cause, but have a cause itself. So cause s are a generally referred to as a facility for chaining together exceptions.

There are two reasons to use cause s. The first is an architectural issue. You generally will separate the outer abstraction layers from the low-level inner workings of your program. An exception can be caused at a lower program level that is unrelated to the outer layers , but gets propagated outward anyway because of the design of the program. Cause chaining acts like a wrapper, allowing you to maintain the integrity of your layer separation effectually; that is, if you change your implementation, you don't have to change the exception structure in your API, because a low-level exception will pass a cause up to a higher-level exception so that it can be handled more appropriately.

There are two ways to use a cause by associating it with a Throwable . The first is by calling the constructor that accepts a cause as an argument. The second way to do it is by calling the initCause(Throwable) method. The initCause() method is public , so it allows any Throwable object to have an associated cause :

 try {             obj.something();         }         catch (LowLevelException lle) {             // instead of printing the "low level" error,             // we'll pass it on to a "high level" one,             // i.e., chain the cause throw new HighLevelException(hle); 

8.1.5 Multiple catch Clauses

As in ColdFusion, you can define multiple catch clauses, each specifying a different type of exception you anticipate being thrown. You can do that in Java as well, and in the same manner. The concept is not really different from using one catch clause, so we'll just look at an example to see what different kinds of exceptions it might make sense to throw given the same try block.

A typical need in applications is to connect to databases and retrieve information from them. While this is made very easy in ColdFusion with the <cfquery> tag, Java requires rather more work. You have to load a database driver, connect to the database, create a statement and execute it, and then drop the connection. It's all of the same things that you have to do in ColdFusion ”we're just used to that being handled for us.

Note

There certainly are ways to make working with databases easy in Java and especially JavaServer Pages. We'll see how to handle this in another way in later chapters.


Let's say we've got a class that connects to a database. Here we'll write the makeConnection() method that loads the driver and connects to the database via the driver. Because this is such a complex process with many steps, there are a lot of things that can go wrong. So we will employ multiple catch statements to handle the different kinds of exceptions:

[View full width]
 
[View full width]
... //we'll put an error message here String msg; ... public void makeConnection() throws ClassNotFoundException, SQLException, Exception { try { // load Driver class Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); // connect to the ODBC datasource conn = DriverManager.getConnection("jdbc:odbc:JavaForCFDB", "username", graphics/ccc.gif "mypassword"); } // in case driver can't be found catch (ClassNotFoundException ce) { msg = "Hey! I can't find the driver"; throw new ClassNotFoundException(msg); } // in case of error connection to database catch (SQLException se) { msg = "Hey! I can't connect to the database"; throw new SQLException(msg); } // in any other exceptional case catch (Exception e) { msg = "An exception was thrown"; throw new Exception(msg); } } // end makeConnection()

8.1.6 What About <cfrethrow> ?

In ColdFusion we can rethrow an exception using the <cfrethrow> tag. This is very useful, especially when your program is performing an operation on another resource. In these cases, it is usually a good idea to shut down your connection to those resources, whether they are files, printers, or what have you, and then rethrow the exception so that it can be dealt with.

Java does not make use of a keyword such as "rethrow." To perform a rethrow in Java, just use the same syntax used to throw new exceptions:

 try { ...          catch (FileNotFoundException fe) {                  closeResources();                  throw fe ;           } } 

8.1.7 finally

The finally keyword is used after all of your catch clauses to indicate a code block that should run no matter what; that is, whether an exception was thrown or not. Obviously, the finally clause cannot execute if the execution thread dies.

Note

ColdFusion has no finally clause. During a couple of the beta versions of CFMX, the CFML included a <cffinally> tag. It was dropped from the language prior to the final release of CFMX.


The following code demonstrates how this works:

8.1.8 DemoFinally.java

 package chp8;  public class DemoFinally {       public static double tripleIt(String s) {             try {                 double d = Double.valueOf(s).doubleValue();                 return d * 3;             }             catch (NumberFormatException ne) {                 System.out.println("Number Format Problem!");                 return -1.0;             }             finally {                 System.out.println("Hello from finally");             }         }         public static void main(String[] ar) {           DemoFinally df = new DemoFinally();           System.out.println(df.tripleIt("hi dude")); //error!     } }// eof 

The tripleIt() method accepts a string, converts the value to a double , and multiplies it by three. When the tripleIt() method is supplied with a real number, such as 6, we see this output:

 Hello from finally  18.0 

When it is supplied with a non-numeric value, we get this output:

 Number Format Problem!  Hello from finally -1.0 

This tells us a couple of things. First, the finally clause gets executed no matter what. If we supply an incorrect type, we return an arbitrary error code of -1.0. The finally clause is always executed after the try block. When you pass tripleIt() as a number in a string, the try puts a double in the current method frame as a return value for the calling method. Then the finally block does a S ystem.out.println() in the context of the same method frame. Then the tripleIt() method pops off the stack, and the main method's System.out.println() is called to print out the double returned from the try in the previous method. If you put a System.out.println() in the try block too, you'll see that finally is always executed after the try . If we supply a non-numeric value, such as "hi dude," we get the exception output, then the finally block, and then the error code returned by the method. This allows you to replace the error code in the finally block, so that you don't have to rely on the operation performed inside the method where things are going wrong.

Let's look at part of a more realistic example regarding an instance when you might actually need to use finally . First of all, it is a good idea to use finally whenever you've got try blocks that might need some cleaning up if they throw an exception. These typically include connections to a database that should get closed, or connections to files that should be closed.

The java.io.FileWriter class is a convenience class that helps you write create and write character text files.

8.1.9 MakeFileWriter.java

 package chp8;  // import java.io to get access to the IOException import java.io.*; // we'll use this to write the file import java.io.FileWriter.*; public class MakeFileWriter {         // method to write file     public void makeFile(String fileName) throws IOException, Exception {         String theFile = fileName;         FileWriter fw = null;         try         {                 // create the file object             fw = new FileWriter(theFile);                 // write the data             fw.write("Dude, I am some important data");         }         catch (IOException ioe){             ioe.printStackTrace();          }         catch (Exception e){             e.printStackTrace();          }          finally          {                  // close the connection             fw.close();          }     }         // main     public static void main(String[] args) throws IOException,               Exception {         MakeFileWriter fw = new MakeFileWriter();             fw.makeFile("C:\myData.dat");         System.out.println("The file was written");     } } 

We have not covered input/output operations directly. But for now, you can see how this creates a new file and writes some character data to it. If the file specified does not exist, then it is created and the data is written. If the file does exist, then the new data overwrites any existing character data in the file.

What is important about the previous listing is to show that the finally block contains the method to close the connection to the file. That way, if any exception is thrown, the close() method can still execute. Without finally , the connection to the resource could have been left open .

As you can see if you run this example, the above listing is the rough equivalent to this sort of thing in ColdFusion:

 <cfset theData = "Here is some important stuff.">  <cftry>      <cffile action = "write" file="C:\myData.dat"               output="#theData#">       <cfif NOT cffile.FileWasWritten>             <cfthrow message = "Error! The file wasn't                      written">       </cfif> <cfcatch type = "Any"> <cfoutput>        <cfloop index = i from = 1 to =                #ArrayLen(cfcatch.tagContext)#>           <cfset c = #cfcatch.tagContext[i]#>               <br>               #i# #c["ID"] #c["LINE"]# #c["COLUMN"]#        </cfloop> </cfoutput> </cfcatch> </cftry> 

8.1.10 Exceptions in Constructors

There are really only two limitations on the exceptions that a constructor can throw:

  • A constructor can throw any exception that is not thrown by its superclass constructor.

  • A subclass constructor cannot catch an exception thrown by a superclass constructor it calls. The reason is because a call to the superclass constructor has to be the first statement in the subclass constructor. While we haven't talked about inheritance yet, this is similar to how any call to another constructor in the same class using this ( ) must be the first item on the constructor's agenda too.

There is no way for a constructor in a subclass to catch an exception thrown by the constructor of a superclass. Therefore, the subclass constructor must declare all of the same checked exceptions as its superclass does in its constructor's throws clause.

8.1.11 Errors and Checked and Unchecked Exceptions

Errors are like exceptions in that they indicate problems with program execution. However, exceptions indicate a problematic state from which the program can recover. Errors, on the other hand, indicate a problematic state from which the program cannot recover. They represent an abnormal state that the program should never enter. That is the basic conceptual difference between exceptions and errors. An Error is a subclass of Throwable , and its subclasses should not be declared in a throws clause. That is the basic practical difference for the programmer between exceptions and errors.

Instances of the java.lang.Error class represent malfunctions in the program's environment that indicate that the program cannot continue to work. The distinction between errors and exceptions creates another distinction: checked and unchecked exceptions.

Exceptions are checked. The compiler checks to see whether checked exceptions are handled in the code. Exceptions represent issues that can possibly be recovered from or that can be reasonably anticipated to occur. A common example is a ClassNotFoundException , which occurs when you try to load a class using its string name, and it can't be found. A related and equally common error is the java.io.FileNotFoundException . In working with the compiler at the command line, you've probably seen the ArrayIndexOutOfBoundsException ; this is thrown when you run a program that expects arguments, references the args String array directly, and then throws the exception when it doesn't get any. In these kinds of operations, you can reasonably expect to have to deal with such shenanigans, and Java encourages you to do so.

Errors are unchecked. Unchecked exceptions are subclasses of Error and the RuntimeException classes. "Unchecked" means that the compiler does not check to see if all of its code is caught or declared in a method's throws clause. If Java didn't make this distinction, everything would have to be checked, and your execution would be far slower, your development slower, and your code would be absolutely riddled with generally unnecessary exception handling.

The difference between checked and unchecked exceptions only exists at compile time. When you compile your source, the compiler checks to see that particular exceptions are either caught or declared. Once compiled, the Java Virtual Machine just executes the bytecodes ”the checked exceptions have already been checked.

8.1.12 Assertions

Assertions have been in programming languages for some time, and while they're not properly exception handling, they are a key part of the debugging process, and can even help you circle in on problems once you've deployed an application.

The assert statement was introduced with Java version 1.4. It is used to debug applications as they are being written. It helps you do this by verifying assumptions in your code. An assertion is written to state something that you, the programmer, believe should always be the case at the that point in your program. An assertion is written using the assert keyword and then a boolean expression representing what you believe should always evaluate to true at that point.

The assert statement does nothing so long as it evaluates to true. If the expression evaluates to false, a java.lang.AssertionError is thrown. By default, assertions are not enabled.

Note

Prior to JDK 1.4, the java.lang.Assertion error did not exist.


It was possible to use assertions previously in Java, and, in fact, an assertion is just a simple replacement for this code:

 if (x < 10) throw new java.lang.AssertionError("Oh no! x is     less than 10!"); 

What has been added is the ability to specify whether the statements should be executed. We will see how to do this in a moment.

Assertions look like this:

 assert booleanExpression; 

or

 assert booleanExpression : errorMessage; 

where booleanExpression is what you expect to be true. In the second form, errorMessage allows you to write an expression to be passed to the AssertionError 's constructor. It allows you to indicate a custom message regarding the nature of the error.

Note

The error message expression that gets passed to the constructor does not have to be a string. It can be any value, such as int or char , which are sometimes used to represent error codes.


Note that writing assert false will always fail. This can be useful if you've got a situation where all of your possible states are explicitly outlined. You could use an assert to throw an error in any other event:

 package chp7;  public class AssertTest { public static void main(String[] arg) {     int i = 1;     System.out.println("i is: " + i);     assert i == 1 : "i is not 1!"; } } 

Assertions have been available in other OOP languages, and so many Java programmers have tried to homegrow them. Now that assert is a keyword in Java 1.4, Sun has made an effort to avoid breaking code that may already exist, and you therefore need to supply an argument to the compiler in order to use assertions in your code. Compile programs using assertions like this:

 javac -source 1.4 AssertTest.java 

Remember that assertions are disabled by default, even if they have been compiled at compile time. You must explicitly turn on assert checks. To do this for one program, use java 's -ea option, which stands for "enable assertions":

 java -ea AssertTest 

Your assertion error will be thrown.


   
Top


Java for ColdFusion Developers
Java for ColdFusion Developers
ISBN: 0130461806
EAN: 2147483647
Year: 2005
Pages: 206
Authors: Eben Hewitt

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net