Назад Зміст Вперед

9.9. Винятки в перевизначених методах

У перевизначеному методі можна порушувати лише ті винятки, які були описані в методі базового класу. Це корисне обмеження означає, що програма, яка працює з базовим класом, автоматично зможе працювати і з об'єктом, що створений від базового (звичайно, це фундаментальний принцип ООП), включаючи і виключення. Наступний приклад демонструє види обмежень (під час компіляції), накладені на виключення:

//: exceptions/StormyInning.java
// Перевизначені методи можуть порушувати тільки
// винятки, описані в версії базового класу,
// або виключення, успадковані від винятків
// базового класу.

class BaseballException extends Exception {}
class Foul extends BaseballException {}
class Strike extends BaseballException {}
 
abstract class Inning {
  public Inning() throws BaseballException {}
  public void event() throws BaseballException {
    // Doesn't actually have to throw anything
  }
  public abstract void atBat() throws Strike, Foul;
  public void walk() {} // Throws no checked exceptions
}
 
class StormException extends Exception {}
class RainedOut extends StormException {}
class PopFoul extends Foul {}
 
interface Storm {
  public void event() throws RainedOut;
  public void rainHard() throws RainedOut;
}
 
public class StormyInning extends Inning implements Storm {
  // OK to add new exceptions for constructors, but you
  // must deal with the base constructor exceptions:
  public StormyInning()
    throws RainedOut, BaseballException {}
  public StormyInning(String s)
    throws Foul, BaseballException {}
  // Regular methods must conform to base class:
//! void walk() throws PopFoul {} //Compile error
  // Interface CANNOT add exceptions to existing
  // methods from the base class:
//! public void event() throws RainedOut {}
  // If the method doesn't already exist in the
  // base class, the exception is OK:
  public void rainHard() throws RainedOut {}
  // You can choose to not throw any exceptions,
  // even if the base version does:
  public void event() {}
  // Overridden methods can throw inherited exceptions:
  public void atBat() throws PopFoul {}
  public static void main(String[] args) {
    try {
      StormyInning si = new StormyInning();
      si.atBat();
    } catch(PopFoul e) {
      System.out.println("Pop foul");
    } catch(RainedOut e) {
      System.out.println("Rained out");
    } catch(BaseballException e) {
      System.out.println("Generic baseball exception");
    }
    // Strike not thrown in derived version.
    try {
      // What happens if you upcast?
      Inning i = new StormyInning();
      i.atBat();
      // You must catch the exceptions from the
      // base-class version of the method:
    } catch(Strike e) {
      System.out.println("Strike");
    } catch(Foul e) {
      System.out.println("Foul");
    } catch(RainedOut e) {
      System.out.println("Rained out");
    } catch(BaseballException e) {
      System.out.println("Generic baseball exception");
    }
  }
}
У класі Inning і конструктор, і метод event() оголошують, що порушуватимуть виключення, але в дійсності цього не роблять. Це допустимо, оскільки подібний підхід змушує користувача перехоплювати всі види винятків, які потім можуть бути додані в перевизначені версії методу event(). Даний принцип поширюється і на абстрактні методи, що і показано для методу atBat(). Інтерфейс Storm цікавий тим, що містить один метод (event ()), вже визначений у класі Inning, і один унікальний. Обидва методи збуджують новий тип виключення RainedOut. Коли клас StormyInning розширює Inning і реалізує інтерфейс Storm, з'ясовується, що метод event() з Storm не здатний змінити тип виключення для методу event () класу Inning.

Знову-таки це цілком розумно, тому що інакше ви б ніколи не знали, перехоплюєте чи потрібне виключення в разі роботи з базовим класом. Звичайно, коли метод, описаний в інтерфейсі, відсутня в базовому класі (як rainHard ()), ніяких проблем з порушенням винятків немає. Метод StormyInning.walk () НЕ компілюється через те, що він збуджує виключення, тоді як Inning.walk () такого не робить.

Якби це дозволялося, ви могли б написати код, що викликає метод Inning.walk () і не перехоплює ніяких винятків, а потім при підстановці об'єкта класу, похідного від Inning, виникли б виключення, що порушують роботу програми. Таким чином, примусово забезпечуючи відповідність специфікацій винятків в похідних і базових версіях методів, Java домагається взаємозамінності об'єктів.

Перевизначення метод event () показує, що метод похідного класу може взагалі не порушувати винятків, навіть якщо це робиться в базовій версії. Знову-таки це нормально, тому що не впливає на вже написаний код - мається на увазі, що метод базового класу збуджує виключення. Аналогічна логіка застосовна для методу atBat (), що порушує виняток PopFoul, похідне від Foul, яке порушується базовою версією atBat (). Отже, якщо ви пишете код, який працює з Inning і викликає atBat (), то він повинен перехоплювати виняток Foul. Так як PopFoul успадковує від Foul, обробник виключення для Foul перехопить і PopFoul.

Останній цікавий момент зустрічається в методі main (). Ми бачимо, що при роботі саме з об'єктом StormyInning компілятор змушує перехоплювати тільки ті винятки, які характерні для цього класу, але при висхідному перетворенні до базового типу компілятор змушує перехоплювати виключення з базового класу. Всі ці обмеження значно підвищують ясність і надійність коду обробки винятків. Хоча компілятор змушує описувати виключення при спадкуванні, специфікація винятків не є частиною оголошення (сигнатури) методу, яке складається тільки з імені методу і типів аргументів. Відповідно, не можна перевизначати методи тільки за специфікаціями винятків.

До того ж, навіть якщо специфікація виключення присутній в методі базового класу, це зовсім не гарантує його існування в методі похідного класу. Дана практика сильно відрізняється від правил спадкування, за якими метод базового класу обов'язково присутній і в похідному класі. Іншими словами, «інтерфейс специфікації винятків» для певного методу може звузитися в процесі успадкування і перевизначення, але ніяк не розширитися - і це пряма протилежність інтерфейсу класу під час успадкування.

.