Handling exceptions

Exceptional cases should be handled in Java using exceptions. The ClassCastException is there and it happens when the sort tries to compare String to Integer using StringComparator, and to do that, it tries to cast an Integer to String.

When an exception is thrown by the program using the throw command, or by the Java runtime, the execution of the program stops at that point, and instead of executing the next command, it continues where the exception is caught. It can be in the same method, or in some calling method up in the call chain. To catch an exception, the code throwing the exception should be inside a try block, and the catch statement following the try block should specify an exception that is compatible with the exception thrown.

If the exception is not caught, then the Java runtime will print out the message of the exception along with a stack trace that will contain all the classes, methods, and line numbers on the call stack at the time of the exception. In our case, the mvn test command will produce the following trace in the output:

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String 
at packt.java9.by.example.ch03.bubble.StringComparator.compare(StringComparator.java:9)
at packt.java9.by.example.ch03.bubble.BubbleSort.sort(BubbleSort.java:13)
at packt.java9.by.example.ch03.bubble.BubbleSortTest.canNotSortMixedElements(BubbleSortTest.java:49)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
... some lines deleted from the print
at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:141)
at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:112)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
... some lines deleted from the print
at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115)
at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75)

This stack trace is not really long. In the production environment in an application that runs on an application server, the stack trace may contain a few hundred elements. In this trace, you can see that Maven was starting the test execution, involved Maven surefire plugin, and then the JUnit executor, until we get through the test to the comparator, where the actual exception was thrown.

This exception was not printed by the Java runtime to the console. This exception is caught by the JUnit library code and the stack trace is logged out to the console using Maven logging facility.

The problem with this approach is that the real issue is not the class casting failure. The real issue is that the collection contains mixed elements. It is only realized by the Java runtime when it tries to cast two incompatible classes. Our code can be smarter. We can amend the comparator.

package packt.java9.by.example.ch03.bubble; 
import java.util.Comparator;
public class StringComparator implements Comparator {
@Override
public int compare(Object first, Object second) {
try {
final String f = (String) first;
final String s = (String) second;
return f.compareTo(s);
} catch (ClassCastException cce) {
throw new NonStringElementInCollectionException (
"There are mixed elements in the collection.", cce);
}
}
}

This code catches the ClassCastException and throws a new one. The advantage of throwing a new exception is that you can be sure that this exception is thrown from the comparator and that the problem really is that there are mixed elements in the collection. Class casting problems may happen at other places of the code as well, inside some of the sort implementations. Some application code may want to catch the exception and want to handle the case; for example, sending an application-specific error message and not dumping only a stack trace to the user. This code can catch ClassCastException as well, but it cannot be sure what the real cause of the exception is. On the other hand, NonStringElementInCollectionException is definite.

The NonStringElementInCollectionException is an exception that does not exist in the JDK. We will have to create it. Exceptions are Java classes and our exception looks as follows:

package packt.java9.by.example.ch03.bubble; 

public class NonStringElementInCollectionException extends RuntimeException {
public NonStringElementInCollectionException (String message, Throwable cause) {
super(message, cause);
}
}

Java has the notion of checked exceptions. It means that any exception that is not extending RuntimeException should be declared in the method definition. Suppose our exception was declared as follows:

public class NonStringElementInCollectionException extends Exception

Then, we will have to declare the compare method as follows:

public int compare(Object first, Object second) throws NonStringElementInCollectionException

The problem is that the exception a method throws is part of the method signature, and this way compare will not override the compare method of the interface, and, that way, the class will not implement the Comparator interface. Thus, our exception has to be a runtime exception.

There can be a hierarchy of exceptions in an application, and often, novice programmers create huge hierarchies of them. If there is something you can do, it does not mean that you should do it. Hierarchies should be kept as flat as possible, and this is especially true for exceptions. If there is an exception in the JDK that describes the exceptional case, then use the readymade exception. Just as well as for any other class: if it is ready, do not implement it again.

It is also important to note that throwing an exception should only be done in exceptional cases. It is not to signal some normal operational condition. Doing that hinders readability of the code and also eats CPU. Throwing an exception is not an easy task for the JVM.

It is not only the exception that can be thrown. The throw command can throw, and the catch command can catch anything that extends the Throwable class. There are two subclasses of Throwable: Error, and Exception. The Error exception is thrown if some error happened during the execution of the Java code. The two most infamous errors are OutOfMemoryError and StackOverflowError. If any of these happens, you cannot do anything reliably to catch the error.

There is also InternalError and UnknownError in the JVM, but since JVM is fairly stable, you will hardly ever meet these errors.

When any of those errors happen, try to debug the code and try to find out why you use that much memory or such deep method calls and try to optimize your solution. What I have just said about creating exception hierarchies is true again to catch errors. The fact that you can catch errors does not mean that you should. On the contrary, you should never catch an error and, especially, never ever catch a Throwable.

This way, we handled this special case when some programmer accidentally writes 42 among the names, but will it be nicer if the error was identified during compile time? To do that, we will introduce generics.

Just a last thought before we go there. What class behavior do we test with the canNotSortMixedElements unit test? The test is inside the BubbleSortTest test class, but the functionality is in the comparator implementation, StringComparator. This test checks something that is out of the scope of the unit test class. I can use it for demonstration purposes, but this is not a unit test. The real functionality of the sort implementation can be formulized this way: whatever exception the comparator throws is thrown by the sort implementation. You can try to write this unit test, or read on; we will have it in the next section.

The StringComparator class does not have a test class because StringComparator is part of the test and we will never write a test for a test. Otherwise, we will sink into an endless rabbit hole.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.205.27