Често има парче код, което бихте искали да се изпълни в try блока, независимо дали има или не изключение. То обикновено е свързано с операции различни от освобождаването на рачет (понеже за последното се грижи боклучарят). За да се постигне това се използва клаузата finally4 в края на всички обработчици на изключения. Така пълната картина на кода за обработка на изключения е следната:
try {
// Охраняваният регион:
// Опасни неща които могат да изхвърлят A, B или C
} catch (A a1) {
// Манипулатор A
} catch (B b1) {
// Манипулатор B
} catch (C c1) {
// Манипулатор C
} finally {
// Парченцето код, което трябва да се изпълни винаги
}
За да се демонстрира че finally винаги се пуска, опитваме следната програма:
//: c09:FinallyWorks.java
// The finally clause is always executed
public class FinallyWorks {
static int count = 0;
public static void main(String[] args) {
while(true) {
try {
// post-increment is zero first time:
if(count++ == 0)
throw new Exception();
System.out.println("No exception");
} catch(Exception e) {
System.out.println("Exception thrown");
} finally {
System.out.println("in finally clause");
if(count == 2) break; // out of "while"
}
}
}
} ///:~
Тази програма показва и начин какво да правите с факта, че изключенията в Java (подобно на C++) не позволяват да се продължи от мястото, където изключението е било изхвърлено, както беше дискутирано преди. Ако сложите вашия try блок в цикъл, може да носочите условие, което трябва да бъде изпълнено, преди да продължи програмата. Може също да добавите static брояч или нещо друго подходящо, което да позволи на цикъла да пробва няколко подхода, преди да се откаже. По този начин може да се постигне по-високо ниво на "робастност" с вашата програма.
Изходът е:
Exception thrown
in finally clause
No exception
in finally clause
Било или не изхвърлено изключение, клаузата на finally винаги се изпълнява.
За какво е finally?
В език без боклукосъбиране и без автоматично извикване на деструктори,5 finally е важна, понеже дава възможност на програмиста да гарантира освобождаването на паметта без значение какво се е случило в try блока. Но Java има събиране на боклука, така че освобождаването на паметта практически никога не е проблем. Също, няма деструктори за извикване. Кога има нужда от finally в Java?
finally е необходима когато трябва нещо различно от памет да се сложи обратно в предишното му състояние. Това е обикновено нещо от рода на отворен файл или мрежова връзка, нещо начертано на екран или даже превключвател в отвъдния, реалния свят, както е моделирано в следния пример:
//: c09:OnOffSwitch.java
// Why use finally?
class Switch {
boolean state = false;
boolean read() { return state; }
void on() { state = true; }
void off() { state = false; }
}
public class OnOffSwitch {
static Switch sw = new Switch();
public static void main(String[] args) {
try {
sw.on();
// Code that can throw exceptions...
sw.off();
} catch(NullPointerException e) {
System.out.println("NullPointerException");
sw.off();
} catch(IllegalArgumentException e) {
System.out.println("IOException");
sw.off();
}
}
} ///:~
Целта тук е ключът да бъде изключен когато main( ) завърши, така че sw.off( ) се слага на края на трай блока на края на обработчика. Възможно е обаче да е изхвърлено изключение което да не е прихванато точно тука, така че sw.off( ) ще се пропусне. С finally може да сложите завършващия код на трай блока в само едно място:
//: c09:WithFinally.java
// Finally Guarantees cleanup
class Switch2 {
boolean state = false;
boolean read() { return state; }
void on() { state = true; }
void off() { state = false; }
}
public class WithFinally {
static Switch2 sw = new Switch2();
public static void main(String[] args) {
try {
sw.on();
// Code that can throw exceptions...
} catch(NullPointerException e) {
System.out.println("NullPointerException");
} catch(IllegalArgumentException e) {
System.out.println("IOException");
} finally {
sw.off();
}
}
} ///:~
Тук sw.off( ) е преместено на въпросното място, където гарантирано ще се изпълни без значение какво ще се случи.
Даже и в случаите когато изключението не е хванато в текущото множество на catch клаузи, finally ще се изпълни преди механизмът за поддръжка на изключенията да продължи търсенето си на обработчик в следващия по-широк контекст:
//: c09:AlwaysFinally.java
// Finally is always executed
class Ex extends Exception {}
public class AlwaysFinally {
public static void main(String[] args) {
System.out.println(
"Entering first try block");
try {
System.out.println(
"Entering second try block");
try {
throw new Ex();
} finally {
System.out.println(
"finally in 2nd try block");
}
} catch(Ex e) {
System.out.println(
"Caught Ex in first try block");
} finally {
System.out.println(
"finally in 1st try block");
}
}
} ///:~
Изходът от тази програма показва какво се случва:
Entering first try block
Entering second try block
finally in 2nd try block
Caught Ex in first try block
finally in 1st try block
Операторът finally ще се изпълни в ситуации където има break и continue оператори. Забележете че заедно с етикетирания break и continue, finally елиминира нуждата от goto оператор в Java.
Изобщо, реализацията на изключенията в Java е забележителна, но за нещастие има пукнатина. Въпреки че изключенията са признак за криза с вашата програма и не бива да се игнорират, възможно е просто да се загуби изключение. Това се случва в конкретна ситуация с използване на finally клауза:
//: c09:LostMessage.java
// How an exception can be lost
class VeryImportantException extends Exception {
public String toString() {
return "A very important exception!";
}
}
class HoHumException extends Exception {
public String toString() {
return "A trivial exception";
}
}
public class LostMessage {
void f() throws VeryImportantException {
throw new VeryImportantException();
}
void dispose() throws HoHumException {
throw new HoHumException();
}
public static void main(String[] args)
throws Exception {
LostMessage lm = new LostMessage();
try {
lm.f();
} finally {
lm.dispose();
}
}
} ///:~
Изходът е:
A trivial exception
at LostMessage.dispose(LostMessage.java:21)
at LostMessage.main(LostMessage.java:29)
Може да се види, че няма следа от VeryImportantException, което просто е заместено от HoHumException във finally клаузата. Това е достатъчно сериозен капан, понеже изключение може да бъде напълно изгубено, също и в много по-засукана и трудна за проследяване ситуация отколкото горната. За контраст, C++ третира ситуацията в която се изхвърля едно изключение преди да е приключила обработката на друго като ужасяваща програмна грешка. Може би някоя бъзеща версия на Java ще оправи проблема. (Горните резултати се произведоха с Java 1.1.)
Сподели с приятели: |