In Java, methods that throw checked exceptions (Exception or its subtypes - IOException, InterruptedException, etc) must declare throws sta
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 an Exception
, just as storing a String
in an Object
doesn't turn the String
into an Object
. Polymorphism means that a variable that can hold an Object
can hold anything derived from Object
(such as a String
). Likewise, as Exception
is the superclass of all exception types, a variable of type Exception
can hold any class derived from Exception
, without turning the object into an Exception
. Consider this:
import java.lang.*;
// ...
public String iReturnAString() { return "Consider this!"; }
// ...
Object o = iReturnAString();
Despite the variable's type being Object
, o
still stores a String
, does it not? Likewise, in your code:
try {
safeMethod();
} catch (Exception e) { // catching checked exception
throw e; // so I can throw... a checked Exception?
}
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 a std::exception
will also catch std::runtime_error
, std::logic_error
, std::bad_alloc
, any properly-defined user-created exceptions, and so on, because they all derive from std::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.
Java 7 introduced more inclusive exception type checking.
However, in Java SE 7, you can specify the exception types FirstException and SecondException in the throws clause in the rethrowException method declaration. The Java SE 7 compiler can determine that the exception thrown by the statement throw e must have come from the try block, and the only exceptions thrown by the try block can be FirstException and SecondException.
This passage is talking about a try
block that specifically throws FirstException
and SecondException
; even though the catch
block throws Exception
, the method only needs to declare that it throws FirstException
and SecondException
, not Exception
:
public void rethrowException(String exceptionName) throws FirstException, SecondException { try { // ... } catch (Exception e) { throw e; } }
This means that the compiler can detect that the only possible exception types thrown in test
are Error
s or RuntimeException
s, neither of which need to be caught. When you throw e;
, it can tell, even when the static type is Exception
, 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 ordinary Exception
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 general Exception
catching those specific subtypes. However, in this case, it allows you to catch a general Exception
and not have to declare any exception in a throws
clause, because no specific types that can be thrown are checked exceptions.
Quoting the Java Language Specification, §11.2.3:
It is a compile-time error if a catch clause can catch checked exception class E1 and it is not the case that the try block corresponding to the catch clause can throw a checked exception class that is a subclass or superclass of E1, unless E1 is Exception or a superclass of Exception.
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
, or Throwable
if you want to catch Error
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 a RuntimeException
will be an error:
try {
System.out.println("hello");
} catch (IOException e) { // compilation error
e.printStackTrace();
}