Почему Java не поддерживает общие Throwables?
class Bouncy<T> extends Throwable {
}
// Error: the generic class Bouncy<T> may not subclass java.lang.Throwable
Почему Java не поддерживает общий Throwable
s?
Я понимаю, что стирание типа усложняет некоторые вещи, но, очевидно, Java уже много работает, поэтому почему бы не надавить на него еще одну метку и разрешить общий Throwable
s, с полной проверкой времени компиляции для потенциальных проблем?
Мне кажется, что аргумент стирания типа довольно слабый. В настоящее время мы не можем:
void process(List<String> list) {
}
void process(List<Integer> list) {
}
Конечно, мы обойдемся без него. Я не прошу, чтобы мы могли делать catch Bouncy<T1>
и Bouncy<T2>
в том же блоке try
, но если мы будем использовать их в непересекающихся контекстах со строгими правилами принудительного исполнения во время компиляции (что в значительной степени является способом дженериков работает прямо сейчас), не будет ли это выполнимо?
Ответы
Ответ 1
Спецификация языка Java
8.1.2 Общие классы и параметры типа:
Это ограничение необходимо, так как механизм catch виртуальной машины Java работает только с не-генерическими классами.
Лично я думаю, потому что мы не можем получить какие-либо преимущества дженериков внутри предложения catch
. Мы не можем написать catch (Bouncy<String> ex)
из-за стирания типа, но если мы напишем catch (Bouncy ex)
, было бы бесполезно сделать его общим.
Ответ 2
Стирание типа. Тип исключения Runtime не имеет информации о дженериках. Таким образом, вы не можете
} catch( Mistake<Account> ea) {
...
} catch( Mistake<User> eu) {
...
}
все, что вы можете сделать,
catch( Mistake ea ) {
...
}
И стирание типа - это то, как было решено сохранить обратную совместимость, когда Java двигалась с 1.4 до 1.5. Тогда многие люди были недовольны, и это справедливо. Но имея в виду количество развернутого кода, было немыслимо сломать код, который с радостью работал в версии 1.4.
Ответ 3
Короткий ответ: потому что они использовали ярлыки, как и при стирании.
Длинный ответ: как уже указывали другие, из-за стирания нет никакого способа сделать разницу во время выполнения между "catch MyException <String> " и "catch MyException <Integer> ".
Но это не означает, что нет необходимости в общих исключениях. Я хочу, чтобы дженерики могли использовать общие поля! Они могли бы просто разрешить общие исключения, но разрешить их только в исходном состоянии (например, "catch MyException" ).
Конечно, это сделало бы дженерики еще более сложными.
Это должно показать, насколько плохо было решение об уничтожении дженериков. Когда у нас будет версия Java, поддерживающая реальные генерики (с RTTI), а не текущий синтаксический сахар?
Ответ 4
Вы все равно можете использовать общие методы, например:
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);
}
Другими словами, почему бы не запретить генерики в большинстве случаев?
Этот второй аргумент забывает, что, хотя мы не могли позволить другим родовым конструкциям, которые стирают одну и ту же не-общую конструкцию в этих контекстах, мы все еще можем сделать следующее лучшее и разрешить дженерики до тех пор, пока типы don 't стереть один и тот же тип. Это то, что мы делаем с параметрами метода: мы позволяем вам использовать дженерики, но жаловаться, как только обнаруживаем повторяющиеся подписи после стирания типа. Ну, мы могли бы сделать примерно то же самое с исключениями и блоками catch...
В заключение я хотел бы расширить ответ Кристиана. Вместо того, чтобы разрешать общие классы исключений и использовать необработанные типы в блоках catch
:
class MyException<T> {}
...
catch (MyException e) { // raw
Java могла бы пройти весь путь без проблем:
class MyException<T> {}
...
catch (MyException<Foo> e) {
Ответ 5
Вот несколько вещей, которые вы можете сделать:
- 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);
}