Обзор ограничений, накладываемых на исключения, на примере наследования и реализации интерфейса

 

Как известно исключения в Java, при наследовании подчиняются требованию сужения спецификации, а не ее расширения, в отличие от обычных классов.
В книге Bruce Eckel «Thinking in Java, 4 ed.» приведено описание ограничений, накладываемых при использовании исключений. Пример кода описывает наследование от базового класса и одновременную реализацию интерфейса, причем у обоих имеются методы с одинаковой сингатурой, но разной спецификацией исключений. Попытки реализации методов интерфейса пересекаются с попытками одновременного переопределения методов базового класса, в результате которых делается упор на недопущение расширения спецификации исключений. Упрощенный пример кода, доносящий идею:

 

class BaseBallException extends Exception{}
class Foul extends BaseBallException{}
class Strike extends BaseBallException{}

abstract class Inning {
    //-----Methods----
    public Inning() throws BaseBallException{}
    public void event() throws BaseBallException{}
        
    public abstract void atBat() throws Strike, Foul;
}

class StormException extends Exception{}
class RainedOut extends StormException{}
class PopFoul extends Foul{}

interface Storm {
    public void event() throws RainedOut;
}

class StormyInning extends Inning implements Storm {
    //--------Methods-----
    public StormyInning() throws RainedOut, BaseBallException{}
    public StormyInning(String str) throws Foul, BaseBallException{}

    /**ошибка компиляции - реализация метода Storm.event() throws RainedOut пытается изменить спецификацию 
    * исключений метода Inning.event() throws BaseBallException на новое исключение RainedOut*/
    //! public void event() throws RainedOut{}

    /**требуется переопределение метода event() с пустой спецификацией исключений - это безболезненно в 
    * силу сужения спецификации и необходимо для реализации интерфейса. 
    * В данном случае - единственный вариант*/
    public void event() {}
    
    public void atBat() throws PopFoul {}	
}



Пример можно дополнить другими вариантами спецификаций исключений, перебрав некоторые интересные сочетания:

class BaseBallException extends Exception{}
class Foul extends BaseBallException{}
class Strike extends BaseBallException{}
class LightStrike extends Strike{}
class TwistedLightStrike extends LightStrike{}

abstract class Inning {
    //-----Methods----
    public Inning() throws BaseBallException{}
    public void event() throws BaseBallException{}
    public void event1() throws BaseBallException{}
    public void event2() {}
    public void event3() throws RainedOut{}
    public void event4() throws Strike{}
    public void event5() throws LightStrike{}
}

class StormException extends Exception{}
class RainedOut extends StormException{}
class Drizzle extends RainedOut{}
class PopFoul extends Foul{}

interface Storm {
    public void event() throws RainedOut;
    public void event1();
    public void event2() throws RainedOut;
    public void event3() throws RainedOut;
    public void event4() throws LightStrike;
    public void event5() throws Strike;
}

class StormyInning extends Inning implements Storm {
    //--------Methods-----
    public StormyInning() throws RainedOut, BaseBallException{}
    public StormyInning(String str) throws Foul, BaseBallException{}

    /**ошибка компиляции - реализация метода Storm.event() throws RainedOut пытается изменить спецификацию 
    * исключений метода Inning.event() throws BaseBallException на новое исключение RainedOut*/
    //! public void event() throws RainedOut{}

    /**ошибка компиляции - реализация метода Storm.event() throws RainedOut c заменой исключния RainedOut на
    * BaseBallException является попыткой изменить спецификацию исключений этого метода*/
    //! public void event() throws BaseBallException{}
    
    /**ошибка компиляции - смешаный вариант не проходит по причине обоюдных попыток расширить 
    * спецификацию исключений*/
    //! public void event() throws RainedOut, BaseBallException{}	

    /**Единственный и необходимый вариант*/
    public void event() {}	
    
    /**ошибка компиляции - метод интерфейса Storm.event1() не допускает расширения спецификации*/
    //! public void event1() throws BaseBallException {}

    /**Единственный и необходимый вариант - интерфейс требует реализации метода Storm.event1() и 
    * только с чистой спецификацией*/
    public void event1(){}
    

    /**ошибка компиляции - метод абстрактного базового класса Inning.event() сопротивляется расширению
    * спецификации*/
    //! public void event2() throws RainedOut{}

    /**Единственный однако не необходимый вариант реализации интерфейсного метода. 
    * Реализация может отсутствовать вовсе т.к. абстрактный базовый класс реализует интерфейсный метод
    * Storm.event2() throws RainedOut с сужением спецификации исключений*/
    //public void event2(){}
    
    
    /**Реализация метода Storm.event3() throws RainedOut может отсутствовать вовсе - абстрактный базовый 
    * класс реализует интерфейсный метод Storm.event3() throws RainedOut, спецификации исключений 
    * совпадают. Но можно и реализовать метод с исходной спецификацией исключений*/ 
    //public void event3() throws RainedOut{}

    /**также можно в соответствии с иерархией исключений реализовывать методы с последовательным 
    * сужением спецификации  исключений вплоть до чистой*/
    //public void event3() throws Drizzle{}
    //public void event3() {}


    /**Теперь ловим плечо, на котором может быть реализован метод event4()*/

    /**Реализация метода event4() требуется т.к. абстрактный базовый класс расширяет, а не сужает 
    * спецификацию исключений*/

    /**ошибка компиляции - попытка расширить спецификацию исключений метода интерфейса Storm.event4()*/
    //! public void event4() throws Strike{}
    
    /**Работает - спецификация метода абстрактного класса сузилась, интерфейса - соответствует*/
    //public void event4() throws LightStrike{}

    /**Работает - здесь и далее обе спецификации исключений сузились*/
    //public void event4() throws TwistedLightStrike{}
    public void event4() {}

    /**Идея как и для event4(), но на этот раз реализации не требуется - абстрактный класс сужает спецификацию 
    * и реализует интерфейсный метод*/
    //! public void event5() throws Strike{}	
    //public void event5() throws LightStrike{}
    //public void event5() throws TwistedLightStrike{}	
    //public void event5()
}

Очевидно, приведенные выше ограничения и фичи будет не лишним учитывать при проектировании иерархии классов для очередного проекта.

habrahabr.ru/post/178477/

Вверх