Why doesn't Java support generic Throwables?
class Bouncy<T> extends Throwable {
}
// Error: the generic class Bouncy<T> may not subclass java.lang.Throwable
Why doesn't Java support generic Throwable
s?
I realize that type erasure complicates certain things, but obviously Java gets by with a lot already, so why not push it one more notch and allow generic Throwable
s, with comprehensive compile-time check for potential problems?
I feel like the type erasure argument is rather weak. Currently, we can't do:
void process(List<String> list) {
}
void process(List<Integer> list) {
}
Of course, we get by without it. I'm not asking that we should be able to do catch Bouncy<T1>
and Bouncy<T2>
in the same try
block, but if we use them in disjoint contexts with strict compile-time enforceable rules (which is pretty much the way generics works right 开发者_如何学Cnow), wouldn't it be workable?
Java Language Specification 8.1.2 Generic Classes and Type Parameters:
This restriction is needed since the catch mechanism of the Java virtual machine works only with non-generic classes.
Personally, I think it's because we can't get any benefits of generics inside a catch
clause. We can't write catch (Bouncy<String> ex)
due to type erasure, but if we write catch (Bouncy ex)
, it would be useless to make it generic.
Short answer: because they took shortcuts, just like they did with erasure.
Long answer: as others already indicated, because of erasure, there is no way to make a difference at runtime between a "catch MyException<String>" and "catch MyException<Integer>".
But that doesn't mean that there is no need for generic exceptions. I want generics to be able to use generic fields! They could have simply allowed generic exceptions, but only allow catching them in the raw state (e.g. "catch MyException").
Granted, this would make generics even more complicated. This is to show just how bad the decision to erase generics was. When will we have a Java version that supports real generics (with RTTI), not the current syntactic sugar?
Type erasure. Runtime exception type has no generics information. Thus you cannot do
} catch( Mistake<Account> ea) {
...
} catch( Mistake<User> eu) {
...
}
all you can do is
catch( Mistake ea ) {
...
}
And type erasure is how it was decided to preserve the backward compatibility when Java was moving from 1.4 to 1.5. Many people was unhappy then, and rightfully so. But having in mind the amount of deployed code, it was unthinkable to break code that worked happily in 1.4.
You can still use generic methods, like this:
public class SomeException {
private final Object target;
public SomeException(Object target) {
this.target = target;
}
public <T> T getTarget() {
return (T) target;
}
}
....
catch (SomeException e) {
Integer target = e.getTarget();
}
I do agree with Cristian's answer above. While the accepted answer is technically correct (insofar as it references the JVM specs), Cristian Vasile's answer is one that qualifies and even challenges the limitation.
There are least two arguments that I noted in answers to this question that I do not agree with and that I will rebut. If the arguments in these answers were correct, we could use these arguments to attack generics in other contexts where they are used successfully today.
The first argument states that we can't use this:
catch (Exception<T1> e) {}
because the JVM doesn't know how to work with Exception<T1>
. This argument would seem to also attack this use of generics, on the basis that the JVM doesn't know how to use List<T1>
:
List<T1> list;
The argument, of course, forgets that the compiler performs type erasure and so the JVM doesn't need to know how to handle Exception<T1>
. It can simply handle Exception
, just like it handles List
.
Of course, we could never handle catch(Exception<T1> e)
and catch(Exception<T2> e)
in the same try/catch because of type erasure, but then again, that's no worse than with method arguments or return values today: we don't handle myMethod(List<T1>)
and myMethod(List<T2>)
today either... (I reiterate this aspect in the second rebuttal below.)
A second argument goes as follows. We don't allow this:
catch (Exception<T1> e) {}
because this wouldn't work:
catch (Exception<T1> e) {}
catch (Exception<T2> e) {}
OK, then why not disallow this:
interface MyInterface {
Comparable<Integer> getComparable();
}
because this does not work:
interface MyInterface {
Comparable<Integer> getComparable();
Comparable<String> getComparable();
}
or this:
interface MyInterface {
void setComparable(Comparable<Integer> comparable);
}
because this does not work:
interface MyInterface {
void setComparable(Comparable<Integer> comparable);
void setComparable(Comparable<String> comparable);
}
In other words, why not disallow generics in most cases?
This second argument forgets that, although we couldn't possibly allow different generic constructs that that erase to the same non-generic construct in these contexts, we can still do the next best thing and allow generics as long as the types don't erase to the same type. That's what we do with method parameters: we allow you to use generics, but complain as soon as we detect duplicate signatures after type erasure. Well, we could have done about the same thing with exceptions and catch blocks...
In conclusion, I would expand on Cristian's answer. Instead of allowing generic exception classes and using the raw types in catch
blocks:
class MyException<T> {}
...
catch (MyException e) { // raw
Java could have gone the whole way without problems:
class MyException<T> {}
...
catch (MyException<Foo> e) {
Here are a couple of things you can do:
- Throwables can implement generic interfaces, as long as the throwable itself has no type parameters, e.g.
interface Bouncy<E> {
// ...
}
class BouncyString extends Exception implements Bouncy<String> {
// ...
}
- A
throws
clause can refer to type parameters, e.g.static <X extends Throwable> void
throwIfInstanceOf(Throwable ex, Class<X> clazz) throws X {
if (clazz.isInstance(ex)) throw clazz.cast(ex);
}
精彩评论