views:

1108

answers:

5

According to the Java Language Sepecification, 3rd edition:

It is a compile-time error if a generic class is a direct or indirect subclass of Throwable.

I wish to understand why this decision has been made. What's wrong with generic exceptions?

(As far as I know, generics are simply compile-time syntactic sugar, and they will be translated to Object anyway in the .class files, so effectively declaring a generic class is as if everything in it was an Object. Please correct me if I'm wrong.)

+6  A: 

Here is a simple example of how to use the exception:

class IntegerExceptionTest {
  public static void main(String[] args) {
    try {
      throw new IntegerException(42);
    } catch (IntegerException e) {
      assert e.getValue() == 42;
    }
  }
}

The body of the TRy statement throws the exception with a given value, which is caught by the catch clause.

In contrast, the following definition of a new exception is prohibited, because it creates a parameterized type:

class ParametricException<T> extends Exception {  // compile-time error
  private final T value;
  public ParametricException(T value) { this.value = value; }
  public T getValue() { return value; }
}

An attempt to compile the above reports an error:

% javac ParametricException.java
ParametricException.java:1: a generic class may not extend
java.lang.Throwable
class ParametricException<T> extends Exception {  // compile-time error
                                     ^
1 error

This restriction is sensible because almost any attempt to catch such an exception must fail, because the type is not reifiable. One might expect a typical use of the exception to be something like the following:

class ParametricExceptionTest {
  public static void main(String[] args) {
    try {
      throw new ParametricException<Integer>(42);
    } catch (ParametricException<Integer> e) {  // compile-time error
      assert e.getValue()==42;
    }
  }
}

This is not permitted, because the type in the catch clause is not reifiable. At the time of this writing, the Sun compiler reports a cascade of syntax errors in such a case:

% javac ParametricExceptionTest.java
ParametricExceptionTest.java:5: <identifier> expected
    } catch (ParametricException<Integer> e) {
                                ^
ParametricExceptionTest.java:8: ')' expected
  }
  ^
ParametricExceptionTest.java:9: '}' expected
}
 ^
3 errors

Because exceptions cannot be parametric, the syntax is restricted so that the type must be written as an identifier, with no following parameter.

01
What do you mean when you say 'reifiable'? 'reifiable' is not a word.
ForYourOwnGood
I didn't know the word myself, but a quick search in google got me this: http://java.sun.com/docs/books/jls/third_edition/html/typesValues.html#4.7
Hosam Aly
Thank you @mark.
Hosam Aly
+2  A: 

I would expect that it's because there's no way to guarantee the parameterization. Consider the following code:

try
{
    doSomethingThatCanThrow();
}
catch (MyException<Foo> e)
{
    // handle it
}

As you note, parameterization is just syntactic sugar. However, the compiler tries to ensure that parameterization remains consistent across all references to an object in compilation scope. In the case of an exception, the compiler has no way to guarantee that MyException is only thrown from a scope that it is processing.

kdgregory
Yes, but why isn't it flagged as "unsafe" then, as with casts for example?
eljenso
Because with a cast, you are telling the compiler "I know that this execution path produces the expected result." With an exception, you can't say (for all possible exceptions) "I know where this was thrown." But, as I say above, it's a guess; I wasn't there.
kdgregory
"I know that this execution path produces the expected result." You don't know, you hope so. That's why generic and downcasts are statically unsafe, but they are nevertheless allowed. I upvoted Torsten's answer, because there I see the problem. Here I don't.
eljenso
If you don't know that an object is of a particular type, you shouldn't be casting it. The whole idea of a cast is that you have more knowledge than the compiler, and are making that knowledge explicitly part of the code.
kdgregory
Yes, and here you may have more knowledge than the compiler as well, since you want to do an unchecked conversion from MyException to MyException<Foo>. Maybe you "know" it will be a MyException<Foo>.
eljenso
But in the general case, you can't know that about exceptions. You might know it about your specific exception type, but not about any possible exception. So its disallowed.
kdgregory
I think a comparison could be made to the requirement that local variables (and parameters) used by an anonymous inner class must be final. The only reason for that requirement is that it provides a consistent mental model: the variable will not change independently in the class and method.
kdgregory
But again, I have no input into the JLS, so everything I've said in this answer and comments is speculation.
kdgregory
The reason local vars used by an inner class must be final and definitely assigned to, is because your inner class then can work on a copy of those variables to avoid scoping issues when the enclosing method exits. Not because of a mental model. Again I fail to see your point :)
eljenso
I have not written anything in the JLS either, but that does not mean we can't have a discussion about it.
eljenso
Local vars in anon classes are entirely about the mental model. As you note, they values are copied at construction. There's no physical reason not to let them change independently. The mental model, however, is that there's a single variable, so they shouldn't change independently.
kdgregory
Very nice and interesting discussion! :)
Hosam Aly
+14  A: 

As mark said, the types are not reifiable, which is a problem in the following case:

try {
   doSomeStuff();
} catch (SomeException<Integer> e) {
   // ignore that
} catch (SomeException<String> e) {
   crashAndBurn()
}

Both SomeException<Integer> and SomeException<String> are erased to the same type, there is no way for the JVM to distinguish the exception instances, and therefore no way to tell which catch block should be executed.

Torsten Marek
Thanks @Torsten. This is the clearest answer IMO.
Hosam Aly
+1, I agree with Hosam
JaredPar
+1 for being the only answer to mention "type erasure"
matt b
but what does "reifiable" mean?
aberrant80
@aberrant80: simply speaking it means that you can get to the concret type at runtime. Which is not the fact in Java.
Joachim Sauer
A: 

Others have correctly mentioned that the parameter type is erased at runtime, and that's the reason why this use case is dodgy. I wonder, however, why would Java language designers choose to forbid the creation of parametric types altogether, instead of flagging the ambiguous catch construct as an error. And why not offer a single-catch workaround MyException<?>, for the matter of that.

+1  A: 

In case anyone's still interested in a longer and more detailed explanation, I found this article on Javalobby.

aberrant80