Почему Java не поддерживает универсальные Throwables?
Вопрос
class Bouncy<T> extends Throwable {
}
// Error: the generic class Bouncy<T> may not subclass java.lang.Throwable
Почему Java не поддерживает generic Throwable
с?
Я понимаю, что стирание типов усложняет определенные вещи, но, очевидно, Java и так со многим справляется, так почему бы не продвинуть ее еще на одну ступеньку и не разрешить generic Throwable
s, с всесторонней проверкой во время компиляции на наличие потенциальных проблем?
Я чувствую, что аргумент удаления типа довольно слаб.В настоящее время мы не можем сделать:
void process(List<String> list) {
}
void process(List<Integer> list) {
}
Конечно, мы обходимся без этого.Я не прошу, чтобы мы были в состоянии сделать catch Bouncy<T1>
и Bouncy<T2>
в том же самом try
блок, но если мы будем использовать их в непересекающихся контекстах со строгими правилами, подлежащими исполнению во время компиляции (что в значительной степени соответствует тому, как дженерики работают прямо сейчас), разве это не было бы выполнимо?
Решение
Спецификация языка Java 8.1.2 Общие классы и параметры типа:
Это ограничение необходимо, поскольку механизм catch виртуальной машины Java работает только с не-универсальными классами.
Лично я думаю, это потому, что мы не можем получить никаких преимуществ от дженериков внутри catch
оговорка.Мы не можем писать catch (Bouncy<String> ex)
из-за стирания типа, но если мы напишем catch (Bouncy ex)
, было бы бесполезно делать его универсальным.
Другие советы
Введите стирание.Тип исключения во время выполнения не содержит общей информации.Таким образом, вы не могу сделать
} catch( Mistake<Account> ea) {
...
} catch( Mistake<User> eu) {
...
}
все вы может сделать является
catch( Mistake ea ) {
...
}
И удаление типа - это то, как было решено сохранить обратную совместимость, когда Java переходила с 1.4 на 1.5.Тогда многие люди были несчастливы, и это было справедливо.Но, принимая во внимание объем развернутого кода, было немыслимо взломать код, который успешно работал в версии 1.4.
Короткий ответ:потому что они срезали путь точно так же, как это было с erasure.
Длинный ответ:как уже указывали другие, из-за удаления нет способа сделать разницу во время выполнения между "catch MyException<String>" и "catch MyException<Integer>".
Но это не означает, что нет необходимости в общих исключениях.Я хочу, чтобы дженерики могли использовать универсальные поля!Они могли бы просто разрешить общие исключения, но разрешать перехватывать их только в необработанном состоянии (например"поймать MyException").
Конечно, это сделало бы дженерики еще более сложными.Это делается для того, чтобы показать, насколько плохим было решение удалить дженерики.Когда у нас будет версия Java, которая поддерживает реальные дженерики (с RTTI), а не текущий синтаксический сахар?
Вы все еще можете использовать общие методы, подобные этому:
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();
}
Я действительно согласен с ответом Кристиана выше.Хотя принятый ответ технически верен (поскольку он ссылается на спецификации JVM), ответ Кристиана Василе уточняет и даже оспаривает ограничение.
Есть по крайней мере два аргумента, которые я отметил в ответах на этот вопрос, с которыми я не согласен и которые я опровергну.Если бы аргументы в этих ответах были правильными, мы могли бы использовать эти аргументы для атаки на дженерики в других контекстах, где они успешно используются сегодня.
Первый аргумент утверждает, что мы не можем использовать это:
catch (Exception<T1> e) {}
потому что JVM не знает, как работать с Exception<T1>
.Этот аргумент, по-видимому, также критикует такое использование дженериков на том основании, что JVM не знает, как использовать List<T1>
:
List<T1> list;
Аргумент, конечно, забывает, что компилятор выполняет удаление типа, и поэтому JVM не нужно знать, как обрабатывать Exception<T1>
.Он может просто справиться Exception
, точно так же , как он обрабатывает List
.
Конечно, мы никогда не смогли бы справиться catch(Exception<T1> e)
и catch(Exception<T2> e)
в том же try / catch из-за удаления типа, но опять же, это не хуже, чем с аргументами метода или возвращаемыми значениями сегодня:мы не справляемся myMethod(List<T1>)
и myMethod(List<T2>)
и сегодня тоже...(Я повторяю этот аспект во втором опровержении ниже.)
Второй аргумент звучит следующим образом.Мы этого не допускаем:
catch (Exception<T1> e) {}
потому что это не сработало бы:
catch (Exception<T1> e) {}
catch (Exception<T2> e) {}
Хорошо, тогда почему бы не запретить это:
interface MyInterface {
Comparable<Integer> getComparable();
}
потому что это не работает:
interface MyInterface {
Comparable<Integer> getComparable();
Comparable<String> getComparable();
}
или это:
interface MyInterface {
void setComparable(Comparable<Integer> comparable);
}
потому что это не работает:
interface MyInterface {
void setComparable(Comparable<Integer> comparable);
void setComparable(Comparable<String> comparable);
}
Другими словами, почему бы не запретить дженерики в большинстве случаев?
Этот второй аргумент забывает, что, хотя мы не могли бы допустить, чтобы в этих контекстах разные общие конструкции, которые стирают одну и ту же не общую конструкцию, мы все равно можем сделать следующую лучшую вещь и разрешать генерики до тех пор, пока типы не будут стерты до одного и того же типа.Это то, что мы делаем с параметрами метода:мы разрешаем вам использовать дженерики, но жалуемся, как только обнаруживаем дубликаты подписей после удаления типа.Ну, мы могли бы сделать примерно то же самое с исключениями и блоками catch...
В заключение я хотел бы подробнее остановиться на ответе Кристиана.Вместо того, чтобы разрешать общие классы исключений и использование необработанных типов в catch
блоки:
class MyException<T> {}
...
catch (MyException e) { // raw
Java могла бы пройти весь путь без проблем:
class MyException<T> {}
...
catch (MyException<Foo> e) {
Вот пара вещей, которые вы может делай:
- Throwables могут реализовывать универсальные интерфейсы, при условии, что сам throwable не имеет параметров типа, например
interface Bouncy<E> {
// ...
}
class BouncyString extends Exception implements Bouncy<String> {
// ...
}
- A
throws
предложение может ссылаться на параметры типа, напримерstatic <X extends Throwable> void
throwIfInstanceOf(Throwable ex, Class<X> clazz) throws X {
if (clazz.isInstance(ex)) throw clazz.cast(ex);
}