Подтвердить что ты не робот

Почему Java не разрешает общие подклассы Throwable?

В соответствии с Java Seecification, 3-е издание:

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

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

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

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

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

Ответ 3

Это по существу потому, что он был разработан плохо.

Эта проблема предотвращает чистый абстрактный дизайн, например,

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

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

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

Ответ 4

Общие данные проверяются во время компиляции для правильности ввода. Затем информация об общем типе удаляется в процессе, называемом стиранием типа. Например, 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.

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

Ответ 5

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

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

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