In Java, methods that throw checked exceptions (Exception or its subtypes - IOException, InterruptedException, etc) must declare throws statement:
public abstract int read() throws IOException;
Methods that do not declare throws
statement can't throw checked exceptions.
public int read() { // does not compile
throw new IOException();
}
// Error: unreported exception java.io.IOException; must be caught or declared to be thrown
But catching checked exceptions in safe methods is still legal in java:
public void safeMethod() { System.out.println("I'm safe"); }
public void test() { // method guarantees not to throw checked exceptions
try {
safeMethod();
} catch (Exception e) { // catching checked exception java.lang.Exception
throw e; // so I can throw... a checked Exception?
}
}
Actually, no. It's a bit funny: compiler knows that e is not a checked exception and allows to rethrow it. Things are even a bit ridiculous, this code does not compile:
public void test() { // guarantees not to throw checked exceptions
try {
safeMethod();
} catch (Exception e) {
throw (Exception) e; // seriously?
}
}
// Error: unreported exception java.lang.Exception; must be caught or declared to be thrown
The first snippet was a motivation for a question.
Compiler knows that checked exceptions can't be thrown inside a safe method - so maybe it should allow to catch only unchecked exceptions?
Returning to the main question - are there any reasons to implement catching checked exceptions in this way? Is it just a flaw in the design or am I missing some important factors - maybe backward incompatibilities? What could potentially go wrong if only RuntimeException
were allowed to be catched in this scenario? Examples are greatly appreciated.
Java 7 introduced more inclusive exception type checking.
This passage is talking about a
try
block that specifically throwsFirstException
andSecondException
; even though thecatch
block throwsException
, the method only needs to declare that it throwsFirstException
andSecondException
, notException
:This means that the compiler can detect that the only possible exception types thrown in
test
areError
s orRuntimeException
s, neither of which need to be caught. When youthrow e;
, it can tell, even when the static type isException
, that it doesn't need to be declared or re-caught.But when you cast it to
Exception
, this bypasses that logic. Now the compiler treats it as an ordinaryException
which needs to be caught or declared.The main reason for adding this logic to the compiler was to allow the programmer to specify only specific subtypes in the
throws
clause when rethrowing a generalException
catching those specific subtypes. However, in this case, it allows you to catch a generalException
and not have to declare any exception in athrows
clause, because no specific types that can be thrown are checked exceptions.The issue here is that checked/unchecked exception limitations affect what your code is allowed to throw, not what it's allowed to catch. While you can still catch any type of
Exception
, the only ones you're allowed to actually throw again are unchecked ones. (This is why casting your unchecked exception into a checked exception breaks your code.)Catching an unchecked exception with
Exception
is valid, because unchecked exceptions (a.k.a.RuntimeException
s) are a subclass of Exception, and it follows standard polymorphism rules; it doesn't turn the caught exception into anException
, just as storing aString
in anObject
doesn't turn theString
into anObject
. Polymorphism means that a variable that can hold anObject
can hold anything derived fromObject
(such as aString
). Likewise, asException
is the superclass of all exception types, a variable of typeException
can hold any class derived fromException
, without turning the object into anException
. Consider this:Despite the variable's type being
Object
,o
still stores aString
, does it not? Likewise, in your code:What this means is actually "catch anything compatible with class
Exception
(i.e.Exception
and anything derived from it)." Similar logic is used in other languages, as well; for example, in C++, catching astd::exception
will also catchstd::runtime_error
,std::logic_error
,std::bad_alloc
, any properly-defined user-created exceptions, and so on, because they all derive fromstd::exception
.tl;dr: You're not catching checked exceptions, you're catching any exceptions. The exception only becomes a checked exception if you cast it into a checked exception type.
Quoting the Java Language Specification, §11.2.3:
I'm guessing that this rule originated long before Java 7, where multi-catches did not exist. Therefore, if you had a
try
block that could throw a multitude of exceptions, the easiest way to catch everything would be to catch a common superclass (in the worst case,Exception
, orThrowable
if you want to catchError
s as well).Note that you may not catch an exception type that is completely unrelated to what is actually thrown - in your example, catching any subclass of
Throwable
that is not aRuntimeException
will be an error:Edit by OP: The main part of the answer is the fact that question examples work only for Exception class. Generally catching checked exceptions is not allowed in random places of the code. Sorry if I confused somebody using these examples.