Why doesn't Java support generic Throwables?

后端 未结 5 1632
独厮守ぢ
独厮守ぢ 2020-12-14 17:02
class Bouncy extends Throwable {     
}
// Error: the generic class Bouncy may not subclass java.lang.Throwable

Why doesn\'t Java

相关标签:
5条回答
  • 2020-12-14 17:36

    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.

    0 讨论(0)
  • 2020-12-14 17:43

    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) {
    
    0 讨论(0)
  • 2020-12-14 17:48

    Here are a couple of things you can do:

    1. 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> {
          // ...
      }

    2. 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);
      }

    0 讨论(0)
  • 2020-12-14 17:54

    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.

    0 讨论(0)
  • 2020-12-14 17:54

    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?

    0 讨论(0)
提交回复
热议问题