Thursday, February 7, 2008

Java Exceptions: Checked Versus Unchecked Examples

The differences between checked and unchecked exceptions in Java is an important distinction to understand. There are several good articles and blog entries on checked versus unchecked exceptions and when to use them. The purpose of this blog entry is merely to demonstrate extremely simple examples of the differences between a checked exception and an unchecked exception.

The first class, CheckedException, is a simple example of a checked exception. This exception extends the java.lang.Exception exception class directly (does not extend RuntimeException) and is thus a checked exception.

CheckedException.java

package exceptions;

public class CheckedException extends Exception
{
public CheckedException(final String aMessage)
{
super(aMessage);
}
}


The second code listing is for UncheckedException. This class is nearly identical with our definition for class CheckedException above except for the fact that it, UncheckedException extends java.lang.RuntimeException. This seemingly minor difference (extending RuntimeException rather than extending Exception directly) makes a significant difference! This amazing difference is even more remarkable when you consider that RuntimeException actually extends Exception.

UncheckedException.java

package exceptions;

public class UncheckedException extends RuntimeException
{
public UncheckedException(final String aMessage)
{
super(aMessage);
}
}


The exception class that I named UncheckedException is an unchecked exception because it extends RuntimeException. The exception class that I named CheckedException is a checked exception because it does not in anyway extend RuntimeException, but does extend Exception.

The next code listing provides a test that will demonstrate clearly the difference between how a checked exception and an unchecked exception are handled in Java.

ExceptionTest.java

package exceptions;

public class ExceptionTest
{
/**
* Calculate quotient by dividing provided dividend by provided divisor.
*
* @param aDividend Dividend for division.
* @param aDivisor Divsor in division.
* @return Quotient of division (dividend divided by divisor).
*/
public double calculateQuotient(final int aDividend, final int aDivisor)
{
if ( aDivisor == 0 )
{
throw new UncheckedException("Cannot have zero in the divisor.");
}
return aDividend / aDivisor;
}

/**
* Calculate quotient by dividing provided dividend by provided divisor.
*
* @param aDividend Dividend for division.
* @param aDivisor Divsor in division.
* @return Quotient of division (dividend divided by divisor).
*/
public double calculateQuotient(final double aDividend, final double aDivisor)
{
if ( aDivisor == 0 )
{
throw new CheckedException("Cannot have zero in the divisor.");
}
return aDividend / aDivisor;
}

public static void main(final String[] aArguments)
{
ExceptionTest me = new ExceptionTest();
final double quotientWithInts = me.calculateQuotient(10,2);
final double quotientWithDoubles = me.calculateQuotient(10.2,2.0);
System.out.println("Quotient with ints: " + quotientWithInts);
System.out.println("Quotient with doubles: " + quotientWithDoubles);
}
}


In the ExceptionTest code above, both the CheckedException and the Unchecked exception are used in the same manner in the test code. Both are thrown when a divisor passed into a division method is zero.

When we try to compile the three classes shown above, we see the following compiler error (click on image to see larger version):



The compiler error reports: "exceptions\ExceptionTest.java:32: unreported exception exceptions.CheckedException; must be caught or declared to be thrown." The compiler error message goes on to show that the error occurred in the first character of the statement throw new CheckedException("Cannot have zero in the divisor.");.

Someone not familiar with the difference between a Java checked exception and a Java unchecked exception might wonder why the compiler error occurred only for CheckedException and not UncheckedException when they are both used identically in the ExceptionTest code. The difference is exactly what is pointed out in the compiler error: checked exceptions must be either captured in a catch block in the method in which they can be possibly thrown or there must be a throws clause added to that method's definition.

One way to address this compiler error is to have the method that throws the checked exception declare that it throws CheckedException as part of its signature. This is shown in the next code listing, which is mostly the same as the listing above for ExceptionTest, but with the changes made to make the code compile highlighted.

ExceptionTest.java with Checked Exception Throws Clause Added to Method that Might Throw It

package exceptions;

public class ExceptionTest
{
/**
* Calculate quotient by dividing provided dividend by provided divisor.
*
* @param aDividend Dividend for division.
* @param aDivisor Divsor in division.
* @return Quotient of division (dividend divided by divisor).
*/
public double calculateQuotient(final int aDividend, final int aDivisor)
{
if ( aDivisor == 0 )
{
throw new UncheckedException("Cannot have zero in the divisor.");
}
return aDividend / aDivisor;
}

/**
* Calculate quotient by dividing provided dividend by provided divisor.
*
* @param aDividend Dividend for division.
* @param aDivisor Divsor in division.
* @return Quotient of division (dividend divided by divisor).
*/
public double calculateQuotient(final double aDividend, final double aDivisor)
throws CheckedException
{
if ( aDivisor == 0 )
{
throw new CheckedException("Cannot have zero in the divisor.");
}
return aDividend / aDivisor;
}

public static void main(final String[] aArguments)
{
ExceptionTest me = new ExceptionTest();
final double quotientWithInts = me.calculateQuotient(10,2);
double quotientWithDoubles = 0;
try
{

quotientWithDoubles = me.calculateQuotient(10.2,2.0);
}
catch (CheckedException checkedException)
{
System.err.println("Checked Exception: " + checkedException.getMessage());
}

System.out.println("Quotient with ints: " + quotientWithInts);
System.out.println("Quotient with doubles: " + quotientWithDoubles);
}
}


The addition of the throws clause that indicates the method throws a checked exception makes the code compile, but also required that exception to be handled in the caller (the main function in this case). The output from running ExceptionTest is shown next:



Because zero was not passed in as a divisor to either method, neither CheckedException nor UncheckedException were ever thrown. Note that even in the code that did not compiler earlier, zero was never passed in to the function capable of throwing a checked exception, but the need to capture or explicitly state that the checked exception was being thrown was still required by the compiler.

Adding the throws clause to the method that is capable of throwing the checked exception is only one way to handle the situation. The other approach is to actually catch the checked exception in the method rather than throwing it to clients. This approach is shown in the next code example. This is similar to the two previous code listings for ExceptionTest, but has changes made for catching the error at its source highlighted.

ExceptionTest.java with Checked Exception Caught And Handled

package exceptions;

public class ExceptionTest
{
/**
* Calculate quotient by dividing provided dividend by provided divisor.
*
* @param aDividend Dividend for division.
* @param aDivisor Divsor in division.
* @return Quotient of division (dividend divided by divisor).
*/
public double calculateQuotient(final int aDividend, final int aDivisor)
{
if ( aDivisor == 0 )
{
throw new UncheckedException("Cannot have zero in the divisor.");
}
return aDividend / aDivisor;
}

/**
* Calculate quotient by dividing provided dividend by provided divisor.
*
* @param aDividend Dividend for division.
* @param aDivisor Divsor in division.
* @return Quotient of division (dividend divided by divisor).
*/
public double calculateQuotient(final double aDividend, final double aDivisor)
{
try
{

if ( aDivisor == 0 )
{
throw new CheckedException("Cannot have zero in the divisor.");
}
}
catch (CheckedException checkedException)
{
System.err.println("ERROR: attempted to divide by zero!");
}

return aDividend / aDivisor;
}

public static void main(final String[] aArguments)
{
ExceptionTest me = new ExceptionTest();
final double quotientWithInts = me.calculateQuotient(10,2);
double quotientWithDoubles = me.calculateQuotient(10.2,2.0);
System.out.println("Quotient with ints: " + quotientWithInts);
System.out.println("Quotient with doubles: " + quotientWithDoubles);
}
}


With this approach, because the exception was handled nearer to its source, the client code (the main function in this case) did not need to handle the exception. When possible, I prefer this because the client does not then need to handle the error. Note that this example is particularly contrived because it is silly to throw the CheckedException just to catch it again, but it does display how a checked exception works and the two choices Java developers have when dealing with checked exceptions.

By the way, running the third example of ExceptionTest produces the same output as the second example of ExceptionTest.

Besides the links included above to other blogs and articles on checked versus unchecked exceptions, see also Best Practices for Exception Handling and Designing with Exceptions: When and How to Use Exceptions for additional details on exception handling in Java and the differences between checked and unchecked exceptions. I intentionally avoided the checked versus unchecked exception debate here and instead focused on what the difference is between the two.

No comments: