Книга е още в много ранна фаза на написване



страница52/73
Дата25.07.2016
Размер13.53 Mb.
#6732
1   ...   48   49   50   51   52   53   54   55   ...   73

Основни изключения


Изключително състояние е проблем, който не дава да продължи изпълнението на текущия метод или обхват. Важное да се различи изключителното състояние от обикновения проблем, в който има достатъчно информация в текущия кон­текст, даваща възможност нещо да се направи по въпроса. В изключителното съ­стояние не може нищо да се направи поради липсата на информация в текущия контекст. Всичко което може да се направи е да се изскочи от те­ку­щия контекст и да се разгледа проблема в по-висок контекст. Това е, което се случ­ва като се изхвърли изключение.

Прост пример е делението. Ако може да се опитате да делите на нула, може и да си струва да проверявате и да не продължите с делението. Но какво следва ако делителят е нула? Може да знаете, в контекста на конкретния метод, какво да се прави в такъв случай. Но може и да не знаете, ако такава стойност не се очак­ва и тогава трябва да изхвърлите изключение, а не да правите проверки.

Като изхвърлите изключение се случват няколко неща. Първо се създава обект на изключението по начин по който се създава всеки обект в Java: на хийпа, с new. После текущия път на изпълнение (този, който не може да продължи, запомнете) се спира и манипулатор към обекта на изключението се изтласква от те­кущия контекст. В този момент механизма за обслужване на изключенията вли­за в действие и започва да търси подходящо място където програмата да про­дължи. Това подходящо място е exception handler-а, чиято задача е да се спра­ви с проблема така, че програмата да може да вземе друг курс или просто да продължи.

Като прост пример на изхвърляне на изключение да вземем обектов ма­ни­пу­ла­тор наречен t. Възможно е да се даде манипулатор, който не е бил инициа­ли­зи­ран, така че може да пожелаете да правите проверка преди подаването на въ­прос­ния манипулатор като аргумент. Може да пратите информация за грешката в по-широкия контекст чрез създаване на обект представящ нужната инфор­ма­ция и “изхвърлянето му” извън текущия контекст. Това се казва изхвърляне на изклю­чение. Ето го как изглежда:

if(t == null)
throw new NullPointerException();

Това изхвърля изключение, като ви позволява – в текущия контекст – да аб­ди­ки­рате от отговорността да мислите повече за проблема. Той се оправя някакси ма­гически някъде другаде. Скоро ще се покаже точно къде.


Аргументи на изключението


Вакто всеки обект в Java изключенията винаги се създават в хийпа с new и се вика конструктор. Има два конструктора за всички стандартни изключения; пър­вият е по подразбиране, а вторият приема стринг за аргумент така че може да включите уместна информация в изключението:

if(t == null)


throw new NullPointerException("t = null");

Този стринг после може да бъде извлечен чрез различни методи както ще се по­ка­же по-късно.

Ключовата дума throw причинява случването на относително магически неща. Пъ­рво се изпълнява new-израз за да се създаде обект, който не е там при нор­мал­ното изпълнение на програмата и, разбира се, конструктор се вика за обек­та. После обекта, фактически, се “връща” от метода, макар и типът му да не е като този, закойто е проектиран метода. Опростен начин да се мисли за механизма на изключенията е да се смятат за друг начин на връщане от метод, макар и да има трудности ако се прокара тази аналогия твърде далеч. Може също да се излезе от най-вътрешния обхват чрез изхвърляне на изключение. Връща се стойност, а методът или обхватът завършват изпълнението си.

Всяка прилика с обикновеното завършване на метод свършва тук, понеже мя­сто­то където се връща изпълнението на програмата е напълно различнот от то­ва при нормално завършване на метода. (Завършвате с подходящия обработчик на изключения който може да бъде на километри далеч – много нива позниско в стека на извикванията – от мястото където изкючението е изхвърлено.)

В добавка може да изхвърляте който тип от Throwable обект си искате. Ти­пич­но ще изхвърляте различен тип изключение за различни типове грешки. Идеята е да се съхрани информация в обекта на изключението и в типа на избрания обект, така че някой в по-широкия контекст да може да разбере какво да прави с вашето изключение. (Често единствената информация е типа на обекта на из­клю­чението, а нищо значително не се съхранява в обекта.)

Хващане на изключение


Ако метод изхвъърли изключение той трябва да предполага, че то ще бъде хва­на­­то и обработено. Едно от предимствата на обработката на изключения в Java е, че позволява да се съсредоточите върху решаването на даден проблем на ед­но място, а после да се оправяте с грешките от този код на друго място.

За да видим как се хваща изключениетрябва първо да усвоим концепцията за guarded region, което е секция от код, която може да изхвърля изключения и е след­вана от код, който обработва тези изключения.


Блокът try


Ако сте вътре в метод и изхвърлите изключение (или друг метод извикан из­въ­тре на този изхвърли изключение), методъът ще завърши като част от процеса на изхвърлянето. Ако не искате throw да напусне метод, може да напишете спе­циа­лен блок в метода който да хване изключението. Това го казват try block по­не­же “опитвате” различните извиквания на методи вътре. Трай блокът е обик­но­вен обхват предшестван от ключовата дума try:

try {


// Code that might generate exceptions

}

Ако трябваше грижливо да пишете код за обработка на грешки на програмен език, който не поддържа изключения, щеше да е необходимо да окръжавате вся­ко извикване на метод с код за откриване и обработка на грешки, даже ако из­виквате един и същ метод няколко пъти. С изключенията слагате всичко в един блок и обработвате всички грешки в него. Това значи че основната про­гра­ма е много по-лесна за написване и четене, понеже кодът не се преплита с про­верките за грешки.


Обработчици на изключения


Разбира се, изхвърленото изключения трябва да завърши някъде. Това “място” е exception handler-а, а има по един за всеки тип изключение което искате да хва­нете. Обработчиците на изключения непосредствено следват трай блока и са оз­начени с ключовата дума catch:

try {


// Code that might generate exceptions

} catch(Type1 id1) {

// Handle exceptions of Type1

} catch(Type2 id2) {

// Handle exceptions of Type2

} catch(Type3 id3) {

// Handle exceptions of Type3

}
// etc...

Всяка клауза за хващане (exception handler) прилича на малък метод който взе­ма един и само един аргумент от определен тип. Идентификаторът (id1, id2, и т.н.) може да бъде използван вътре в хендлъра, точно като аргумент на метод. По­някога не употребявате идентификатора, понеже типът дава дотатъчно инфор­мация, но идентификаторът трябва да си бъде там.

Обработчиците трябва да се появят непосредствено до трай блока. Ако е из­хвър­лено изключениемеханизмът за изключенията тръгва на лов за първия хенд­лър който е за дадения тип. После влиза в клаузата за хващане, а из­клю­че­ние­то се счита обработено. (Търсенето спира щом се влезе в catch клаузата.) Из­пъл­нява се само съвпадащата клауза; не е както при switch оператора където тряб­ва break след всеки case за да не се изпълнят и следващите.

Забележете че, с try блока, различни методи могат да генерират същото из­клю­че­ние, но е необходим само един обработчик.

Прекратяване vs. продължаване


Има два основни модела в теорията на обработката на изключения. В пре­кра­тя­ването (което се поддържа в Java и C++) се предполага, че грешката е тол­ко­ва критична, че не може нищо да се направи на мястото на възникване. Който е из­хвърлил изключението е преценил, че не може да се спаси положението и не ис­ка да се връща.

Алтернативата се нарича продължаване. Това значи че обработчикът на из­клю­че­ния се оставя да свърши нещо за поправяне на ситуацията, а после методът в кой­то е възникнало изключението се повтаря, с предполагаем успех този път. Ако предпочитате продължаване, значи се надявате да може да продължите след обработката на изключението. В този случай вашето изключение е по-по­доб­но на извикване на метод – което и ще направите, за да имитирате тази идео­логия в Java в случаите когато искате такова поведение. (Тоест, не из­хвър­ляй­те изключение; извикайте метод, който да оправи нещата.) Алтернативно, сло­жете вашия try вътре в while цикъл който продължава да влиза пак в try бло­ка докато резултатът стане удовлетворителен.

Исторически програмистите са използвали нещо подобно на продължаване от опе­рационната система, което в края на краищата е завършвало с прекратяване чрез пропускане на продължаващия код. Така че и да изглежда по-при­вле­ка­тел­но на пръв поглед, продължаването изглежда че не е чак толкова полезно на прак­тика. Главната причине е вероятно свързването което се получава: вашият хенд­лър често трябва да знае каде е възникнало изключението и да съдържа не-ро­дов код за конкретното място. Това прави кода труден за писане и под­дър­жа­не, особено за големи системи, където изключението може да възникне в раз­лич­ни точки.

Специфициране на изключението


В Java се изисква да информирате клиент-програмиста, който вика вашия ме­тод, за изключенията които биха могли да бъдат изхвърлени от този метод. То­ва е цивилизовано, понеже потребителят може да узнае какъв точно код да на­пи­ше за прихващането на всичките възможни изключения. Разбира се, ако рез­по­лага със сорса, въпросният програмист може да го разгледа и да намери къде има throw оператори, но често библиотеките не изват със сорс. За да се избегне про­блемът, Java дава синтаксис (и изисква да го използвате) за да може да ка­же­те учтиво на клиента какви изключения изхвърля вашия метод, така че да мо­же да бъдат обработени. Това е спецификация на изключенията и е част от де­кларацията на метода, появяваща се след списъка аргументи.

Спецификацията на изключението използва допълнителна ключова дума, throws, следвана от типовете на потенциалните изключения. Тоест де­фи­ни­ция­та на метода би могла да изглежда така:

void f() throws tooBig, tooSmall, divZero { //...

Ако напишем

void f() { // ...

това значи че не се изхвърлят изключения от метода. (Освен от тип RuntimeException, който може да бъде изхвърлен навсякъде – това ще се опи­ше по-нататък.)

Не може да се лъже със спецификацията на изключенията – ако вашият метод пред­извиква изключения и не се справя с тях, компилаторът ще открие това и ще ви застави или да поддържате изключението, или да го опишете (споменете) в спецификацията. Чрез налагане на спецификация на изключенията отгоре до до­лу Java че ще има коректност в изключенията по време на изпълнение.2

Има едно място където може да лъжете: може да твърдите че изхвърляте из­клю­чение, а да не го правите. Компилаторът си взема наум вашето и заставя по­тре­бителите на метода да поддържат въпросното изключение. Това има по­лез­ния ефек да означава изключението само, така че да може да започнете да из­хвър­ляте изключението по-късно, без промяна на съществуващия код.


Хващане на кое да е изключение


Възможно е да се създаде обработчик, който хваща всякакъв тип изключение. То­ва се прави чрез хващане на базовия тип Exception (има и други видове ба­зо­ви изключения, но Exception е базата която е уместна практически за всички про­грамни активности):

catch(Exception e) {

System.out.println("caught an exception");

}

Това ще хване всякакво изключение, така че ако ще го използвате ще го сло­жи­те накрая на вашия списък от обработчици, за да се избегне отнемането на из­клю­ченията на обработчиците, които иначе биха се намирали след него.



Тъй като класът Exception е базов за всичките изключения които са важни за про­грамиста, не получавате много информация за изключението, но може да ви­кате методите които идват от неговия базов тип Throwable:

String getMessage( )
Взема детайлно съобщение.

String toString( )
Връща кратко описание на Throwable, включително детайлно съобщение ако има такова.

void printStackTrace( )
void printStackTrace(PrintStream)
Извежда Throwable и трасирания стек на извикванията на Throwable. Стекът на из­викванията показва веригата от викания на методи, която е довела до мя­сто­то, където е изхвърлено изключението.

Първата версия извежда на стандартния изход за грешки, втората на поток по ваш избор. Ако работите под Windows, не можете да пренасочите стандартната греш­ка и затова може да искате да използвате друг поток и накрая System.out; по този начин изходът може да бъде пренасочен по какъвто вие искате начин.

В добавка получавате някои други методи от базовия тип на Throwable Object (базовия тип на всичко). Метод който може да е полезен при из­клю­че­ния­та е getClass( ), който връща обект, представящ класа на този обект. Може то­гава да питате този Class обект за името му чрез getName( ) или toString( ). Мо­же да правите също по-сложни неща с Class обектите които неща не са необ­ходими при обработката на изключения. Class обектите ще се изучават по-къс­но в тази книга.

Ето пример който показва използването на методите на Exception: (Виж стра­ни­ца 89 при проблеми с пускането на програмата.)

//: c09:ExceptionMethods.java

// Demonstrating the Exception Methods

package c09;
public class ExceptionMethods {

public static void main(String[] args) {

try {

throw new Exception("Here's my Exception");



} catch(Exception e) {

System.out.println("Caught Exception");

System.out.println(

"e.getMessage(): " + e.getMessage());

System.out.println(

"e.toString(): " + e.toString());

System.out.println("e.printStackTrace():");

e.printStackTrace();

}

}

} ///:~



Изведеното е:

Caught Exception

e.getMessage(): Here's my Exception

e.toString(): java.lang.Exception: Here's my Exception

e.printStackTrace():

java.lang.Exception: Here's my Exception

at ExceptionMethods.main

Вижда се, че методите измеждат все повече информация с наследяването – все­ки ефективно е надмножество на предишния.


Преизхвърляне на изключение


Понякога ще искате да изхвърлите повторно изключението, което току-що сте хва­нали, в частност когато сте използвали Exception за хващане на което и да е из­ключение. Понеже вече имате манипулатор към текущото изключение, може про­сто да изхвърлите този манипулатор:

catch(Exception e) {

System.out.println("An exception was thrown");

throw e;


}

Преизхвърлянето на изклчението довежда изключението до обработчиците от след­ващия по-голям контекст. Всякакъв по-нататъшен catch блок за същия try блок се игнорира. Освен това всичко за обекта на изключението се съхранява, та­ка че обработчикът от по-високия контекст разполага с всичката информация за изключението.

Ако просто преизхвърлите текущото изключение, информацията която бихте из­вели за него в printStackTrace( ) ще отразява оригиналното възникване на из­клю­чение, а не мястото, където го преизхвърляте. Ако искате да инсталирате но­ва трасираща информация за стека, може да го направите чрез извикване на fillInStackTrace( ), което връща обект-изключение чрез наслагването на те­ку­ща­та информация за стека към стария обект. Ето как изглежда:

//: c09:Rethrowing.java

// Demonstrating fillInStackTrace()
public class Rethrowing {

public static void f() throws Exception {

System.out.println(

"originating the exception in f()");

throw new Exception("thrown from f()");

}

public static void g() throws Throwable {



try {

f();


} catch(Exception e) {

System.out.println(

"Inside g(), e.printStackTrace()");

e.printStackTrace();

throw e; // 17

// throw e.fillInStackTrace(); // 18

}

}

public static void



main(String[] args) throws Throwable {

try {


g();

} catch(Exception e) {

System.out.println(

"Caught in main, e.printStackTrace()");

e.printStackTrace();

}

}



} ///:~

Важните номера на редове са дадени в коментар. С ред 17 некоментиран (както е показано), изходът е:

originating the exception in f()

Inside g(), e.printStackTrace()

java.lang.Exception: thrown from f()

at Rethrowing.f(Rethrowing.java:8)

at Rethrowing.g(Rethrowing.java:12)

at Rethrowing.main(Rethrowing.java:24)

Caught in main, e.printStackTrace()

java.lang.Exception: thrown from f()

at Rethrowing.f(Rethrowing.java:8)

at Rethrowing.g(Rethrowing.java:12)

at Rethrowing.main(Rethrowing.java:24)

Така че трасирането на стека на изключението винаги помни своето място на по­я­вяване, без значение колко пъти е преизхвърляно.

С ред 17 коментиран и ред 18 некоментиран се използва този път fillInStackTrace( ) и резултатът е:

originating the exception in f()

Inside g(), e.printStackTrace()

java.lang.Exception: thrown from f()

at Rethrowing.f(Rethrowing.java:8)

at Rethrowing.g(Rethrowing.java:12)

at Rethrowing.main(Rethrowing.java:24)

Caught in main, e.printStackTrace()

java.lang.Exception: thrown from f()

at Rethrowing.g(Rethrowing.java:18)

at Rethrowing.main(Rethrowing.java:24)

Поради fillInStackTrace( ) ред 18 става нова начална точка на изключението.

Класът Throwable трябва да се появи в спецификацията на изключенията на g( ) и main( ) понеже fillInStackTrace( ) произвежда манипулатор към Throwable обект. Понеже Throwable е базов клас на Exception, възможно е да се получи обект който е Throwable но не Exception, така че манипулаторът Exception в main( ) би могъл да го пропусне. За да осигури че всичко е наред компилаторът при­нуждава към спецификация на изключение Throwable. Например из­клю­че­ние­то в следната програма не се хваща в main( ):

//: c09:ThrowOut.java

public class ThrowOut {

public static void

main(String[] args) throws Throwable {

try {


throw new Throwable();

} catch(Exception e) {

System.out.println("Caught in main()");

}

}



} ///:~

Възможно е също да се преизхвърли различно от хванатото съобщение. Ако направите това получавате подобен ефект като с използването на fillInStackTrace( ): информацията за оригиналното положение на изхвърлянето е загубена, а имате информацията свързана с новия throw:

//: c09:RethrowNew.java

// Rethrow a different object from the one that

// was caught
public class RethrowNew {

public static void f() throws Exception {

System.out.println(

"originating the exception in f()");

throw new Exception("thrown from f()");

}

public static void main(String[] args) {



try {

f();


} catch(Exception e) {

System.out.println(

"Caught in main, e.printStackTrace()");

e.printStackTrace();

throw new NullPointerException("from main");

}

}



} ///:~

Изходът е:

originating the exception in f()

Caught in main, e.printStackTrace()

java.lang.Exception: thrown from f()

at RethrowNew.f(RethrowNew.java:8)

at RethrowNew.main(RethrowNew.java:13)

java.lang.NullPointerException: from main

at RethrowNew.main(RethrowNew.java:18)

Последното изключение знае само че е възникнало в main( ), а не от f( ). За­бе­ле­же­те че Throwable не е непременно в някоя спецификация на изключе­ния.

Никога не се грижим за почистване на предишното изключение, или за каквото и да е изключение. Те са обекти в хийпа създадени с new, така че боклучарят ав­то­матично ги почиства всичките.




Сподели с приятели:
1   ...   48   49   50   51   52   53   54   55   ...   73




©obuch.info 2024
отнасят до администрацията

    Начална страница