Когато подтискате метод може да изхвърляте само изключенията, зададени за метода от базовия клас. Това е полезно ограничение, понеже води до това, че код който работи с базовия клас ще работи също така и с който и да е извлечен клас (основна концепция в ООП, разбира се), включително изключенията.
Този пример демонстрира видовете наложени ограничения (по време на компилация) за изключенията:
//: c09:StormyInning.java
// Overridden methods may throw only the
// exceptions specified in their base-class
// versions, or exceptions derived from the
// base-class exceptions.
class BaseballException extends Exception {}
class Foul extends BaseballException {}
class Strike extends BaseballException {}
abstract class Inning {
Inning() throws BaseballException {}
void event () throws BaseballException {
// Doesn't actually have to throw anything
}
abstract void atBat() throws Strike, Foul;
void walk() {} // Throws nothing
}
class StormException extends Exception {}
class RainedOut extends StormException {}
class PopFoul extends Foul {}
interface Storm {
void event() throws RainedOut;
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:
StormyInning() throws RainedOut,
BaseballException {}
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 base version does:
public void event() {}
// Overridden methods can throw
// inherited exceptions:
void atBat() throws PopFoul {}
public static void main(String[] args) {
try {
StormyInning si = new StormyInning();
si.atBat();
} catch(PopFoul e) {
} catch(RainedOut e) {
} catch(BaseballException e) {}
// 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) {
} catch(Foul e) {
} catch(RainedOut e) {
} catch(BaseballException e) {}
}
} ///:~
В Inning може да видите че както конструкторът така и методът event( ) казват че ще изхвърлят изключение но не го правят. Това е законно понеже кара потребителя да прихване всяко изключение което би могло да се появи в подтиснатите версии на event( ). Същата идея важи и за abstract методи, както се вижда в atBat( ).
interface Storm е интересен с това, че съдържа един метод (event( )) дефиниран в Inning и един метод който не е. И двата метода изхвърлят нов тип изключение, RainedOut. Когато StormyInning extends Inning и implements Storm, ще видите че методът event( ) в Storm не може да смени интерфейса на изключенията event( ) в Inning. Отново това има смисъл понеже иначе никога няма да е сигурно дали имате провилното нещо когато хващате изключение от базовия клас. Разбира се ако метод описан в interface не е в базовия клас, както rainHard( ), няма проблеми ако той изхвърля изключения.
Ограничението на изключенията не се прилага за конструкторите. В StormyInning може да видите, че конструкторът може да изхвърли всичко каквото иска, без значение какво изхвърля конструкторът на базовия клас. Обаче тъй като базовият конструктор се вика винаги по един или друг начин (тука конструктор по подразбиране се вика автоматично), конструкторът на извлечения клас трябва да декларира всички изключения на конструктора на базовия клас в своята спецификация на изключения.
Причината да не се компилира StormyInning.walk( ) е че изхвърля изключение, докато Inning.walk( ) не. Ако това беше допустимо, можеше да се напише код който вика Inning.walk( ) и той нямаше да е нужно да обработи изключения, но когато замените обект от клас низвлечен от Inning, биха били изхвърляни изключения и кодът щеше да се скапе. Чрез заставянето на методите на извлечения клас да отговарят на спецификацията на изключенията на базовия клас се обслужва взаимозаменяемостта на методите.
Подтиснатия event( ) метод показва, че версията на метод в извлечен клас може да избере въобще да не изхвърля изключения, макар и версията на базовия клас да го прави. Отново това е необходимото за да работи с всякакъв наследен код. Подобна логика важи за atBat( ), който изхвърля PopFoul, изключение извлечено от Foul изхвърляно от базовата версия на atBat( ). По този начин ако някой напише кад кайто работи с Inning и вика atBat( ), те трябва да хванат Foul изключението. Понеже PopFoul е извлечено от Foul, обработчикът ще хване и PopFoul.
Последната интересна точка е в main( ). Тук се вижда че ако се разправяте точно с StormyInning обект, компилаторът ви заставя да хващате изключенията специфични точно за него, но ако направите ъпкаст към базовия клас компилаторът (коректно) ви заставя да хващате изключенията на базовия тип. Всички тези ограничения довеждат до много по-добър код за обработка на изключения.3
Полезно е да се разбере, че макар и ограниченията при изключенията да се налагат от компилатора чрез наследяване, спецификациите на ограничения не са част от даден тип, който е съставен само от името и типовете на аргументите. Затова не може да се пренатоварват методи чрез спецификацията на изключения. Освен това ако има спецификация на изключенията в базовия метод това не значи че непременно тя съществува и в извлечения, а това е доста различно от наследяване на методи (тоест, метод в базовия клас трябва също да съществува и в извлечения клас). С други думи казано, “интерфейсът на спецификация на изключенията” за конкретен метод може да се стеснява в процеса на наследяване и подтискане, но не може да се разширява – това е точно наопаки на интерфейса на базовия и извлечения клас при наследяване.
Сподели с приятели: |