Когато се пише код за изключения особено важно е да се питаме “ако се случи изключение, това ще бъде ли правилно почистено?” Повечето време е съвсем безопасно, но с конструкторите има проблем. Конструкторът извежда обекта до безопасно стартово състояние, но би могъл да изпълнява и някаква операция – като отваряне на файл – която да не бъде почистена (откъм въздействията се - б.пр.) докато потребителят не изпълни специално написан за това код. Ако изхвърлите изключение извътре на конструктор този код може и да не се изпълни, почистването да не стане правилно. Това значи че трябва да бъдете особено прилежни когато пишете своя конструктор.
Понеже току-що научихте за finally, може да помслите, че това е коректното решение. Не е така просто, обаче, понеже finally изпълнява кода всякога, даже и в ситуации, когато не искате да се изпълнява почистване докато не се пусне почистващия метод. Така, ако почиствате във finally, трябва да сложите някакъв флаг (семафор) ако конструкторът завърши нормално и само тогава да почиствате (ако е сложен). Тъй като това не е много елегантно (свързвате едно място на кода си с друго), най-добре е да избягвате да правите този вид почистване във finally докато не ви се наложи.
В следващия пример се създава клас наречен InputFile, той отваря файл и дава възможност да се прочете един ред от него (превъърнат в String) наведнъж. Използвани са класовете FileReader и BufferedReader от стандартната входно-изходна библиотека на Java която ще се разгледа в глава 10, но които са достатъчно прости така че няма да имате проблеми с разбирането на основната им употреба:
//: c09:Cleanup.java
// Paying attention to exceptions
// in constructors
import java.io.*;
class InputFile {
private BufferedReader in;
InputFile(String fname) throws Exception {
try {
in =
new BufferedReader(
new FileReader(fname));
// Other code that might throw exceptions
} catch(FileNotFoundException e) {
System.out.println(
"Could not open " + fname);
// Wasn't open, so don't close it
throw e;
} catch(Exception e) {
// All other exceptions must close it
try {
in.close();
} catch(IOException e2) {
System.out.println(
"in.close() unsuccessful");
}
throw e;
} finally {
// Don't close it here!!!
}
}
String getLine() {
String s;
try {
s = in.readLine();
} catch(IOException e) {
System.out.println(
"readLine() unsuccessful");
s = "failed";
}
return s;
}
void cleanup() {
try {
in.close();
} catch(IOException e2) {
System.out.println(
"in.close() unsuccessful");
}
}
}
public class Cleanup {
public static void main(String[] args) {
try {
InputFile in =
new InputFile("Cleanup.java");
String s;
int i = 1;
while((s = in.getLine()) != null)
System.out.println(""+ i++ + ": " + s);
in.cleanup();
} catch(Exception e) {
System.out.println(
"Caught in main, e.printStackTrace()");
e.printStackTrace();
}
}
} ///:~
Този пример използва класовете на Java 1.1 вход-изхода.
Конструкторът на InputFile взема String аргумент, който е името на файла, който ще се отваря. Вътре в try блока той създава FileReader използвайки името. FileReader не е особено полезен докато не го използвате за създаване на BufferedReader на който фактическиможе да говорите – забележете че една от ползите от InputFile е че комбинира тези две действия.
Ако конструктора на FileReader е неуспешен, той изхвърля FileNotFoundException, което трябва да се хване отделно, понеже иначе ще искате да затваряте файл който не е бил отворен. Всякакви други catch клаузи трябва да затворят файла понеже той е бил отворен към времето, когато се е влизало в тях. (Разбира се, още по-измамно е ако повече от един метод би могъл да изхвърли FileNotFoundException. В този сбучай може да поискате да разделите нещата в няколко try блока.) Методът close( ) изхвърля изключение което се опитва и хваща даже и да е вътре в друга catch клауза – това е само друга двойка фигурни скоби за компилатора на Java. След изпълнение на локалните опаерации изключението се преизхвърля, което е подходящо понеже този конструктор се е провалил, а вие не искате извикваният метод да смята, че всичко е наред.
В този пример, който не използва споменатата преди техника с флаговете, finally клаузата определено не е мястото за close( ) файла, повеже ще го затваря всеки път когато конструкторът завършва работата си. Тъй като искаме файлът да бъде отворен за полезна работа времето на живот на InputFile обекта не би било подходащо в такъв случай.
Методът getLine( ) връща String съдържащ следващия ред от файла. Той вика readLine( ), който може да изхвърли изключение, но то се хваща, така че getLine( ) не изхвърля никакви изключения. Един от въпросите къито възникват с изключенията е дали да се обработват изцяло на това ниво, или да се обработят частично и да се подаде същото изключение (или някое друго) нататък, или просто да се предаде нататък. Предаването му, когато е уместно, определено може да опрости програмирането. getLine( ) методът става:
String getLine() throws IOException {
return in.readLine();
}
Но разбира се, извикващият метод сега е отговорен за бработката на всякакво IOException което би могло да възникне.
cleanup( ) методът трябва да бъде извикано от потребителя когато свърши използването на InputFile обекта за да се освободят заеманите системни ресурси (като файлови манипулатори) заети чрез BufferedReader и/или FileReader обекти.6 Не искаме да правим това докато не свършим с InputFile обекта, точката, в която го искаме. Може да се мисли за слагане на такава функционалност във finalize( ) метод, но както се спомена в глава 4 не може да бъдем сигурни, че ще се извика някога finalize( ) (даже и да можехте да бъдете сигурни че ще се извика, не знаете кога). Това е един от минусите на Java – всички финални действия освен освобождаването на паметта не стават автоматично, така че трябва да информирате клиентите-програмисти че са отговорни, та може би да гарантират нещата чрез използване на finalize( ).
В Cleanup.java се създава InputFile за да отвори сорса на тази програма, файлът се чете ред по ред, добавят се номера на редовете. Всички изключения се хващат родово в main( ), макар и да бихте избрали, може би, по-голяма нееднородност.
Една от ползите на този пример е да се покаже защо изключенията бяха въведени точно на това място в книгата. Изключенията са толкова интегрирани в програмирането на Java, особено понеже компилаторът ги прави задължителни, че може да свършите само толкова (колкото знаете до тази глава-б.пр.) без да ги познавате.
Сподели с приятели: |