Encapsulation is one of the primary benefits of object-oriented programming. It allows one object to give direction to another object without having to know how those directions will be carried out. The java.awt.FlowLayout object doesn’t care how the javax.swing.JList arrives at its preferred size. So long as it returns an accurate Dimension, the “how” isn’t relevant.
Unfortunately, this freedom from details that good encapsulation provides breaks down when errors start to crop up. Looking at the example in Listing A, we can see that when things are working well, the Scheduler object doesn’t care how the Task objects arrive at the value they return for the getDuration method. However, when things go wrong, suddenly the Scheduler is looking at a java.lang.IOException from the DownloadTask, when the Scheduler wasn’t even aware IO was going to happen.
In Listing B, we can see a clean, object-oriented way to solve this problem. Task translates the exception into a subclass of Exception with which the Scheduler is familiar. Thus, instead of having a Scheduler instance puzzling over a java.io.IOException, it creates a TaskException with which the Scheduler knows how to deal elegantly.
The drawback to this solution is that the IOException that started the whole mess is now lost. It has been consumed by the Task object and replaced by a TaskException. This allows for cleaner exception handling by the Scheduler but doesn’t help the developer who will likely have to debug the error later.
Before Java 1.4
Before Java 1.4 was released, you had three ways do deal with the situation. First, you could pass the IOException all the way to the top and let the uppermost layers in the calling stack try to deal with an exception whose type they knew nothing about, as shown in Listing A. Second, you could create increasingly general replacement exceptions all the way up the chain like the TaskException shown in Listing B. Third, you could create your own Exception subclass to allow for the associating of a cause Throwable with the exception. This last route is well explained in Henri Yandell’s article “Two ways to work more effectively with Java exceptions.” Given those options, the third one is certainly the best choice, but it starts to fall apart when the use of someone else’s API prevents you from using your own Exception subclass everywhere.
In Java 1.4
Fortunately, Java 1.4 requires no compromise. Now the java.lang.Throwable class, the base class of java.lang.Exception, has a cause member field, which can be used to pass the important details of the original problem up the chain all the way to the user or a log file. This cause field can be set either in the constructor of Throwable or using a provided setter method. In the former case, you simply add a Throwable after the string, which is to become the exception’s message in the constructor’s parameter list. In the later case, you use the initCause method to set the cause on an already-constructed Throwable.
There is a corresponding getter method, getCause, but its use is not terribly common, as the printStackTrace method has been modified to descend the full depth of the exception chain. This allows developers to use good encapsulation in their designs without any reduction in their ability to debug problems.
In Listing C, we can see our Scheduler/Task setup modified to take advantage of the Java 1.4’s exception chaining. When the TaskException has percolated its way to the top, the IOException that was the root cause is still available. With exception chaining in our 1.4 toolboxes, there’s no longer any excuse for sloppy exception handling.
What Java topics do you want to see? Post a comment below or e-mail us.