Throwable is the superclass for exceptions and errors. A Throwable will unwind the call stack until it is caught, or until the program (or thread) is completely unwound and terminates.
An Error represents a condition that an application should not try to catch; usually an abnormal condition, such as the VM running out of memory, or a class missing from the class path. In theory, it is impossible to recover from either of those conditions. The most common method of handling Error is to let it terminate the program, writing a useful log message. Then you can fix whatever happened to your build.
A real-world example of NoClassDefFoundError or NoSuchMethodError may occur when the wrong version of a library is included in the class path. This can happen whenever another library includes the library you thought you had asked for. We will not get into a long discussion of Maven, but running
mvn dependency:tree
will show which library includes another; adding exclusions on dependencies is the way to solve this problem.
RuntimeException and its subclasses are unchecked. This means that that the application is not expected to anticipate them. Examples are NullPointerException (covered extensively above), integer divide-by-zero, and array index out-of-bounds. Sometimes this should be treated like an Error, where you don’t catch it, but instead fix your code. Other times, such as handling invalid data input, the proper way to handle the problem is to reject the result of the bad data; if it came from the console, print a nasty message to the naughty person who gave you bad data (but be professional!).
These are conditions that the application may want to catch. Exception and its subclasses, that are not also subclasses of RuntimeException, are always checked. That means if a method might throw a checked exception, it must declare that in a throws clause, as here:
public void methodName(int param1, String param2) throws IOException {
<statements>
}
Any method that calls another method, which declares checked exceptions, must also either declare that it can throw that exception, or catch it.
When your code encounters an exceptional condition, you may throw an exception yourself. The first step is to create the exception. The zeroth step is to choose the type of exception to throw:
The constructors for standard exception classes come in four general flavors:
Some standard Exception types don't come with the flavors that take the cause parameter. These types are assumed never to be caused by another condition. When an exception is logged, it often includes a stack trace; if the throwable had a cause, that cause appears with its own stack trace. You should rarely (or never) call the no-parameter constructor. Create a useful message; your successors will thank you, and you might even thank yourself.
If you write your own exception class, you should probably include at least the constructor with a message parameter. Include the cause parameter if you might wrap a different Throwable.
If you ever catch and then re-throw an exception, throw a new exception with the caught exception as the cause.
In general, if a method has no idea what to do with an exception, it should not catch it. Rather, let the call stack unwind until it reaches a method that can handle the issue.
Different exceptions might be handled with different catch blocks; or sometimes with the same block. Consider:
try {
<something>
} catch (EOFException ex) {
<handle EOF>
} catch (FileNotFoundException | RemoteException ex) {
<handle those two>
} catch (IOException ex) {
<handle non-specific IOException>
}
You may have as many types separated by | as make sense for the catch block.
Note that the catch (IOException ex) block is last. If it came before EOFException's or FileNotFoundException's catch, you would never enter those catch blocks, because they are subclasses of IOException. Always catch the more specific exception first. The base class Throwable should only be caught explicitly as a last resort, and then only in a debugging situation, to figure out what was actually thrown and why.
Notes:
Veterans of C++ and its concept of Resource Acquisition Is Initialization (RAII) will miss the concept of a destructor that releases resources. Since Java uses garbage collection instead of destruction, and GC happens only when necessary, something else is needed.
Any try block (with or without catch blocks) may have a finally block as well. The statements in the finally block will be executed when either:
Resource rez;
try {
rez = acquireResource();
<statements>
} catch(Exception ex) {
<more statements>
} finally {
<even more statements>
releaseResource(rez);
}
The finally block can be used to release any resource acquired for the try. Make sure that any exceptions thrown from the finally block are handled, and that your resources are really released. Whether there are catch blocks before the finally block, or elsewhere in the stack chain, is an implementation detail.
Since Java 8, the try with resources form has added a shorthand for releasing resources:
static String readLineFromFile(String path) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
}
}
Both the unnamed FileReader and the BufferedReader br are resources that need to be released. The form of try above will cause any object constructed in the resources block, that implements the interface Closeable, to be closed as if from a finally block. See here for a full explanation.
The JVM is a Two’s Complement machine; so are most modern processors, including Intel x86 and x86_64 and ARM. (So are the 6800 and 68000 families, the 88000, Sparc, POWER and other IBM architectures, and many, many others.) If you have not studied computer logic design, that link can be a lot to digest, but the linked article would be well worth your time. Mostly, you can forget about that, but where you need to understand Two’s Complement math is when using Java’s shift operators. Remember, the left end of the number is the big end. The unary – operator, for negation, returns the two’s complement of the operand. By contrast, the unary ~ operator, which inverts every bit of its operand, returns the one’s complement.
The first thing to keep in mind about floating-point in Java (and other languages) is that not all real numbers can be expressed in floating-point. Please read this article: What Every Computer Scientist Should Know About Floating-Point Arithmetic.
Some basic points:
public class NanDemoApplication {
public static void main(String[] args) {
double one = 0.0 / 0.0;
double two = 0.0 / 0.0;
System.err.println("NaNs: " + (one == two ? "equal" : "NOT equal"));
one = 1.0 / 0.0;
two = 1.0 / 0.0;
System.err.println("Infs: " + (one == two ? "equal" : "NOT equal"));
}
}
The output will be:
NaNs: NOT equal
Infs: equal
A floating-point primitive with a NaN value won’t even be equal to itself. On the other hand, a boxed floating-point value is always equal to itself, even if the boxed value is NaN, because the == operator will do reference comparison. But calling foo.equals(foo) when foo’s referent is a NaN will return false. (See also the section on Unnecessary Autoboxing.)
While the primitive data type double can exactly express powers of 2, and sums of powers of 2, that can fit within the 64-bit definition, a BigDecimal can exactly express any number that can be written as a decimal number (this excludes all repeating decimals, such as ⅓, and all irrational numbers). The same rounding issues apply, though they are less important, since many more numbers can be expressed as BigDecimal than as double.
Tips for using BigDecimal:
public class Main {
public static void main(String[] args) {
BigDecimal e56 = new BigDecimal("1.234e56");
System.err.println(e56.toString());
System.err.println(e56.toEngineeringString());
System.err.println(e56.toPlainString());
}
}
The output is:
1.234E+56
123.4E+54
123400000000000000000000000000000000000000000000000000000