Страница:
полнения программы, в языке Java используется конструкция try/
catch/finally. Блок try содержит инструкции, выполнение может
привести к возникновению исключительных ситуаций. Следующие за
ним один или несколько блоков catch предназначены для обработки
исключительных ситуаций. Наконец, блок finally содержит инст-
рукции, которые будут выполнены независимо от возникновения
исключительной ситуации в блоке try. При выходе из try-части
посредством инструкций передачи управления (break, return и
т.п.) блок finally также будет выполнен.
Для передачи информации об исключительной ситуации используются
объекты классов - наследников класса Throwable. Например, класс
ArrayIndexOutOfBoundsException отвечает за контроль выхода ин-
дексов за границы массивов, класс OutOfMemoryException - за ре-
акцию на исчерпание свободной памяти, класс ClassCastException
- за ошибки при преобразовании типов, класс
InterruptedException - за обработку прерывания текущего потока
и т.д. Компонентой всех этих классов является поле типа String,
в которое помещается текст сообщения об ошибке. Метод
getMessage возвращает этот текст.
В подобных объектах может содержаться и дополнительная информа-
ция. Например, объекты класса InterruptedIOException содержат
поле, в которое заносится число байт, переданных до возникнове-
ния исключительной ситуации.
Следующий фрагмент программы распечатывает сообщения из массива
messages. При этом мы не пытаемся выяснить размер этого масси-
ва, а просто полагаемся на механизм обработки исключительных
ситуаций. (Конечно, мы не советуем писать программы в таком
стиле).
try {
for (int i = 0; i < 100; i++) {
System.out.println (messages[i]);
}
}
catch (ArrayOutOfBoundException e) {
System.out.println ("No more messages");
}
catch (Exception e) {
System.out.println ("Unexpected exception");
System.out.println (e.getMessage());
}
finally {
System.out.println ("Work done");
}
Искобчительные ситуации могут возбуждаться программно при помо-
щи инструкций вида
throw new MyException ("Something's wrong");
Спецификации языка Java подразделяют исключительные ситуации на
две категории. К первой категории (класс Error) относятся ситу-
ации, на которые программа не обязана реагировать (это заведомо
сделает Java-машина). Ко второй категории (класс Exception) от-
носятся ситуации, которые программа должна обрабатывать обяза-
тельно. Если при выполнении метода может возникать исключитель-
ная ситуация второго типа, он должен либо обрабатывать ее сам с
помощью конструкции try/catch/finally, либо в его определении
должна фигурировать конструкция
throws Exception1, Exception2, ...
Пример.
class Foo extends Object {
. . .
public void readFromFile (String fn) throws InvalidFormatException {
FileInputStream fis;
try {
fis = new FileInputStream (fn);
// Читаем данные из файла.
. . .
// Если файл имеет неправильный формат,
// вожбуждаем исключительную ситуацию:
throw new InvalidFormatException ("Wrong format");
. . .
}
catch (FileNotFoundException e) {
// Предпринимаем соответствующие действия
}
finaly {
if (fis != null )
fis.close(); // всегда закрываем файл, если он был открыт
}
}
. . .
}
В этом примере в методе readFromFile могут возникнуть две искл-
чительные ситуации. Первая связана с тем, что нужный файл не-
доступен. Эта ситуация обработывается внутри метода
readFromFile. Вторая исключительная ситуация может вознкнуть,
если файл имеет неправильный формат. Эта ситуация передается
для обработки наверх.
Механизм потоков - обязательная черта современных операционных
сред. За счет потоков обеспечивается масштабируемость программ-
ных систем, оптимальное использование аппаратных ресурсов, вы-
сокая скорость отклика на запросы пользователей. Поэтому нет
ничего удивительного в том, что в языке Java механизм потоков
представлен с самого начала и в полном объеме.
В языке Java потоки представлены посредством класса Thread, ин-
терфейса Runnable, спецификатора метода synchronized и методов
класса Object wait и notify.
4.1.6.1. Класс Thread и интерфейс Runnable
Поток (thread) представляет собой отдельный поток управления в
пределах процесса. Таким образом, у каждого потока есть начало,
последовательность действий, текущее состояние и конец.
Поток запускается с помощью вызова метода start() класса
Thread. Последовательность действий, выполняемых в рамках пото-
ка, задается в методе run(). Подчеркнем, что метод run() ис-
пользуется только для задания помледовательности действий; явно
вызывать его не только не нужно, но и просто вредно.
Поток заканчивается либо при завершении выполнения метода
run(), либо с помощью явных вызовов методов класса Thread
stop() или destroy(). Возобновить работу завершенного потока
невозможно.
Для временной приостановки работы потока с последующим возоб-
новлением служат методы suspend(), sleep() и yeild().
Обычно поток, приостановленный с помощью метода suspend, возоб-
новляет работу посредством метода resume().
Вызов метода sleep() приводит к приостановке потока на заданное
число миллисекунд.
Вызов метода yeild() означает добровольную уступку процессора
другому потоку; первоначальный поток остается готовым к выпол-
нению.
Java-потоки обладают приоритетами. В спецификациях оговаривает-
ся, что Java-машина реализует вытесняющую многопотоковость. Это
означает, что поток с большим приоритетом может прервать выпол-
нение менее приоритетного потока. Однако, спецификации не тре-
буют наличия разделения времени. Это значит, что для передачи
управления потоку с тем же приоритетом, вообще говоря, требуют-
ся явные действия со стороны первоначального потока - вызов ме-
тодов suspend(), sleep() или yeild().
На рис. 10 представлена диаграмма состояний потоков.
Рис. 10. Диаграмма состояний потоков.
Следующий пример содержит фрагмент одного из многочисленных ва-
риантов решения задачи "производитель/потребитель". Он заимст-
вован из письма, которое написал Mark Tillotson в группу сете-
вых новостей comp.lang.java.
class my_producer extends Thread
{
int items_to_do ;
my_buffer the_buffer ;
my_producer (my_buffer buf, int count)
{ super() ;
the_buffer = buf ;
items_to_do = count ;
}
public void run ()
{
while (items_to_do > 0)
{ System.out.println ("producer to_do = " + items_to_do) ;
Integer item = new Integer (items_to_do*items_to_do) ;
the_buffer.insert (item) ;
items_to_do-- ;
}
System.out.println ("producer exiting") ;
}
}
Данный производитель помещает в буфер квадраты целых чисел.
В приведенном простом примере класс my_producer является нас-
ледником класса Thread, что делает его потоком с последователь-
ностью действий, заданной методом run(). В реальных программах,
как правило, объект должен наследовать у какого-либо предшест-
венника содержательные свойства, а возможность паралелльного
выполнения ему предоставляется интерфейсом Runnable. Этот ин-
терфейс содержит единственный метод - run(). Пример.
1 class SomethingToRun extends BaseRunner implements Runnable {
2 private Thread aThread;
3 public void run () {
// выполняемые действия
. . .
4 }
5 SomethingToRun () {
6 aThread = new Thread (this);
7 aTread.start ();
8 }
9 }
В строке 6 создается новый поток. Аргументом конструктора явля-
ется обект класса SomethingToRun, а, значит, последовательность
выполняемых действий потока будет определяться методом run()
этого класса. Вызов метода start() в строке 7 ставит поток в
очередь готовых для выполнения.
Как и во всякой многопроцессной или многопотоковой среде, в
Java существует проблема синхронизации доступа к разделяемым
ресурсам. Примером такого ресурса является буфер в задаче "про-
изводитель/потребитель".
Для опытных программистов отметим, что модель синхронизации,
принятая в языке Java, опирается на концепцию монитора, предло-
женную в 70-е годы Бринк-Хансеном.
В Java-программах можно выделять критические интервалы, которые
обозначаются ключевым словом synchronized. Если критическим ин-
тервалом является метод, спецификатор synchronized помещается в
его (метода) заголовок. Для превращения произвольной инструкции
(обычно это блок) в критический интервал служит конструкция
synchronized (выражение) инструкция;
где результатом выражения должен быть объект или массив.
Выполнение критического интервала начинается только после полу-
чения потоком монопольного доступа к соответствующему объекту.
До наступления этого момента поток блокируется.
Вызов wait() внутри критического интервала приводит к тому, что
текущий поток уступает монопольное право на критический интер-
вал и приостанавливается до тех пор, пока из какого-либо друго-
го потока не будет сделан вызов notify() или notifyAll(). Хоро-
шей иллюстрацией использования средств синхронизации потоков
является упоминавшаяся выше программа Марка Тиллотсона.
class my_buffer
{
Object [] vec = new Object [8] ;
int ip = 0 ;
int ep = 0 ;
synchronized void insert (Object item)
{
do
{
if (ip-ep < 8)
{ vec [(ip++) & 7] = item ;
if (ip-ep == 1) notify () ; // Уведомить, если буфер был пуст
return ;
}
try wait () ; catch (InterruptedException e) ;
} while (true) ;
}
synchronized Object extract ()
{
do
{
if (ip > ep)
{ Object result = vec [(ep++) & 7] ;
if (ip-ep == 7) notify(); // Уведомить, если буфер был полон
return result ;
}
try wait () ; catch (InterruptedException e) ;
} while (true) ;
}
}
class my_producer extends Thread
{
int items_to_do ;
my_buffer the_buffer ;
my_producer (my_buffer buf, int count)
{ super() ;
the_buffer = buf ;
items_to_do = count ;
}
public void run ()
{
while (items_to_do > 0)
{ System.out.println ("producer to_do = " + items_to_do) ;
Integer item = new Integer (items_to_do*items_to_do) ;
the_buffer.insert (item) ;
items_to_do-- ;
}
System.out.println ("Производитель заканчивает работу") ;
}
}
class my_consumer extends Thread
{
int items_to_do ;
my_buffer the_buffer ;
my_consumer (my_buffer buf, int count)
{ super() ;
the_buffer = buf ;
items_to_do = count ;
}
public void run ()
{
while (items_to_do > 0)
{ System.out.println ("consumer to_do = " + items_to_do) ;
Object item = the_buffer.extract () ;
System.out.println ("consumer got " + item) ;
items_to_do-- ;
}
System.out.println ("Потребитель заканчивает работу") ;
synchronized (this){
notify () ; // Посылаем уведомление о завершении работы
// (см. con.wait() в main())
}
}
}
public class threaded3
{
public static void main (String [] args) throws InterruptedException
{
my_buffer the_buffer = new my_buffer () ;
my_producer prod = new my_producer (the_buffer, 40) ;
my_consumer con = new my_consumer (the_buffer, 40) ;
Thread.currentThread().setPriority (5) ;
prod.setPriority (4) ; // Производитель получает более высокий приоритет
con.setPriority (3) ; // по сравнению с потребителем
prod.start() ;
con.start() ;
synchronized (con)
{
con.wait() ; // Ждем уведомления от производителя об окончании
// его работы
}
System.out.println ("Производитель и потребитель закончили работу") ;
}
}
Приведенная программа написана в очень хорошем, понятном стиле.
Мы прокомментируем лишь один момент. В методах insert() и
extract() класса my_buffer вызов wait() содержится внутри бес-
конечного цикла. Дело в том, что вызов notify() относится к об-
ъекту в целом. "Разбуженный" объект должен проанализировать
свое состояние и решить, что делать дальше. Так, если "заснул"
метод insert(), то после возобновления работы необходимо прове-
рить, что буфер уже не полон и добавление нового элемента стало
возможным. Если это не так, метод insert() заснет вновь.
В принципе, технологический цикл подготовки, трансляции, редак-
тирования внешних связей, тестирования, отладки и выполнения
Java-программ тот же, что и для других интерпретируемых языков
программирования, но с одним существенным отличием - при редак-
тировании внешних связей требуемые компоненты могут доставлять-
ся по сети (рис. xxx).
Рис. xxx. Технологический цикл Java-программы
Важно отметить, однако, что Java-программы могут представать
как бы в двух ипостасях - как самостоятельное приложение и как
аплет, то есть совокупность объектов, выполняющихся в среде
WWW-навигатора.
С точки зрения программиста, аплет и приложение отличаются в
первую очередь точками входа и жизненным циклом.
Приложение в качестве точки входа имеет метод
public static void main (String args[]);
этот метод должен быть определен в том public-классе, который содержится в
файле, выполняемом виртуальной Java-машиной.
В параметр args передается массив строк - параметров командной строки.
Пример: программа, печатающая свои аргументы
public class myTop {
public static void main (String args[]){
int argc = args.length;
for (int i = 0; i < argc; i++)
System.out.println (argc[i]);
}
}
Аплет выполняется в контексте навигатора и его жизненный цикл
определяется следующими методами класса Applet:
public void init () вызывается навигатором при загрузка аплета;
public void start (); вызывается навигатором при показе страницы;
public void stop (); вызывается навигатором, когда тот уходит с Web-страницы;
public void destroy (); этот метод предназначен для освобожденя
ресурсов; аналог деструктора, но не вызывается автоматически; всегда
вызывает stop(); всегда вызывается при выходе из навигатора и при
перезагрузке аплета.
Простейший аплет выглядит так:
1 import java.awt.Graphics;
2 import java.applet.Applet;
3 class SimpleApplet extends Applet {
4 public void paint (Graphics g) {
5 g.drawString ("Hello world!", 10, 10);
6 }
7 }
Метод public void paint (Graphics g) (строки 4-6) определяет,
как аплет перерисовывает себя в тот момент, когда оконный ме-
неджер посылает WWW-навигатору запрос на перерисовку.
Включение аплета в WWW-страницу производится следующим образом.
В языке HTML 2.0 предусмотрены специальные конструкции
catch/finally. Блок try содержит инструкции, выполнение может
привести к возникновению исключительных ситуаций. Следующие за
ним один или несколько блоков catch предназначены для обработки
исключительных ситуаций. Наконец, блок finally содержит инст-
рукции, которые будут выполнены независимо от возникновения
исключительной ситуации в блоке try. При выходе из try-части
посредством инструкций передачи управления (break, return и
т.п.) блок finally также будет выполнен.
Для передачи информации об исключительной ситуации используются
объекты классов - наследников класса Throwable. Например, класс
ArrayIndexOutOfBoundsException отвечает за контроль выхода ин-
дексов за границы массивов, класс OutOfMemoryException - за ре-
акцию на исчерпание свободной памяти, класс ClassCastException
- за ошибки при преобразовании типов, класс
InterruptedException - за обработку прерывания текущего потока
и т.д. Компонентой всех этих классов является поле типа String,
в которое помещается текст сообщения об ошибке. Метод
getMessage возвращает этот текст.
В подобных объектах может содержаться и дополнительная информа-
ция. Например, объекты класса InterruptedIOException содержат
поле, в которое заносится число байт, переданных до возникнове-
ния исключительной ситуации.
Следующий фрагмент программы распечатывает сообщения из массива
messages. При этом мы не пытаемся выяснить размер этого масси-
ва, а просто полагаемся на механизм обработки исключительных
ситуаций. (Конечно, мы не советуем писать программы в таком
стиле).
try {
for (int i = 0; i < 100; i++) {
System.out.println (messages[i]);
}
}
catch (ArrayOutOfBoundException e) {
System.out.println ("No more messages");
}
catch (Exception e) {
System.out.println ("Unexpected exception");
System.out.println (e.getMessage());
}
finally {
System.out.println ("Work done");
}
Искобчительные ситуации могут возбуждаться программно при помо-
щи инструкций вида
throw new MyException ("Something's wrong");
Спецификации языка Java подразделяют исключительные ситуации на
две категории. К первой категории (класс Error) относятся ситу-
ации, на которые программа не обязана реагировать (это заведомо
сделает Java-машина). Ко второй категории (класс Exception) от-
носятся ситуации, которые программа должна обрабатывать обяза-
тельно. Если при выполнении метода может возникать исключитель-
ная ситуация второго типа, он должен либо обрабатывать ее сам с
помощью конструкции try/catch/finally, либо в его определении
должна фигурировать конструкция
throws Exception1, Exception2, ...
Пример.
class Foo extends Object {
. . .
public void readFromFile (String fn) throws InvalidFormatException {
FileInputStream fis;
try {
fis = new FileInputStream (fn);
// Читаем данные из файла.
. . .
// Если файл имеет неправильный формат,
// вожбуждаем исключительную ситуацию:
throw new InvalidFormatException ("Wrong format");
. . .
}
catch (FileNotFoundException e) {
// Предпринимаем соответствующие действия
}
finaly {
if (fis != null )
fis.close(); // всегда закрываем файл, если он был открыт
}
}
. . .
}
В этом примере в методе readFromFile могут возникнуть две искл-
чительные ситуации. Первая связана с тем, что нужный файл не-
доступен. Эта ситуация обработывается внутри метода
readFromFile. Вторая исключительная ситуация может вознкнуть,
если файл имеет неправильный формат. Эта ситуация передается
для обработки наверх.
Механизм потоков - обязательная черта современных операционных
сред. За счет потоков обеспечивается масштабируемость программ-
ных систем, оптимальное использование аппаратных ресурсов, вы-
сокая скорость отклика на запросы пользователей. Поэтому нет
ничего удивительного в том, что в языке Java механизм потоков
представлен с самого начала и в полном объеме.
В языке Java потоки представлены посредством класса Thread, ин-
терфейса Runnable, спецификатора метода synchronized и методов
класса Object wait и notify.
4.1.6.1. Класс Thread и интерфейс Runnable
Поток (thread) представляет собой отдельный поток управления в
пределах процесса. Таким образом, у каждого потока есть начало,
последовательность действий, текущее состояние и конец.
Поток запускается с помощью вызова метода start() класса
Thread. Последовательность действий, выполняемых в рамках пото-
ка, задается в методе run(). Подчеркнем, что метод run() ис-
пользуется только для задания помледовательности действий; явно
вызывать его не только не нужно, но и просто вредно.
Поток заканчивается либо при завершении выполнения метода
run(), либо с помощью явных вызовов методов класса Thread
stop() или destroy(). Возобновить работу завершенного потока
невозможно.
Для временной приостановки работы потока с последующим возоб-
новлением служат методы suspend(), sleep() и yeild().
Обычно поток, приостановленный с помощью метода suspend, возоб-
новляет работу посредством метода resume().
Вызов метода sleep() приводит к приостановке потока на заданное
число миллисекунд.
Вызов метода yeild() означает добровольную уступку процессора
другому потоку; первоначальный поток остается готовым к выпол-
нению.
Java-потоки обладают приоритетами. В спецификациях оговаривает-
ся, что Java-машина реализует вытесняющую многопотоковость. Это
означает, что поток с большим приоритетом может прервать выпол-
нение менее приоритетного потока. Однако, спецификации не тре-
буют наличия разделения времени. Это значит, что для передачи
управления потоку с тем же приоритетом, вообще говоря, требуют-
ся явные действия со стороны первоначального потока - вызов ме-
тодов suspend(), sleep() или yeild().
На рис. 10 представлена диаграмма состояний потоков.
Рис. 10. Диаграмма состояний потоков.
Следующий пример содержит фрагмент одного из многочисленных ва-
риантов решения задачи "производитель/потребитель". Он заимст-
вован из письма, которое написал Mark Tillotson в группу сете-
вых новостей comp.lang.java.
class my_producer extends Thread
{
int items_to_do ;
my_buffer the_buffer ;
my_producer (my_buffer buf, int count)
{ super() ;
the_buffer = buf ;
items_to_do = count ;
}
public void run ()
{
while (items_to_do > 0)
{ System.out.println ("producer to_do = " + items_to_do) ;
Integer item = new Integer (items_to_do*items_to_do) ;
the_buffer.insert (item) ;
items_to_do-- ;
}
System.out.println ("producer exiting") ;
}
}
Данный производитель помещает в буфер квадраты целых чисел.
В приведенном простом примере класс my_producer является нас-
ледником класса Thread, что делает его потоком с последователь-
ностью действий, заданной методом run(). В реальных программах,
как правило, объект должен наследовать у какого-либо предшест-
венника содержательные свойства, а возможность паралелльного
выполнения ему предоставляется интерфейсом Runnable. Этот ин-
терфейс содержит единственный метод - run(). Пример.
1 class SomethingToRun extends BaseRunner implements Runnable {
2 private Thread aThread;
3 public void run () {
// выполняемые действия
. . .
4 }
5 SomethingToRun () {
6 aThread = new Thread (this);
7 aTread.start ();
8 }
9 }
В строке 6 создается новый поток. Аргументом конструктора явля-
ется обект класса SomethingToRun, а, значит, последовательность
выполняемых действий потока будет определяться методом run()
этого класса. Вызов метода start() в строке 7 ставит поток в
очередь готовых для выполнения.
Как и во всякой многопроцессной или многопотоковой среде, в
Java существует проблема синхронизации доступа к разделяемым
ресурсам. Примером такого ресурса является буфер в задаче "про-
изводитель/потребитель".
Для опытных программистов отметим, что модель синхронизации,
принятая в языке Java, опирается на концепцию монитора, предло-
женную в 70-е годы Бринк-Хансеном.
В Java-программах можно выделять критические интервалы, которые
обозначаются ключевым словом synchronized. Если критическим ин-
тервалом является метод, спецификатор synchronized помещается в
его (метода) заголовок. Для превращения произвольной инструкции
(обычно это блок) в критический интервал служит конструкция
synchronized (выражение) инструкция;
где результатом выражения должен быть объект или массив.
Выполнение критического интервала начинается только после полу-
чения потоком монопольного доступа к соответствующему объекту.
До наступления этого момента поток блокируется.
Вызов wait() внутри критического интервала приводит к тому, что
текущий поток уступает монопольное право на критический интер-
вал и приостанавливается до тех пор, пока из какого-либо друго-
го потока не будет сделан вызов notify() или notifyAll(). Хоро-
шей иллюстрацией использования средств синхронизации потоков
является упоминавшаяся выше программа Марка Тиллотсона.
class my_buffer
{
Object [] vec = new Object [8] ;
int ip = 0 ;
int ep = 0 ;
synchronized void insert (Object item)
{
do
{
if (ip-ep < 8)
{ vec [(ip++) & 7] = item ;
if (ip-ep == 1) notify () ; // Уведомить, если буфер был пуст
return ;
}
try wait () ; catch (InterruptedException e) ;
} while (true) ;
}
synchronized Object extract ()
{
do
{
if (ip > ep)
{ Object result = vec [(ep++) & 7] ;
if (ip-ep == 7) notify(); // Уведомить, если буфер был полон
return result ;
}
try wait () ; catch (InterruptedException e) ;
} while (true) ;
}
}
class my_producer extends Thread
{
int items_to_do ;
my_buffer the_buffer ;
my_producer (my_buffer buf, int count)
{ super() ;
the_buffer = buf ;
items_to_do = count ;
}
public void run ()
{
while (items_to_do > 0)
{ System.out.println ("producer to_do = " + items_to_do) ;
Integer item = new Integer (items_to_do*items_to_do) ;
the_buffer.insert (item) ;
items_to_do-- ;
}
System.out.println ("Производитель заканчивает работу") ;
}
}
class my_consumer extends Thread
{
int items_to_do ;
my_buffer the_buffer ;
my_consumer (my_buffer buf, int count)
{ super() ;
the_buffer = buf ;
items_to_do = count ;
}
public void run ()
{
while (items_to_do > 0)
{ System.out.println ("consumer to_do = " + items_to_do) ;
Object item = the_buffer.extract () ;
System.out.println ("consumer got " + item) ;
items_to_do-- ;
}
System.out.println ("Потребитель заканчивает работу") ;
synchronized (this){
notify () ; // Посылаем уведомление о завершении работы
// (см. con.wait() в main())
}
}
}
public class threaded3
{
public static void main (String [] args) throws InterruptedException
{
my_buffer the_buffer = new my_buffer () ;
my_producer prod = new my_producer (the_buffer, 40) ;
my_consumer con = new my_consumer (the_buffer, 40) ;
Thread.currentThread().setPriority (5) ;
prod.setPriority (4) ; // Производитель получает более высокий приоритет
con.setPriority (3) ; // по сравнению с потребителем
prod.start() ;
con.start() ;
synchronized (con)
{
con.wait() ; // Ждем уведомления от производителя об окончании
// его работы
}
System.out.println ("Производитель и потребитель закончили работу") ;
}
}
Приведенная программа написана в очень хорошем, понятном стиле.
Мы прокомментируем лишь один момент. В методах insert() и
extract() класса my_buffer вызов wait() содержится внутри бес-
конечного цикла. Дело в том, что вызов notify() относится к об-
ъекту в целом. "Разбуженный" объект должен проанализировать
свое состояние и решить, что делать дальше. Так, если "заснул"
метод insert(), то после возобновления работы необходимо прове-
рить, что буфер уже не полон и добавление нового элемента стало
возможным. Если это не так, метод insert() заснет вновь.
В принципе, технологический цикл подготовки, трансляции, редак-
тирования внешних связей, тестирования, отладки и выполнения
Java-программ тот же, что и для других интерпретируемых языков
программирования, но с одним существенным отличием - при редак-
тировании внешних связей требуемые компоненты могут доставлять-
ся по сети (рис. xxx).
Рис. xxx. Технологический цикл Java-программы
Важно отметить, однако, что Java-программы могут представать
как бы в двух ипостасях - как самостоятельное приложение и как
аплет, то есть совокупность объектов, выполняющихся в среде
WWW-навигатора.
С точки зрения программиста, аплет и приложение отличаются в
первую очередь точками входа и жизненным циклом.
Приложение в качестве точки входа имеет метод
public static void main (String args[]);
этот метод должен быть определен в том public-классе, который содержится в
файле, выполняемом виртуальной Java-машиной.
В параметр args передается массив строк - параметров командной строки.
Пример: программа, печатающая свои аргументы
public class myTop {
public static void main (String args[]){
int argc = args.length;
for (int i = 0; i < argc; i++)
System.out.println (argc[i]);
}
}
Аплет выполняется в контексте навигатора и его жизненный цикл
определяется следующими методами класса Applet:
public void init () вызывается навигатором при загрузка аплета;
public void start (); вызывается навигатором при показе страницы;
public void stop (); вызывается навигатором, когда тот уходит с Web-страницы;
public void destroy (); этот метод предназначен для освобожденя
ресурсов; аналог деструктора, но не вызывается автоматически; всегда
вызывает stop(); всегда вызывается при выходе из навигатора и при
перезагрузке аплета.
Простейший аплет выглядит так:
1 import java.awt.Graphics;
2 import java.applet.Applet;
3 class SimpleApplet extends Applet {
4 public void paint (Graphics g) {
5 g.drawString ("Hello world!", 10, 10);
6 }
7 }
Метод public void paint (Graphics g) (строки 4-6) определяет,
как аплет перерисовывает себя в тот момент, когда оконный ме-
неджер посылает WWW-навигатору запрос на перерисовку.
Включение аплета в WWW-страницу производится следующим образом.
В языке HTML 2.0 предусмотрены специальные конструкции