Разработка INTRANET приложений

         

Обработка исключений и легковесные процессы (потоки)


Одним из важнейших элементов программирования является обработка особых ситуаций. Такие ситуации порождаются, например, делением на ноль, выходом за границы отведенной памяти или попытками записи в защищенные блоки памяти. Если в обычных языках программирования разработчик может обрабатывать эти нештатные ситуации (обычно называемые прерываниями), а может и не обрабатывать, то в Java он обязан это делать или явно указать на обработку прерываний. Все такие необычные действия в Java называются исключениями (exception).

Перехват исключений в Java оформляется блоком "try-catch". Смысл у него очень простой: сначала делается попытка выполнить фрагмент кода, и если генерируется исключение, то оно обрабатывается фрагментом catch:

try { // codeblock } catch (exception) { //exceptionexecutioncode } catch (exception) { //exceptionexecutioncode throw (exception); } finally { }

Из примера видно, что кроме try и catch при обработке прерываний могут применяться еще операторы throw и finally. Первый используется для порождения исключения, а второй для передачи обработки исключения обработчику умолчания.

Все исключения делятся на два класса Exception и Error. В корне иерархии исключений находится класс Throwable. Класс Exception содержит описания исключений, которые должны перехватываться программным кодом разработчика, если их обработка не предусмотрена в программе, то компилятор выдаст сообщение об ошибке. Например, все работы с сетью должны содержать конструкции try и catch:

importjava.net.*; importjava.io.*; importjava.applet.*; importjava.awt.*; publicclassurl_1 extendsApplet { Socketurl; InputStreamis; OutputStreamos; DataOutputStreamdos; intc; StringBuffersb = newStringBuffer (); bytebyte_b[] = newbyte[1024]; intc_l; longdate;

publicvoidinit () { try { url = newSocket ("localhost",80); is = url.getInputStream (); os = url.getOutputStream (); dos = newDataOutputStream (os); dos.writeBytes ("GET /java/test.htmHTTP/1.0\r\n\r\n"); while ( (c=is.read ())!=-1) { sb.append ( (char) c); } os.close (); is.close (); url.close (); } catch (IOExceptionee) { System.out.println ("Problemwithreading."); } add ("Center",newTextArea (sb.toString ())); } publicvoidpaint (Graphicsg) { g.drawString (sb.toString (),10,40); } }


В этом примере в блок перехвата исключения попали все операции ввода/вывода, которые выполняются через сеть. Данный пример хорош еще и тем, что показывает отсутствие разницы между вводом/выводом локальным и удаленным, и там применяются одни и те же методы, только объекты, у которых эти методы определены - разные. К этому примеру мы еще вернемся при обсуждении реализации сетевых обменов между клиентами и серверами.

Если метод не собирается перехватывать исключение, то это должно быть указано. Для этой цели служит модификатор throws:

typemethod_name (args_list) throwsexception_list { ... }

Если в предыдущем примере убрать блок try-catch, и использовать throws, то компилятор должен допустить данный исходный текст. Если не будет указано ни первое, ни второе, то код не будет оттранслирован.

Если прерывание (исключение) необходимо проигнорировать, то в этом случае в связке с try нужно использовать finally:

try { // code } catch { // code } finally { // code }

Блок finally будет выполняться до того, как исключение будет перехвачено блоком catch. Если catch нет вовсе, то сначала будет выполнен блок finally, а только потом управление будет передано обработчику исключений.

Исключения - это такие же классы, как и все остальные. Разработчик может расширить число этих классов. Однако, расширять можно только класс Exception. Для возбуждения исключения, порожденного пользователем применяется оператор throw. В приведенном примере возбуждение исключения происходит при достижении счетчиком значения 10.

classNewExeptionextendsException { privateintparm; NewException (intparm) { parm = a; } publicStringtoString () { return "valuetoolong:"+parm+"."; } } classCycle { staticvoidculc (intx) throwsNewException { inti; for (i=0;i<x;i++) { if (i==10) thrownewNewException (i); System.out.println ("i="+i+"."); } } publicstaticvoidmain (Stringargs[]) { try { culc (9); culc (20); } catch (NewExceptione) { System.out.println ("NewException."); } } }



В этом примере представлено Java-приложение (application), которое выполняется локально, а не передается по сети. Главный (main) метод класса Cycle пытается выполнить метод culc, который в свою очередь порождает исключение, если параметр цикла превысит значение 10. Обратите внимание, что culc игнорирует исключение NewException, поэтому оно попадает на обработку в блок try-catch метода main.

Любое Java-приложение (application) должно иметь метод main, с которого собственно и начинается процесс выполнения Java-программы. Для апплетов действует совершенно другой порядок выполнения кода.

Еще одним важным свойством Java является способность непосредственно использовать потоки или, как их называют в Unix, легковесные процессы. Отличие потока от полноценного процесса заключается в том, что при создании нового потока система не копирует код программы в новую область памяти, которую отводит под поток. Реально на одном и том же коде выполняется сразу несколько потоков. При этом время порождения потока, существенно меньше, чем при порождении полновесного процесса, и памяти для его функционирования нужно также меньше. При этом потоки исполняются параллельно.

Практически все пользователи Web так или иначе, если конечно они разрешают выполнение Java-апплетов на своем компьютере, видели многопоточные апплеты - это бегущие строки и мигающие картинки. Идея этой мультипликации заключается в том, что кроме основного потока, applet порождает еще один поток, который "просыпается" время от времени и меняет параметры отображения информации, например, координаты отображения текста или название отображаемого графического образа.

В рамках данного курса не преследуется цель научить тонкостям программирования Java, поэтому мы рассмотрим только пример применения легковесного процесса (потока) при программировании бегущей строки:

importjava.applet.*; importjava.awt.*; publicclasshelloextendsAppletimplementsRunnable { intx = 0;inty = 0;intstart_x = 0;intstart_y = 0; Threadnew_thread = null; Stringmessage = "Hellobody!!!"; publicvoidinit () { y = size ().height;x = size ().width;start_x = x;start_y = y; } publicvoidstart () { new_thread = newThread (this); new_thread.start (); } publicvoidrun () { while (true) { repaint (); x -= 10; if (x<0) { x=start_x;} try { Thread.sleep (100);} catch (InterruptedExceptione) { } } } publicvoidpaint (Graphicsg) { Fontnew_font = newFont ("TimesRoman",1,48); g.setFont (new_font); g.drawString ("Hello, Java.",x,y/2); } }



Обсудим этот пример. Первые строчки указывают на то, что мы импортируем в нашу программу описания классов Applet и AWT. Первый класс описывает все необходимые параметры для передачи и запуска небольшой Java-программы по сети. Второй класс описывает параметры настройки интерфейса пользователя, он нам нужен для того, чтобы отображать информацию в окне апплета, которое будет открыто в рабочей области броузера.

Класс hello - это наша программа. Она расширяет класс applet и использует интерфейс Runnable. Этот интерфейс необходим для того, чтобы породить подпроцесс (поток). Он специфицирует метод run (), который мы определяем в своей программе (классе hello).

Для того, чтобы переменные были доступны всем методам класса, мы их вынесли в начало. Метод init () выполняется первым после загрузки апплета. В нем производится присвоение начальных значений. Затем, после init (), выполняется метод start (). В этом методе мы порождаем новый поток. Собственно больше ничего и не делаем. Метод run () содержит код этого потока. Отображение информации осуществляется методом paint (). Реально этот метод вызывается системой только в том случае, если с экраном что-либо произошло: наехало другое окно или мы изменили размеры окна броузера. В нашем случае мы принудительно заставляем выполниться paint (), вызывая метод repaint () в методе run ().

Таким образом, из этого примера видно, что все потоки (вычисление параметров, отображение информации, изменение размеров окна броузера и т.п.) исполняется параллельно и не зависимо друг от друга. Синхронизация осуществляется за счет анализа событий, происходящих в системе.


Содержание раздела