Jim H
Jim H
  • Home
  • Resume
  • Code Review-BestPractices
  • Java Best Practices
  • Kotlin Best Practices
  • Homophones in English
  • Personal
  • Blog

Throwables

Back to Contents


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.

Error

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 and Gradle, but running

         mvn dependency:tree

or

         gradle dependencies

will show which library includes another; adding exclusions on dependencies is the way to solve this problem.

Unchecked Exceptions

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!).

Checked Exceptions

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.

Throwing Exceptions

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 Java library has a large number of "standard" subclasses of Exception and Error. 
  • You will probably never throw an Error, as most are thrown by conditions detected by the JVM (OutOfMemoryError, NoSuchMethodError).
  • Most exceptional conditions you detect will likely be appropriate for some subclass of RuntimeException. For the default case of a switch, you might want to throw an UnsupportedOperationException. A floating-point operation that results in a NaN or Infinity (see below) might warrant an ArithmeticException.
  • In some cases, you will want to define your own exception type. Make sure it inherits from Exception (or from RuntimeException, if you don't want a checked exception), through zero or more intermediate classes.

The constructors for standard exception classes come in four general flavors:

  • A plain constructor with no parameters;
  • A constructor that takes String message, to describe the condition;
  • A constructor that takes Throwable cause, to wrap another condition; and
  • A constructor that takes both message and cause.

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 (the caught exception's trace, followed by "Caused by", followed by the cause's 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.

Where Should You Catch?

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: 

  • When you explicitly throw an exception, write a meaningful message 
  • Never leave a catch block empty. This would result in the exception just being ignored, and the program continuing as if nothing bad had happened. That might not be the right way to handle a serious issue.

Releasing Resources

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.


finally

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:

  • If no exception is thrown, when the try block exits; or
  • If any catch block catches the exception, after the catch block exits.
  • If no catch block catches the exception, when the exception is thrown.

Resource rez = acquireResource();

 try {

  <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.


try with resources

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.

Integer Arithmetic

Back to Contents


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 including the System/360, 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 simple (pencil and paper) way to get the two’s complement of a number: starting from the rightmost bit, keep all the zeroes to the right of the rightmost one bit; also keep the rightmost one; then invert every bit to the left of the rightmost one.
  • The relationship between one’s complement and two’s complement: If anInt is an integer value, then (~anInt + 1) == -anInt. Some interesting examples:
    • The value 1 is a word with all zero bits except the rightmost. Therefore, ~1 is a word with all one bits except the rightmost; adding 1 makes the rightmost bit one, and no other changes. That means all one bits, or -1.
    • ~0 is a word of all one bits, (still -1). Adding 1 yields 0.
    • Integer.MIN_VALUE (-2147483648) is, in Two’s Complement hexadecimal 0x80000000. Remember the pencil and paper method: keep everything to the right of, and including, the rightmost one bit. Therefore: 
    • -Integer.MIN_VALUE == Integer.MIN_VALUE, and 
    • Math.abs(Integer.MIN_VALUE) == Integer.MIN_VALUE (this is the only result of Math.abs(int) that is negative). 
  • Left shift, or i << n, will move the bits of i to the left by n places, adding "zero" bits to the right; effectively multiplying I by the nth power of 2.
  • Arithmetic (signed) right shift, or i >> n, does the inverse, dividing i by 2^n. This copies the leftmost bit (which, in Two’s Complement math, is the sign bit).
  • Logical (unsigned) right shift, or i >>> n, will always shift "zero" bits onto the left. The value of the expression (-1L >>> 1) is equal to Long.MAX_VALUE.
  • For operations other than right-shift, the rules for evaluating signed and unsigned expressions are exactly the same–although the way you interpret the results would be different, the exact same bit patterns are produced. This makes implementation simpler–both for hardware and software.
  • One “trick” of Two’s Complement math: the expression (i & -i) is an integer with only the rightmost one bit of i set to 1 (why? see the statement above about pencil-and-paper two’s complement). To see a possible application of this trick, compare the "countOnes" methods in the appendix.

Floating-point Arithmetic

Back to Contents


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:

  • The IEEE-754 standard allows floating-point representations in binary and decimal.  The native floating-point types in Java (float, double) and their auto-boxed equivalents are binary.  Java has an implementation (BigDecimal) that is decimal (it's also big).
  • A primitive floating-point number can represent, exactly, many powers of 2, and sums of powers of 2. A number like 0.1 (decimal) is a repeating pattern in binary, so cannot be expressed exactly.  (This is analogous to ⅓, which cannot be expressed exactly in decimal.)
  • A single (float) distributes its 32 bits as a 23-bit mantissa, 8-bit exponent, and 1 sign bit.  A double has a 52-bit mantissa, 11-bit exponent, and 1 sign bit.  One implication of this is, there are two distinct ways to express zero (-0 and +0).  This is less of an issue than one might think, because of the next point, but also because -0 actually tests equal to +0.
  • Since most real numbers cannot be expressed exactly, most calculations will involve rounding error.  Therefore, testing for equality in floating-point results is pure folly.  Instead, one should test that two floats are within “epsilon” of each other (the value of epsilon is, of course, dependent on the application).
  • The standard includes values for infinities (+∞ and -∞) and NaNs (“not a number”).  The simplest way, in a generic programming language, to generate +∞ is: 

          float inf = 1.0F / 0.0F; 

  • Getting -∞ is left as an exercise. (Of course, even simpler is the constant Double.POSITIVE_INFINITY.)
  • Intentionally generating a NaN can be done several ways; one is 0.0 / 0.0 (and, of course, Double.NaN).
  • Generally, infinities and NaNs propagate through calculations. This lets you carry out a complex expression, and only check for infinity or NaN at the end, rather than at each intermediate point.  Examples: 
    • NaN + value = NaN
    • ±∞ + value = ±∞
    • ±∞ + NaN = NaN
    • +∞ + -∞ = NaN
  • In comparison operations, a NaN is not less than, nor greater than, nor equal to, another value.  In other words, (a < b || a >= b) is true most of the time, but false if either a or b is a NaN. Also, two NaNs are never equal to each other:

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.)


BigDecimal

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:

  • It’s important to understand the format.  The important components:
    • BigInteger intVal: basically an arbitrarily long string of decimal digits
    • int scale: the power of 10 by which to divide intVal to get the object’s value
    • int precision: number of significant digits, as defined in scientific calculations
  • This implies that the same value can be expressed with different scale/precision values, because trailing (fractional) zeroes can be involved.
  • BigDecimal.equals() will return false if scale and precision are different, even for the same value.  BigDecimal.compareTo() will return 0 (i.e. "equal") for values that represent the same number, even with different scale/precision.
  • BigDecimal.signum() returns a comparison with zero; -1 indicates a negative value, +1 a positive value and 0 for zero.  While it’s valid to call BigDecimal.ZERO.compareTo(value), it’s better (faster) to call value.signum() (as long as value is not null).
  • You can instantiate a BigDecimal with a double; but it will be subject to the rounding implicit in a double.  You could also instantiate a BigDecimal with a String, and get the exact value.  The value you get with new BigDecimal(10.1) will not be the same as with new BigDecimal("10.1").
  • Arithmetic operations on BigDecimal are done by method calls.  For example, there is BigDecimal.multiply(BigDecimal factor).  There are other versions; and several different overloads of .divide()—see the point below on RoundingMode.
  • BigDecimal defines several constants, including ZERO, ONE and TEN.
  • The enum RoundingMode is used to define how division works.  There are modes for rounding toward and away from zero, toward and away from +∞, or to the nearest value. 
  • Also note that there are 3 different ways of rounding to nearest when the value to be rounded is exactly half-way between two target values:  always up, always down, up when even. The differences show how to round 5.35 and 5.45 to a precision of 1:

  • The Javadoc on ANY value for RoundingMode will show a table of examples.
  • Also, see the class MathContext, which incorporates a precision and a rounding mode.
  • How do you like your numbers to be printed?
    • toString(): scientific notation
    • toEngineeringString() : engineering notation (like scientific notation, but the exponent is always a multiple of 3)
    • toPlainString(): with no exponent field


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

First PagePreviousNextHome

Copyright © 2019-2025 Jim Hamilton - All Rights Reserved.

Powered by

This website uses cookies.

We use cookies to analyze website traffic and optimize your website experience. By accepting our use of cookies, your data will be aggregated with all other user data.

DeclineAccept