Почему Java не допускает общие подклассы Throwable? - PullRequest
134 голосов
/ 01 февраля 2009

Согласно Спецификация языка Java , 3-е издание:

Ошибка времени компиляции, если универсальный класс является прямым или косвенным подклассом Throwable.

Я хочу понять, почему это решение было принято. Что не так с общими исключениями?

(Насколько я знаю, дженерики - это просто синтаксический сахар времени компиляции, и они в любом случае будут преобразованы в Object в файлах .class, поэтому объявление родового класса эффективно, как будто все Object. Пожалуйста, поправьте меня, если я ошибаюсь.)

Ответы [ 5 ]

146 голосов
/ 01 февраля 2009

Как сказано, типы не подлежат перерасчету, что является проблемой в следующем случае:

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

И SomeException<Integer>, и SomeException<String> стираются в один и тот же тип, у JVM нет возможности различить экземпляры исключений, и поэтому нет способа определить, какой блок catch должен быть выполнен.

13 голосов
/ 01 февраля 2009

Вот простой пример использования исключения:

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

Тело оператора TRy выдает исключение с заданным значением, которое перехватывается предложением catch.

Напротив, следующее определение нового исключения запрещено, поскольку оно создает параметризованный тип:

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

При попытке скомпилировать вышесказанное выдает ошибку:

% 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

Это ограничение имеет смысл, потому что почти любая попытка поймать такое исключение должна потерпеть неудачу, потому что тип не является переопределенным. Можно ожидать, что типичное использование исключения будет выглядеть примерно так:

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;
    }
  }
}

Это недопустимо, потому что тип в предложении catch не является переопределенным. На момент написания этой статьи компилятор Sun сообщал о каскаде синтаксических ошибок в таком случае:

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

Поскольку исключения не могут быть параметрическими, синтаксис ограничен, поэтому тип должен записываться как идентификатор без следующего параметра.

12 голосов
/ 09 февраля 2015

Это в основном потому, что он был спроектирован неправильно.

Эта проблема мешает чистому абстрактному дизайну, например,

public interface Repository<ID, E extends Entity<ID>> {

    E getById(ID id) throws EntityNotFoundException<E, ID>;
}

Тот факт, что условие catch для дженериков не выполнится, не является оправданным. Компилятор может просто запретить конкретные обобщенные типы, которые расширяют Throwable или запрещать обобщенные типы внутри предложений catch.

4 голосов
/ 30 сентября 2017

Обобщения проверяются во время компиляции на корректность типов. Информация об общем типе затем удаляется в процессе, называемом стирание типа . Например, List<Integer> будет преобразовано в неуниверсальный тип List.

Из-за стирания типа параметры типа не могут быть определены во время выполнения.

Предположим, вам разрешено расширять Throwable следующим образом:

public class GenericException<T> extends Throwable

Теперь давайте рассмотрим следующий код:

try {
    throw new GenericException<Integer>();
}
catch(GenericException<Integer> e) {
    System.err.println("Integer");
}
catch(GenericException<String> e) {
    System.err.println("String");
}

Из-за стирания типа среда выполнения не будет знать, какой блок перехвата выполнить.

Следовательно, ошибка времени компиляции, если универсальный класс является прямым или косвенным подклассом Throwable.

Источник: Проблемы с стиранием типа

2 голосов
/ 01 февраля 2009

Я ожидаю, что это потому, что нет способа гарантировать параметризацию. Рассмотрим следующий код:

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

Как вы заметили, параметризация - это просто синтаксический сахар. Тем не менее, компилятор пытается обеспечить согласованность параметров во всех ссылках на объект в области компиляции. В случае исключения компилятор не может гарантировать, что MyException выбрасывается только из области, которую он обрабатывает.

...