C, PHP, VB, .NET

Дневникът на Филип Петров


* Изключения

Публикувано на 22 септември 2009 в раздел ПИК3 Java.

Изключението (exception) е събитие, което не е предвидено за нормалното изпълнение на програмата. По принцип добрият начин на програмиране е ние да се подсигуряваме срещу всякакви грешки, като най-често срещаното делене на нула например. Например ако се опитвате да прочетете файл, който в процеса на изпълнение на програмата бива изтрит от друга програма, то се получава изключение java.io.FileNotFoundException.

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

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

Стандартно конструкцията на прихващане на изключения е следната:

try{
   <програмен код>
}
catch (<изключение> <име>){
   <програмен код, който се изпълнява при изключение>
}
finally{
   <програмен код, който се изпълнява винаги, въпреки изключение>
}

Ето веднага и един пример. Нека разгледаме елементарна програма, която "решава" линейно уравнение a.x + b = 0:

    // Izchisliava x ot a.x + b = 0
    int a = 2;
    int b = 4;
    System.out.println("x = "+(-b/a));

Ясно е, че при изпълнение на горния код програмата ще върне резултат x = -2. Очевидно е, че тук не сме се погрижили за ситуация, при която a = 0. Ето какво ще се получи като резултат в такава ситуация:

java.lang.ArithmeticException: / by zero
	at myfirstprogram.main(myfirstprogram.java:7)

Виждаме, че се е получила java.lang.ArithmeticException. Ето как бихме могли да я прихванем и обработим:

    // Izchisliava x ot a.x + b = 0
    int a = 0;
    int b = 4;
    try{
      System.out.println("x = "+(-b/a));
    }
    catch (java.lang.ArithmeticException e){
      if (b == 0) System.out.println("Vsiako x e reshenie");
      else System.out.println("Niama rehesnie");
    }

Изпълнението на горната програма ще даде резултат "Niama rehesnie". Ако прегледате предишните примери от статиите за вход/изход, то ще видите няколко примера за обработка на java.io.IOException и java.io.FileNotFoundException.

Горният пример се счита за лоша програмна практика. По принцип се счита, че колкото повече самите ние се грижим за данните си, толкова по-добра е програмата. Така горният пример е много по-добре да бъде преписан така, че да има просто един if(a==0) вместо да бъдат прихващани изключения. В подкрепа на това трябва да знаете, че изключенията изискват доста повече ресурс от процесора на компютъра. Затова правилото е хем да сте сигурни, че всички възможни ситуации за грешка са преднидени, но и колкото може по-малко try-catch блока са използвани.

Възможно е и прихващане на различни типове изключения. Нека разгледаме примера от миналата статия, в който записваме текст "Hello World" във файл. Ще го модифицираме така, че да обработва два различни типа изключение по различен начин:

    java.io.PrintStream out = null;
    try {
      out = new java.io.PrintStream(new java.io.FileOutputStream("output.txt"));
      int i = 123;
      out.print("Hello");
      out.println(" world "+i);
    }
    catch (java.io.FileNotFoundException e) {
            System.err.println("File Not Found");
    }
    catch (java.io.IOException e) {
            System.err.println("IO error has occured!");
    }
    finally {
       if (out!=null) out.close();
    }

Ако се случи стандартното "FileNotFoundException" (файлът output.txt не съществува - това може да се получи ако файлът бъде изтрит веднага след като PrintStream го е създал), което се "хвърля" от методите out.print() и out.println(), то ще бъде изписан текстът "File Not Found". Ако пък се предизвика друга грешка на вход/изход, то ще бъде изписан текстът "IO error has occured!" - това може да се случи при множество непредвидени ситуации, като например повреден диск или друга програма "заключила" достъпа до файла. Кодът в блок finally е необходим за това да сме напълно сигурни, че ако файлът е все още отворен, то програмата ще го затвори.

Важна е поредността на catch блоковете. В горния пример трябва да знаем, че FileNotFoundException е подмножество на IOException. Така ако сложим catch блока на IOException преди този на FileNotFoundException, то никога няма да можем да достигнем до "System.err.println("File Not Found")". Това вече ви подсказва, че изключенията могат да бъдат групирани.

Най-общата група изключения е изключението "Exception" (т.е. ако напишете catch (Exception e){...}). В него са групирани всички стандартни за Java изключения. Не е добра практика обаче да го използваме - добре е да конкретизираме изключенията, които очакваме.

Всяко изключение в Java носи т.нар. "Stack trace". Тази "пътека" на изключението дава информация къде точно в приложението е възникнала грешката. То се използва, за да може програмистът да проследи къде точно в програмния код е предизвикано изключението. Забелязахте ли, че давахме "име" на обекта на изключенията като "e"? Ето как можем да го използваме. Ще направим демонстрация с лошия пример от началото:

    // Izchisliava x ot a.x + b = 0
    int a = 0;
    int b = 4;
    try{
      System.out.println("x = "+(-b/a));
    }
    catch (java.lang.ArithmeticException e){
      e.printStackTrace();
    }

Резултатът е:

java.lang.ArithmeticException: / by zero
at myfirstprogram.main(myfirstprogram.java:8)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at edu.rice.cs.dynamicjava.symbol.JavaClass$JavaMethod.evaluate(JavaClass.java:326)
at edu.rice.cs.dynamicjava.interpreter.ExpressionEvaluator.handleMethodCall(ExpressionEvaluator.java:92)
at edu.rice.cs.dynamicjava.interpreter.ExpressionEvaluator.visit(ExpressionEvaluator.java:84)
at koala.dynamicjava.tree.StaticMethodCall.acceptVisitor(StaticMethodCall.java:105)
at edu.rice.cs.dynamicjava.interpreter.ExpressionEvaluator.value(ExpressionEvaluator.java:38)
at edu.rice.cs.dynamicjava.interpreter.ExpressionEvaluator.value(ExpressionEvaluator.java:37)
at edu.rice.cs.dynamicjava.interpreter.StatementEvaluator.visit(StatementEvaluator.java:106)
at edu.rice.cs.dynamicjava.interpreter.StatementEvaluator.visit(StatementEvaluator.java:29)
at koala.dynamicjava.tree.ExpressionStatement.acceptVisitor(ExpressionStatement.java:101)
at edu.rice.cs.dynamicjava.interpreter.Interpreter.evaluate(Interpreter.java:86)
at edu.rice.cs.dynamicjava.interpreter.Interpreter.interpret(Interpreter.java:47)
at edu.rice.cs.drjava.model.repl.newjvm.InterpreterJVM.interpret(InterpreterJVM.java:205)
at edu.rice.cs.drjava.model.repl.newjvm.InterpreterJVM.interpret(InterpreterJVM.java:182)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
at sun.rmi.transport.Transport$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at sun.rmi.transport.Transport.serviceCall(Unknown Source)
at sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)

Наблегнете на първите два реда:

java.lang.ArithmeticException: / by zero
at myfirstprogram.main(myfirstprogram.java:8)

Те ни казват, че е възникнала ArithmeticException деление на нула и това се е случило на ред 8 от файл myfirstprogram.java. По-надолу се дава целия стек на програмата.

Ясно е, че не бихме искали да даваме подобна информация на потребителите. Много по-добре е да им дадем адекватно съобщение за грешка, което "непрограмист" да може да разбере. Затова една програма в т.нар. "production release" не би трябвало да има извикани printStackTrace() на изключенията си. В следващата статия ще разгледаме статични методи и по-късно как се получава "хвърляне" на изключения от един метод на друг.

 



Един коментар


Добави коментар

Адресът на електронната поща няма да се публикува


*