Хорошая модель? <X extends Exception>... method() выбрасывает X

Некоторые предпосылки, затем некоторые вопросы.

Я только недавно обнаружил, что интерфейс (или класс) может быть общим в типе (проверенном) исключении, которое могут вызвать его методы. Например:

interface GenericRunnable<X extends Exception> {
    void run() throws X;
}

Дело в том, что если позже вы создадите экземпляр с помощью, скажем, IOException и вызовите метод run, компилятор знает, что вам нужно либо поймать IOException, либо пометить его как брошенное. Еще лучше, если X был RuntimeException, вам вообще не нужно его обрабатывать.

Здесь надуманный пример с использованием вышеприведенного интерфейса, но он в основном обратный вызов и должен быть довольно распространенным.

public <X extends Exception> void runTwice(GenericRunnable<X> runnable) throws X {
    runnable.run(); runnable.run();
}
...
public myMethod() throws MyException {
    runTwice(myRunnable);
}

Мы вызываем общий метод утилиты runTwice (возможно, определенный во внешней библиотеке) для запуска нашего собственного метода с определенным проверенным исключением, и мы не теряем информацию о том, какое конкретное исключенное исключение может быть выбрано.

Альтернативой было бы просто использовать throws Exception как для метода Runnable.run, так и для метода runTwice. Это не будет ограничивать реализацию интерфейса Runnable, но преимущество проверенных исключений будет потеряно. Или вообще не могло быть throws, также теряя преимущество проверенных исключений и потенциально заставляя реализацию обернуть.

Потому что я никогда не видел throws X, возможно, я что-то пропустил. Кроме того, я видел пример обратного вызова, который использовался как аргумент против отмеченных исключений несколько раз, без его опровержения. (Этот вопрос не интересует плюсы и минусы проверенных исключений.)

Является ли throws X хорошей идеей? Каковы плюсы и минусы? Можете ли вы привести несколько примеров, которые либо используют throws X, либо не должны, но должны иметь?

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

  • OutputStream throws IOException (возможно, ByteArrayOutputStream может расширять GenericOutputStream<RuntimeException>)

  • Callable/Future.get

  • Commons Pool borrowObject/makeObject

(Начиная с редактирования, я не прошу, чтобы они могли/должны были быть спроектированы по-разному ретроспективно. Скорее, throws X был бы лучше, чем throws Exception.)

Ответ 1

Я использую этот шаблон все время, в основном для java функционального стиля.

Плюсы:

  • У вас может быть несколько ароматов шаблона более высокого порядка, такого как Visitor, чтобы:

    interface ExceptionalVoidVisitor< E extends Exception > {
        void handleA( A a ) throws E;
        void handleB( B b ) throws E;
    }
    interface VoidVisitor extends ExceptionalVoidVisitor< RuntimeException > {}
    interface ExceptionalVisitor< T, E extends Exception > {
        T handleA( A a ) throws E;
        T handleB( B b ) throws E;
    }
    interface Visitor< T > extends ExceptionalVisitor< T, RuntimeException > {}
    
  • Клиент может объявить базовый класс исключений для всех исключений, которые он может бросить, и вы получите полностью общую библиотеку.

Минусы:

  • Как вы обнаружили, нет способа обработать исключение родового типа; вы должны позволить ему сбежать.
  • Другой вариант - принять генератор E, который может быть неудобным для клиентов.