Почему Java не поддерживает универсальные Throwables?

StackOverflow https://stackoverflow.com/questions/2444633

  •  20-09-2019
  •  | 
  •  

Вопрос

class Bouncy<T> extends Throwable {     
}
// Error: the generic class Bouncy<T> may not subclass java.lang.Throwable

Почему Java не поддерживает generic Throwableс?

Я понимаю, что стирание типов усложняет определенные вещи, но, очевидно, Java и так со многим справляется, так почему бы не продвинуть ее еще на одну ступеньку и не разрешить generic Throwables, с всесторонней проверкой во время компиляции на наличие потенциальных проблем?


Я чувствую, что аргумент удаления типа довольно слаб.В настоящее время мы не можем сделать:

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) {

Вот пара вещей, которые вы может делай:

  1. Throwables могут реализовывать универсальные интерфейсы, при условии, что сам throwable не имеет параметров типа, например

    interface Bouncy<E> {
        // ...
    }
    class BouncyString extends Exception implements Bouncy<String> {
        // ...
    }

  2. A throws предложение может ссылаться на параметры типа, например
    static <X extends Throwable> void
    throwIfInstanceOf(Throwable ex, Class<X> clazz) throws X {
        if (clazz.isInstance(ex)) throw clazz.cast(ex);
    }

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top