-
Notifications
You must be signed in to change notification settings - Fork 0
Safety Guide
Unchecked Java circumvents Java's exception handling mechanisms and can lead to horrible, often very hard to debug errors, when misused.
In the words of Brian Goetz (Java Language Architect):
Warning
Just because you don’t like the rules, doesn’t mean its a good idea to take the law into your own hands. Your advice is irresponsible because it places the convenience of the code writer over the far more important considerations of transparency and maintainability of the program.
First, and I can't believe I am saying this, if you are wondering whether you should use Unchecked Java, you are probably safer off without it.
If you are going to use it, you have to ensure that the caller will catch all possible checked exceptions that you are circumventing with Unchecked Java.
Consider this method which calculates the total size of a list of files:
public static long getSize(final List<Path> files) {
final long size = files.stream().mapToLong(file -> {
try {
return Files.size(file);
} catch (final IOException cause) {
throw new RuntimeException("Unable to get file size of " + file, cause);
}
}).sum();
return size;
}
Notice that you have no choice but to rethrow the IOException
wrapped in a RuntimeException
because the underlying ToLongFunction
interface does not throw checked exceptions.
With Unchecked Java we can make our code much more readable:
import static software.leonov.common.util.function.CheckedToLongFunction.unchecked;
...
public static long getSize(final List<Path> files) { // does not throw any checked exceptions
return files.stream().mapToLong(unchecked(Files::size)).sum();
}
But what you don't realize is that we just put the users of our API (and the JVM) in an impossible position. The signature of the getSize
method does not include an IOException
. There is no reason for the caller to assume or prepare for the possibility that an I/O error of some sort can occur. This can be easily remedied by adding a throws clause to our method:
public static long getSize(final List<Path> files) throws IOException { // fixed
return files.stream().mapToLong(unchecked(Files::size)).sum();
}
In some contexts it may make sense to throw a super exception:
public static long getSize(final List<Path> files) throws Exception { // throws Exception instead of IOException
return files.stream().mapToLong(unchecked(Files::size)).sum();
}
Most good APIs and libraries attempt to prevent their users from shooting themselves in the foot. But in the hands of a reckless developer Unchecked Java can make it easier to write bad code.
Suppose you are writing a method that stores the results of an SQL select query to a file. It becomes your responsibility to close all the all streams and resources (Connection
s, Statement
s, ResultSet
s, and etc...) before the method returns. A naïve user may decide to abuse Unchecked Java and simply propagate all checked exceptions as if they were unchecked, neglecting proper handling I/O resources.
Even if the method signature includes the necessary throws clause, careless use of Unchecked Java can result in risky and problematic code.
If you are an experienced Java developer I hope you are beginning to see that correctly using Unchecked Java can lead to a substantial reduction of boiler plate code while significantly enhancing readability.
I recently used Unchecked Java while writing long running tasks involving a dynamic (not known until runtime) number of I/O operations submitted to a ThreadPoolExecutor
. Remember that you are guaranteed that any possible errors which occur in a Runnable
or Callable
task will be stored in the returned Future
object.
With Unchecked Java I was able to significantly reduce the number of nested try/catch blocks and remove the surplus stack traces resulting from wrapping IOException
s in RuntimeException
s.