Java съдържа клас наречен Throwable който описва всяко нещо, което може да бъде изхвърлено като изключение. Има два общи типа Throwable обекти (“типа” = “наследени от”). Error представя системни и грешки по време на компилация, за които не се безпокоите за хващането им (освен в специални случаи). Exception е основен тип който може да бъде изхвърлен от всеки от методите в стандартните библиотеки класове на Java и от ваши методи и несполуки по време на изпълнение.
Най-добрия начин да се получи поглед върху изключенията е да се разгледа онлайн Java документацията от http://java.sun.com . (Разбира се, по-лесно е първо да се разтовари.) Заслужава си да се направи това веднъж само за да се получи чувство за различните видове изключения, но скоро ще забележите че няма нещо кой знае колко различно между две изключения освен името. Също, броят на изключенията в Java продължава да расте; общо взето е безпредметно да се печатат в книга. Всяка библиотека от трети доставчик също би имала свои собствени изключения вероятно. Важното нещо тук е да се разбере концепцията и вие така ще направите с изключенията.
java.lang.Exception
Това е основният клас на изключение който може да хване вашата програма. Други изключения са извлечени от това. Основната идея е имената на изключенията да отразяват същността на възникналия проблем и да са самоговорящи. Не всички изключения са определени в java.lang; някои са за поддръжка на други библиотеки като util, net и io, което може да видите от техните пълни класови имена откъдето са наследени. Например всички IO изключения са наследени от java.io.IOException.
Специалният случай RuntimeException
Първият пример в тази глава беше
if(t == null)
throw new NullPointerException();
Може да е малко стряскащо да трябва да се проверява всеки път за null всеки манипулатор който се подава на метод (понеже не може да се знае дали е подаден валиден манипулатор). За щастие това не е необходимо – то е част от стандартните проверки по време на изпълнение, които Java прави за вас, та ако някое извикване се прави с нулев манипулатор, Java автоматически изхвърля NullPointerException. Така че горната проверка е винаги излишна.
Има цяла група типове изключения които попадат в тази категория. Те винаги се изхвърлят автоматично от Java и не е необходимо да ги включвате във вашите спецификации на изключения. Достатъчно удобно те са свързани помежду си чрез единствен базов клас RuntimeException, което е перфектен пример на наследяване: основава се фамилия от типове които имат нещо общо в поведението си. Също никога не е необходимо да правите спецификация в която да споменавате че може да се изхвърли RuntimeException, понеже това си се предполага. Понеже те индицират бъгове, практически никога не прихващате RuntimeException – това е направено автоматично. Ако непременно трябваше да проверявате за RuntimeExceptionи кодът ви би станал объркан. Въпреки че типично не прихващате RuntimeExceptionи, във ваши собствени пакети може да поискате да изхвърлите RuntimeExceptionи.
Какво се случва ако не прихванете такива изключения? Понеже компилаторът не ви налага да правите спецификации за тях, твърде правдоподобно е че RuntimeException би могъл да се процеди по някакъв начин през вашия main( ) метод без да бъде хванат. За да видите какво става в такъв случай, пробвайте следния пример:
//: c09:NeverCaught.java
// Ignoring RuntimeExceptions
public class NeverCaught {
static void f() {
throw new RuntimeException("From f()");
}
static void g() {
f();
}
public static void main(String[] args) {
g();
}
} ///:~
Може вече да сте видели че RuntimeException (или нещо наследено от него) е специален случай, понеже компилеторът не изисква спецификация за тези типове.
Изходът е:
java.lang.RuntimeException: From f()
at NeverCaught.f(NeverCaught.java:9)
at NeverCaught.g(NeverCaught.java:12)
at NeverCaught.main(NeverCaught.java:15)
Така че отговорът е следният: Ако RuntimeException се просмуче чрез main( ) без да бъде хванато, вика се printStackTrace( ) за същото изключение и изпълнението на програмата се прекратява.
Помнете че е възможно да се игнорират само RuntimeExceptionите във вашият код, нали за всички други компилаторът грижливо ви заставя да осигурите поддръжка. Основанието е че RuntimeException представя програмни грешки:
-
Грешка която не можете да хванете (получаване на нулев манипулатор подаден на ваш метод от клиента-програмист, например)
-
Грешка, която вие, като програмист, трябва да сте проверили в своя код (като ArrayIndexOutOfBoundsException където е трябвало да внимавате за обхвата на индекса да не се надвиши).
Може да се види какви огромни ползи има от този начин на работа с изключенията, те могат да се използват и за тестване.
Интересно е да се отбележи, че не може да се класифицира за поддръжка на изключения в Java като инструмент с едно приложение. Да, то е проектирано да се справя с онези случаи, когато възникват грешки по време на изпълнение поради причини, които не са подвластни на вашия код, но също е важно и за някои типове програмистки грешки, които компилаторът не може да открие.
Създаване на ваши собствени изключения
Не сте огпеничени да използвате само изключнията на Java. Това е важно, понеже често трябва да се подработи въпросът с някои грешки, които вашата библиотека може да сътвори, които обаче не са могли да бъдат предсказани когато се е създавала йерархията на Java.
За да създадете ваш собствен клас за изключение, принудени сте да наследите от съществуващ тип изключение, за предпочитане от това, което е най-близко по значението си до вашето. Наследяването на изключение е доста просто:
//: c09:Inheriting.java
// Inheriting your own exceptions
class MyException extends Exception {
public MyException() {}
public MyException(String msg) {
super(msg);
}
}
public class Inheriting {
public static void f() throws MyException {
System.out.println(
"Throwing MyException from f()");
throw new MyException();
}
public static void g() throws MyException {
System.out.println(
"Throwing MyException from g()");
throw new MyException("Originated in g()");
}
public static void main(String[] args) {
try {
f();
} catch(MyException e) {
e.printStackTrace();
}
try {
g();
} catch(MyException e) {
e.printStackTrace();
}
}
} ///:~
Наследяването се случва при създаването на нов клас:
class MyException extends Exception {
public MyException() {}
public MyException(String msg) {
super(msg);
}
}
Ключовата фраза тук е extends Exception, тоест “това е всичко което е в Exception и още.” Добавеният код е малък – добавени са два конструктора които показват как да се създава MyException. Помнете че компилаторът автоматично извиква конструктора на базовия клас ако не го извикате явно, както в конструктора по подразбиране MyException( ). Във втория конструктор конструкторът на базовия кас със String аргумент явно се извиква с ключовата дума super.
Изходът от програмата е:
Throwing MyException from f()
MyException
at Inheriting.f(Inheriting.java:16)
at Inheriting.main(Inheriting.java:24)
Throwing MyException from g()
MyException: Originated in g()
at Inheriting.g(Inheriting.java:20)
at Inheriting.main(Inheriting.java:29)
Може да видите отсъствието на детайлно съобщение в изхвърленото от f( ) MyException.
Процесът на създаване на ваши собствени изключения може да бъде продължен. Може да добавите конструктори и членове:
//: c09:Inheriting2.java
// Inheriting your own exceptions
class MyException2 extends Exception {
public MyException2() {}
public MyException2(String msg) {
super(msg);
}
public MyException2(String msg, int x) {
super(msg);
i = x;
}
public int val() { return i; }
private int i;
}
public class Inheriting2 {
public static void f() throws MyException2 {
System.out.println(
"Throwing MyException2 from f()");
throw new MyException2();
}
public static void g() throws MyException2 {
System.out.println(
"Throwing MyException2 from g()");
throw new MyException2("Originated in g()");
}
public static void h() throws MyException2 {
System.out.println(
"Throwing MyException2 from h()");
throw new MyException2(
"Originated in h()", 47);
}
public static void main(String[] args) {
try {
f();
} catch(MyException2 e) {
e.printStackTrace();
}
try {
g();
} catch(MyException2 e) {
e.printStackTrace();
}
try {
h();
} catch(MyException2 e) {
e.printStackTrace();
System.out.println("e.val() = " + e.val());
}
}
} ///:~
Даннов член i е добавен, заедно с метод който чете стойността му и допълнителен конструктор който я задава. Изходът е:
Throwing MyException2 from f()
MyException2
at Inheriting2.f(Inheriting2.java:22)
at Inheriting2.main(Inheriting2.java:34)
Throwing MyException2 from g()
MyException2: Originated in g()
at Inheriting2.g(Inheriting2.java:26)
at Inheriting2.main(Inheriting2.java:39)
Throwing MyException2 from h()
MyException2: Originated in h()
at Inheriting2.h(Inheriting2.java:30)
at Inheriting2.main(Inheriting2.java:44)
e.val() = 47
Понеже изключението е само друг вид обект, може да продължите процеса на украсяване на мощта на вашите класове-изключения. Помнете, обаче, че цялото това дрешено може да бъде загубено при клиент-програмистите, понеже те биха могли просто да видят дали се е изхвърлило изключение и нищо повече. (Това е начинът, по който се използват повечето от изключенията в библиотеките на Java.) Ако случаят е такъв, възможно е да се създаде нов тип изключение с почти никакъв код:
//: c09:SimpleException.java
class SimpleException extends Exception {
} ///:~
Това се обляга на задължението на компилатора да създаде конструктор по подразбиране (който автоматично вика конструктора на базовия клас). Разбира се, в този случай нямате SimpleException(String) конструктор, но на практика той не се и използва много.
Сподели с приятели: |