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

Ответы [ 5 ]

17 голосов
/ 15 марта 2010

Спецификация языка Java 8.1.2 Общие классы и параметры типа :

Это ограничение необходимо, поскольку механизм перехвата виртуальной машины Java работает только с неуниверсальными классами.

Лично я думаю, что это потому, что мы не можем получить никаких преимуществ от дженериков в предложении catch. Мы не можем написать catch (Bouncy<String> ex) из-за стирания типа, но если мы напишем catch (Bouncy ex), было бы бесполезно делать его общим.

8 голосов
/ 17 января 2012

Краткий ответ: потому что они использовали ярлыки, так же как и при стирании.

Длинный ответ: как уже указывали другие, из-за стирания нет способа сделать разницу во время выполнения между «catch MyException » и «catch MyException ».

Но это не значит, что нет необходимости в общих исключениях . Я хочу, чтобы генерики могли использовать универсальные поля! Они могли бы просто разрешать общие исключения, но разрешать их перехват только в необработанном состоянии (например, «catch MyException»).

Конечно, это сделает дженерики еще более сложными. Это показывает, насколько плохим было решение стереть дженерики. Когда у нас будет версия Java, которая поддерживает настоящие дженерики (с RTTI), а не текущий синтаксический сахар?

8 голосов
/ 15 марта 2010

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

} catch( Mistake<Account> ea) {
  ...
} catch( Mistake<User> eu) {
...
}

все, что вы можете сделать это

catch( Mistake ea ) {
  ...
}

И стирание типов - это то, как было решено сохранить обратную совместимость, когда Java переходила с 1.4 на 1.5. Многие были тогда несчастны, и это справедливо. Но, учитывая количество развернутого кода, было немыслимо сломать код, который работал успешно в 1.4.

4 голосов
/ 16 августа 2012

Вы все еще можете использовать универсальные методы, например:

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 из-за стирания типа, но опять же, это не хуже, чем с аргументами метода или возвращаемыми значениями сегодня: мы не обрабатываем 1024 * и 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:

class MyException<T> {}
...
catch (MyException e) { // raw

Java мог бы пройти весь путь без проблем:

class MyException<T> {}
...
catch (MyException<Foo> e) {
2 голосов
/ 21 мая 2011

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

  1. Throwables может реализовывать универсальные интерфейсы, если только у throwables нет параметров типа, например,

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

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