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

Почему 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, но если мы будем использовать их в непересекающихся контекстах со строгими правилами принудительного исполнения во время компиляции (что в значительной степени является способом дженериков работает прямо сейчас), не будет ли это выполнимо?

4b9b3361

Ответ 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);
    }