Программирование на Java

         

Инициализация массивов


Теперь, когда мы выяснили, как создавать экземпляры массива, рассмотрим, какие значения принимают его элементы.

Если создать массив на основе примитивного числового типа, то изначально после создания все элементы массива имеют значение по умолчанию, то есть 0. Если массив объявлен на основе примитивного типа boolean, то и в этом случае все элементы будут иметь значение по умолчанию false. Выше рассматривался пример инициализации элементов с помощью цикла for.

Рассмотрим создание массива на основе ссылочного типа. Предположим, это будет класс Point. При создании экземпляра массива с применением ключевого слова new не создается ни один объект класса Point, создается лишь один объект массива. Каждый элемент массива будет иметь пустое значение null. В этом можно убедиться на простом примере:

Point p[]=new Point[5]; for (int i=0; i<p.length; i++) { System.out.pritntln(p[i]); }

Результатом будут лишь слова null.

Далее нужно инициализировать элементы массива по отдельности, например, в цикле. Вообще, создание массива длиной n можно рассматривать как заведение n переменных и работать с элементами массива (в последнем примере p[i]) по правилам обычных переменных.

Кроме того, существует и другой способ создания массивов – инициализаторы. В этом случае ключевое слово new не используется, а ставятся фигурные скобки, и в них через запятую перечисляются значения всех элементов массива. Например, для числового массива явная инициализация записывается следующим образом:

int i[]={1, 3, 5}; int j[]={}; // эквивалентно new int[0]

Длина массива вычисляется автоматически, исходя из количества введенных значений. Далее создается массив такой длины и каждому его элементу присваивается указанное значение.

Аналогично можно порождать массивы на основе объектных типов, например:

Point p=new Point(1,3); Point arr[]={p, new Point(2,2), null, p}; // или String sarr[]={"aaa", "bbb", "cde"+"xyz"};

Однако инициализатор нельзя использовать для анонимного создания экземпляров массива, то есть не для инициализации переменной, а, например, для передачи параметров метода или конструктора.

Например:


public class Parent { private String[] values;

protected Parent(String[] s) { values=s; } }

public class Child extends Parent {

public Child(String firstName, String lastName) { super(???); // требуется анонимное создание массива } }

В конструкторе класса Child необходимо осуществить обращение к конструктору родителя и передать в качестве параметра ссылку на массив. Теоретически можно передать null, но это приведет в большинстве случаев к некорректной работе классов. Можно вставить выражение new String[2], но тогда вместо значений firstName и lastName будут переданы пустые строки. Попытка записать {firstName, lastName} приведет к ошибке компиляции, так можно только инициализировать переменные.

Корректное выражение выглядит так:

new String[]{firstName, lastName}

Что является некоторой смесью выражения, создающего массивы с помощью new, и инициализатора. Длина массива определяется количеством указанных значений.


Исходный текст приложения MenuApp


Назад Вперед

Исходный текст приложения MenuApp представлен в листинге 1.

Листинг 1. Файл MenuApp.java



import java.awt.*;

public class MenuApp { public static void main(String args[]) { MainFrameWnd frame = new MainFrameWnd("MenuApp");

frame.setSize( frame.getInsets().left + frame.getInsets().right + 320, frame.getInsets().top + frame.getInsets().bottom + 240);

frame.show(); } }

class MainFrameWnd extends Frame { MenuBar mbMainMenuBar; Menu mnFile; Menu mnHelp;

public MainFrameWnd(String sTitle) { super(sTitle);

setSize(400, 200);

setBackground(Color.yellow); setForeground(Color.black);

setLayout(new FlowLayout());

mbMainMenuBar = new MenuBar();

mnFile = new Menu("File");

mnFile.add("New"); mnFile.add("-"); mnFile.add("Exit");

mnHelp = new Menu("Help");

mnHelp.add("Content"); mnHelp.add("-"); mnHelp.add("About");

mbMainMenuBar.add(mnFile); mbMainMenuBar.add(mnHelp);

setMenuBar(mbMainMenuBar); }

public void paint(Graphics g) { g.setFont(new Font( "Helvetica", Font.PLAIN, 12));

g.drawString("Frame window", 10, 70);

super.paint(g); }

public boolean handleEvent(Event evt) { if(evt.id == Event.WINDOW_DESTROY) { setVisible(false); System.exit(0); return true; } else return super.handleEvent(evt); }

public boolean action(Event evt, Object obj) { MenuItem mnItem;

if(evt.target instanceof MenuItem) { mnItem = (MenuItem)evt.target;

if(obj.equals("Exit")) { System.exit(0); }

else if(obj.equals("New")) { MessageBox mbox;

mbox = new MessageBox( "Item New selected", this, "Dialog from Frame", true); mbox.show(); }

else if(obj.equals("Content")) { MessageBox mbox;

mbox = new MessageBox( "Item Content selected", this, "Dialog from Frame", true); mbox.show(); }

else if(obj.equals("About")) { MessageBox mbox; mbox = new MessageBox( "Item About selected", this, "Dialog from Frame", true); mbox.show(); } else return false; return true; } return false; } }



Использование класса Dialog


Для того чтобы создать свою диалоговую панель, вы должны определить новый класс, унаследовав его от класса Dialog, как это показано ниже:

class MessageBox extends Dialog { . . . public MessageBox(String sMsg, Frame parent, String sTitle, boolean modal) { super(parent, sTitle, modal); . . . resize(200, 100); . . . } }

В этом классе нужно определить конструктор, который вызывает конструктор базового метода класса Dialog и определяет размеры окна диалоговой панели. Кроме того, в конструкторе вы должны создать все необходимые компоненты для размещения внутри диалоговой панели (кнопки, списки, текстовые поля, переключатели и так далее), а также выполнить размещение этих компонент, установив нужный режим размещения.

Для окон класса Dialog устанавливается режим размещения BorderLayout. Если нужен другой режим размещения, необходимо установить его явным образом методом setLayout.

Для отображения окна диалоговой панели необходимо вызвать метод show. Чтобы спрятать диалоговой окно, применяйте метод hide. Метод dispose удаляет окно диалоговой панели окончательно и освобождает все связанные с ним ресурсы.

Когда пользователь пытается уничтожить окно диалоговой панели при помощи органов управления, расположенных в заголовке такого окна, возникает событие Event.WINDOW_DESTROY. Вы должны обработать его, обеспечив удаление окна диалоговой панели вызовом метода dispose, если, конечно, это соответствует логике работы вашей панели.

Назад Вперед



Контакты

О компании

Новости

Вакансии

Правовые аспекты

Условия использования

Торговые марки

Copyright 1994-2005 Sun Microsystems, Inc.

printmenus();

Программные продукты

Рабочие станции и тонкие клиенты

Серверы

Системы хранения данных

Посмотреть все

»

  

Solaris 10

Java 2 Standard Edition

Developer Tools

Top Downloads

New Downloads

Патчи и обновления

Посмотреть все

»

  

Каталог решений

Истории успеха

The Sun Grid

Партнерские программы

Посмотреть все

»

  

Гарантийное обслуживание



Класс MainFrameWnd


Класс MainFrameWnd создан на базе класса Frame:

class MainFrameWnd extends Frame { . . . }

В нем мы определили три поля, конструктор, методы paint, handleEvent и action.



Класс массива


Поскольку массив является объектным типом данных, можно попытаться представить себе, как выглядело бы объявление класса такого типа. На самом деле эти объявления не хранятся в файлах, или еще каком-нибудь формате. Учитывая, что массив может быть объявлен на основе любого типа и иметь произвольную размерность, это физически невыполнимо, да и не требуется. Вместо этого во время выполнения приложения виртуальная машина генерирует эти объявления динамически на основе базового типа и размерности, а затем они хранятся в памяти в виде таких же экземпляров класса Class, как и для любых других типов.

Рассмотрим гипотетическое объявление класса для массива, основанного на неком объектном типе Element.

Объявление класса начинается с перечисления модификаторов, среди которых особую роль играют модификаторы доступа. Класс массива будет иметь такой же уровень доступа, как и базовый тип. То есть если Element объявлен как public-класс, то и массив будет иметь уровень доступа public. Для любого примитивного типа класс массива будет public. Можно также указать модификатор final, поскольку никакой класс не может наследоваться от класса массива.

Затем следует имя класса, на котором можно подробно не останавливаться, т.к. к типу массив обращение идет не по его имени, а по имени базового типа и набору квадратных скобок.

Затем нужно указать родительский класс. Все массивы наследуются напрямую от класса Object. Далее перечисляются интерфейсы, которые реализует класс. Для массива это будут интерфейсы Cloneable и Serializable. Первый из них подробно рассматривается в конце этой лекции, а второй будет описан в следующих лекциях.

Тело класса содержит объявление одного public final поля length типа int. Кроме того, переопределен метод clone() для поддержки интерфейса Cloneable.

Сведем все вышесказанное в формальную запись класса:

[public] class A implements Cloneable, java.io.Serializable { public final int length; // инициализируется при создании public Object clone() { try { return super.clone();} catch (CloneNotSupportedException e) { throw new InternalError(e.getMessage()); } } }

Таким образом, экземпляр типа массив является полноценным объектом, который, в частности, наследует все методы, определенные в классе Object, например, toString(), hashCode() и остальные.

Например:

// результат работы метода toString() System.out.println(new int[3]); System.out.println(new int[3][5]); System.out.println(new String[2]);

// результат работы метода hashCode() System.out.println(new float[2].hashCode());

Результатом выполнения программы будет:

[I@26b249 [[I@82f0db [Ljava.lang.String;@92d342 7051261



Класс Menu


Назад Вперед

Для того чтобы дать вам представление о том, что можно делать с меню, приведем краткое описание класса Menu:



Класс MenuApp


В главном классе приложения MenuApp мы определили только один метод main. Этот метод получает управление при запуске приложения.

Первым делом метод main создает объект класса MainFrameWnd, определенного в нашем приложении:

MainFrameWnd frame = new MainFrameWnd("MenuApp");

Этот класс, созданный на базе класса Frame, определяет поведение главного окна нашего приложения.

На втором шаге метод init настраивает размеры главного окна с учетом размеров внешней рамки и заголовка окна:

frame.setSize(frame.getInsets().left + frame.getInsets().right + 320, frame.getInsets().top + frame.getInsets().bottom + 240);

Поля left и right объекта класса Insets, ссылку на который возвращает метод getInsets, содержат ширину левой и правой части рамки окна, соответственно. Поле top содержит высоту верхней части рамки окна с учетом заголовка, а поле bottom - высоту нижней части рамки окна.

Для отображения окна фрейма мы вызываем метод show, как это показано ниже:

frame.show();



Класс MenuItem


Назад Вперед

Класс MenuItem определяет поведение отдельных элементов меню.

Пользуясь методами класса MenuItem вы можете блокировать или разблокировать отдельные строки меню. Это нужно делать, например, если в данный момент функция, соответствующая строке меню, недоступна или не определена. Вы также можете изменять текстовые строки, соответствующие элементам меню, что может пригодиться для переопределения их назначения.



Класс MessageBox


Для отображения названий выбранных строк меню мы создаем диалоговую панель, определив свой класс MessageBox на базе класса Dialog, как это показано ниже:

class MessageBox extends Dialog { . . . }

В классе MessageBox есть два поля, конструктор, методы handleEvent и action.



Клонирование


Механизм клонирования, как следует из названия, позволяет порождать новые объекты на основе существующего, которые обладали бы точно таким же состоянием, что и исходный. То есть ожидается, что для исходного объекта, представленного ссылкой x, и результата клонирования, возвращаемого методом x.clone(), выражение

x == x.clone()

должно быть истинным, как и выражение

x.clone().getClass() == x.getClass()

Наконец, выражение

x.equals(x.clone())

также верно. Реализация такого метода clone() осложняется целым рядом потенциальных проблем, например:

класс, от которого порожден объект, может иметь разнообразные конструкторы, которые к тому же могут быть недоступны (например, модификатор доступа private);цепочка наследования, которой принадлежит исходный класс, может быть довольно длинной, и каждый родительский класс может иметь свои поля – недоступные, но важные для воссоздания состояния исходного объекта;в зависимости от логики реализации возможна ситуация, когда не все поля должны копироваться для корректного клонирования; одни могут оказаться лишними, другие потребуют дополнительных вычислений или преобразований;возможна ситуация, когда объект нельзя клонировать, дабы не нарушить целостность системы.

Поэтому было реализовано следующее решение.

Класс Object содержит метод clone(). Рассмотрим его объявление:

protected native Object clone() throws CloneNotSupportedException;

Именно он используется для клонирования. Далее возможны два варианта.

Первый вариант: разработчик может в своем классе переопределить этот метод и реализовать его по своему усмотрению, решая перечисленные проблемы так, как того требует логика разрабатываемой системы. Упомянутые условия, которые должны быть истинными для клонированного объекта, не являются обязательными и программист может им не следовать, если это требуется для его класса.

Второй вариант предполагает использование реализации метода clone() в самом классе Object. То, что он объявлен как native, говорит о том, что его реализация предоставляется виртуальной машиной. Естественно, перечисленные трудности легко могут быть преодолены самой JVM, ведь она хранит в памяти все свойства объектов.

При выполнении метода clone() сначала проверяется, можно ли клонировать исходный объект. Если разработчик хочет сделать объекты своего класса доступными для клонирования через Object.clone(), то он должен реализовать в своем классе интерфейс Cloneable. В этом интерфейсе нет ни одного элемента, он служит лишь признаком для виртуальной машины, что объекты могут быть клонированы. Если проверка не выполняется успешно, метод порождает ошибку CloneNotSupportedException.

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

Обратите внимание, что сам класс Object не реализует интерфейс Cloneable, а потому попытка вызова new Object().clone() будет приводить к ошибке времени исполнения. Метод clone() предназначен скорее для использования в наследниках, которые могут обращаться к нему с помощью выражения super.clone(). При этом могут быть сделаны следующие изменения:

модификатор доступа расширен до public;удалено предупреждение об ошибке CloneNotSupportedException;результирующий объект может быть модифицирован любым способом, на усмотрение разработчика.


Напомним, что все массивы реализуют интерфейс Cloneable и, таким образом, доступны для клонирования.

Важно помнить, что все поля клонированного объекта приравниваются, их значения никогда не клонируются. Рассмотрим пример:

public class Test implements Cloneable { Point p; int height;

public Test(int x, int y, int z) { p=new Point(x, y); height=z; }

public static void main(String s[]) { Test t1=new Test(1, 2, 3), t2=null; try { t2=(Test) t1.clone(); } catch (CloneNotSupportedException e) {} t1.p.x=-1; t1.height=-1; System.out.println("t2.p.x=" + t2.p.x + ", t2.height=" + t2.height); } }

Результатом работы программы будет:

t2.p.x=-1, t2.height=3

Из примера видно, что примитивное поле было скопировано и далее существует независимо в исходном и клонированном объектах. Изменение одного не сказывается на другом.

А вот ссылочное поле было скопировано по ссылке, оба объекта ссылаются на один и тот же экземпляр класса Point. Поэтому изменения, происходящие с исходным объектом, сказываются на клонированном.

Этого можно избежать, если переопределить метод clone() в классе Test.

public Object clone() { Test clone=null; try { clone=(Test) super.clone(); } catch (CloneNotSupportedException e) { throw new InternalError(e.getMessage()); } clone.p=(Point)clone.p.clone(); return clone; }

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

Теперь метод main можно упростить:

public static void main(String s[]) { Test t1=new Test(1, 2, 3); Test t2=(Test) t1.clone(); t1.p.x=-1; t1.height=-1; System.out.println("t2.p.x=" + t2.p.x + ", t2.height=" + t2.height); }

Результатом будет:

t2.p.x=1, t2.height=3

То есть теперь все поля исходного и клонированного объектов стали независимыми.

Реализация такого "неглубокого" клонирования в методе Object.clone() необходима, так как в противном случае клонирование второстепенного объекта могло бы привести к огромным затратам ресурсов, ведь этот объект может содержать ссылки на более значимые объекты, а те при клонировании также начали бы копировать свои поля, и так далее. Кроме того, типом поля клонируемого объекта может быть класс, не реализующий Cloneable, что приводило бы к дополнительным проблемам. Как показано в примере, при необходимости дополнительное копирование можно добавить самостоятельно.


Клонирование массивов


Итак, любой массив может быть клонирован. В этом разделе хотелось бы рассмотреть особенности, возникающие из-за того, что Object.clone() копирует только один объект.

Рассмотрим пример:

int a[]={1, 2, 3}; int b[]=(int[])a.clone(); a[0]=0; System.out.println(b[0]);

Результатом будет единица, что вполне очевидно, так как весь массив представлен одним объектом, который не будет зависеть от своей копии. Усложняем пример:

int a[][]={{1, 2}, {3}}; int b[][]=(int[][]) a.clone();

if (...) { // первый вариант: a[0]=new int[]{0}; System.out.println(b[0][0]); } else { // второй вариант: a[0][0]=0; System.out.println(b[0][0]); }

Разберем, что будет происходить в этих двух случаях. Начнем с того, что в первой строке создается двухмерный массив, состоящий из двух одномерных. Итого три объекта. Затем, на следующей строке при клонировании будет создан новый двухмерный массив, содержащий ссылки на те же самые одномерные массивы.

Теперь несложно предсказать результат обоих вариантов. В первом случае в исходном массиве меняется ссылка, хранящаяся в первом элементе, что не принесет никаких изменений для клонированного объекта. На консоли появится 1.

Во втором случае модифицируется существующий массив, что скажется на обоих двухмерных массивах. На консоли появится 0.

Обратите внимание, что если из примера убрать условие if-else, так, чтобы отрабатывал первый вариант, а затем второй, то результатом будет опять 1, поскольку в части второго варианта модифицироваться будет уже новый массив, порожденный в части первого варианта.

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



Конструктор класса MainFrameWnd


В качестве единственного параметра конструктору класса MainFrameWnd передается заголовок создаваемого окна. В первой исполняемой строке наш конструктор вызывает конструктор из базового класса, передавая ему строку заголовка через параметр:

public MainFrameWnd(String sTitle) { super(sTitle); . . . }

Далее конструктор определяет размеры окна, вызывая для него метод setSize:

setSize(400, 200);

Затем мы устанавливаем для нашего окна желтый цвет фона и черный цвет изображения:

setBackground(Color.yellow); setForeground(Color.black);

По умолчанию для окон класса Frame устанавливается режим добавления компонент BorderLayout. Мы изменяем этот режим на FlowLayout, вызывая метод setLayout:

setLayout(new FlowLayout());

Далее конструктор приступает к формированию главного меню окна. Это меню создается как объект класса MenuBar:

mbMainMenuBar = new MenuBar();

Затем мы создаем и наполняем меню "File":

mnFile = new Menu("File");

mnFile.add("New"); mnFile.add("-"); mnFile.add("Exit");

Это меню создается на базе класса Menu. Обратите внимание, что между строками New и File расположен разделитель.

Аналогичным образом мы добавляем в главное меню другое меню - "Help":

mnHelp = new Menu("Help");

mnHelp.add("Content"); mnHelp.add("-"); mnHelp.add("About");

После своего окончательного формирования меню "File" и "Help" добавляются в главное меню окна mbMainMenuBar:

mbMainMenuBar.add(mnFile); mbMainMenuBar.add(mnHelp);

И, наконец, когда главное меню будет сформировано, оно подключается к окну вызовом метода setMenuBar, как это показано ниже:

setMenuBar(mbMainMenuBar);



Конструктор класса MessageBox


Наш конструктор создает диалоговую панель с заданным сообщением внутри нее. Ссылка на строку сообщения передается конструктору через первый параметр. Остальные параметры используются конструктором базового класса Dialog для создания диалоговой панели:

super(parent, sTitle, modal);

После вызова конструктора из базового класса наш конструктор устанавливает размеры окна созданной диалоговой панели, вызывая метод resize:

resize(200, 100);

Отменяя установленный по умолчанию режим размещения компонент BorderLayout, конструктор устанавливает режим GridLayout:

setLayout(new GridLayout(2, 1));

Окно диалоговой панели при этом разделяется на две части по горизонтали. В верхнюю часть добавляется текстовое поле для отображения сообщения, в нижнюю - кнопка OK:

lbMsg = new Label(sMsg, Label.CENTER); add(lbMsg); btnOK = new Button("OK"); add(btnOK);



Конструкторы


Для класса Frame определено два конструктора:

Создание окна без заголовка

public Frame();

Создание окна с заголовоком

public Frame(String title);


Создание меню с заданным названием

public Menu(String label);

Создание меню с заданным названием,которое может оставаться на экране после того как пользователь отпустил клавишу мыши

public Menu(String label, boolean tearOff);




Создание диалоговой панели без заголовка

public Dialog(Frame parent, boolean modal);

Создание диалоговой панели с заголовком

public Dialog(Frame parent, String title, boolean modal);



Массивы как тип данных в Java


В отличие от обычных переменных, которые хранят только одно значение, массивы (arrays) используются для хранения целого набора значений. Количество значений в массиве называется его длиной, сами значения – элементами массива. Значений может не быть вовсе, в этом случае массив считается пустым, а его длина равной нулю.

Элементы не имеют имен, доступ к ним осуществляется по номеру индекса. Если массив имеет длину n, отличную от нуля, то корректными значениями индекса являются числа от 0 до n-1. Все значения имеют одинаковый тип и говорится, что массив основан на этом базовом типе. Массивы могут быть основаны как на примитивных типах (например, для хранения числовых значений 100 измерений), так и на ссылочных (например, если нужно хранить описание 100 автомобилей в гараже в виде экземпляров класса Car).

Сразу оговоримся, что в Java массив символов char[] и класс String являются различными типами. Их значения могут легко конвертироваться друг в друга с помощью специальных методов, но все же они не относятся к идентичным типам.

Как уже говорилось, массивы в Java являются объектами (примитивных типов в Java всего восемь и их количество не меняется), их тип напрямую наследуется от класса Object, поэтому все элементы данного класса доступны у объектов-массивов.

Базовый тип также может быть массивом. Таким образом конструируется массив массивов, или многомерный массив.

Работа с любым массивом включает обычные операции, уже описанные для других типов, - объявление, инициализация и т.д. Начнем последовательно изучать их в приложении к массивам.



Меню в окне класса Frame


Назад Вперед

Как мы уже говорили, окно класса Frame может иметь главное меню (Menu Bar) или, как еще говорят, строку меню. Главное меню создается на базе класса MenuBar, краткое описание которого приведено ниже.




Этот метод обрабатывает события, возникающие при выборе строка из меню.

В начале своей работы метод action проверяет, действительно ли событие вызвано меню:

MenuItem mnItem;

if(evt.target instanceof MenuItem) { . . . } return false;

Если это так, в поле mnItem сохраняется ссылка на элемент меню, вызвавший событие:

mnItem = (MenuItem)evt.target;

Тем не менее, для определения строки, выбранной пользователем, нам достаточно проанализировать второй параметр метода action:

if(obj.equals("Exit")) { System.exit(0); }

else if(obj.equals("New")) { MessageBox mbox;

mbox = new MessageBox( "Item New selected", this, "Dialog from Frame", true); mbox.show(); }

else if(obj.equals("Content")) { . . . }

else if(obj.equals("About")) { . . . }

В данном случае второй параметр метода action будет представлять собой ссылку на строку, выбранную из меню, поэтому для определения выбранной строки мы можем выполнить простое сравнение методом equals.

Если пользователь выбрал из меню File строку Exit, мы вызываем метод System.exit, предназначенный для завершения работы виртуальной машины Java.

В том случае когда пользователь выбирает любую другую строку из меню, метод action создает диалоговую панель на базе определенного нами класса MessageBox. В этой диалоговой панели отображаетя название выбранной строки меню.

Заметим, что сразу после создания конструктором диалоговая панель не появляется на экране. Мы отображаем ее, вызывая метод show.



Метод action класса MessageBox


Если пользователь нажимает кнопку OK, расположенную в окне диалоговой панели, метод action вызывает для панели метод dispose, удаляя эту панель с экрана и из памяти:

if(evt.target.equals(btnOK)) { dispose(); }

Назад Вперед



Контакты

О компании

Новости

Вакансии

Правовые аспекты

Условия использования

Торговые марки

Copyright 1994-2005 Sun Microsystems, Inc.

printmenus();

Программные продукты

Рабочие станции и тонкие клиенты

Серверы

Системы хранения данных

Посмотреть все

»

  

Solaris 10

Java 2 Standard Edition

Developer Tools

Top Downloads

New Downloads

Патчи и обновления

Посмотреть все

»

  

Каталог решений

Истории успеха

The Sun Grid

Партнерские программы

Посмотреть все

»

  

Гарантийное обслуживание

Программы SunSpectrum

Консалтинг

Услуги инсталляции

Поддержка ПО

Посмотреть все

»

  

Описание курсов

Сертификация

Авторизованные учебные центры

Посмотреть все

»

  

Проекты

События

Lab Downloads

Посмотреть все

»

  



Метод handleEvent


Для того чтобы определить реакцию окна на попытку пользователя закрыть окно с помощью органов управления, расположенных в заголовке окна, или другим способом, мы переопределили метод handleEvent.

При получении кода события Event.WINDOW_DESTROY (удаление окна) мы скрываем окно, вызывая метод setVisible с параметром false.

Затем с помощью статического метода exit класса System мы завершаем работу интерпретатора:

public boolean handleEvent(Event evt) { if(evt.id == Event.WINDOW_DESTROY) { setVisible(false); System.exit(0); return true; } else return super.handleEvent(evt); }



Метод handleEvent класса MessageBox


Когда пользователь пытается закрыть окно диалоговой панели, например, сделав двойной щелчок левой клавишей мыши по системному меню или одиночный щелчок по кнопке удаления окна, возникает событие Event.WINDOW_DESTROY. Мы его обрабатываем следующим образом:

if(evt.id == Event.WINDOW_DESTROY) { dispose(); return true; } else return super.handleEvent(evt);

Вызывая метод dispose, мы удаляем окно диалоговой панели и освобождаем все связанные с ним ресурсы.



Метод paint


Метод paint получает в качестве параметра ссылку на контекст отображения, пригодный для рисования в нашем окне. Пользуясь этим контекстом, мы устанавливаем шрифт текста и рисуем текстовую строку. Затем мы вызываем метод paint из базового класса Frame, на основе которого создан наш класс MainFrameWnd:

public void paint(Graphics g) { g.setFont(new Font( "Helvetica", Font.PLAIN, 12));

g.drawString("Frame window", 10, 70);

super.paint(g); }



Методы


addNotify

Вызов метода createFrame

public void addNotify();

dispose

Удаление окна и освобождение связанных с ним ресурсов

public void dispose();

getCursorType

Определение типа курсора

public int getCursorType();

getIconImage

Получение пиктограммы, установленной для окна

public Image getIconImage();

getMenuBar

Получение ссылки на главное меню

public MenuBar getMenuBar();

getTitle

Получение заголовка окна

public String getTitle();

isResizable

Определение возможности изменения размеров окна пользователем

public boolean isResizable();

paramString

Получение строки параметров

protected String paramString();

remove

Удаление компоненты меню

public void remove(MenuComponent m);

setCursor

Установка типа курсора

public void setCursor(int cursorType);

setIconImage

Установка пиктограммы

public void setIconImage(Image image);

setMenuBar

Установка главного меню

public void setMenuBar(MenuBar mb);

setResizable

Включение или выключение возомжности изменения размеров окна

public void setResizable(boolean resizable);

setTitle

Установка заголовка окна

public void setTitle(String title);


add

Добавление меню в главное меню окна

public Menu add(Menu m);

addNotify

Вызов метода createMenuBar

public void addNotify();

countMenus

Определение количества меню, добавленных в главное меню

public int countMenus();

getHelpMenu

Получение ссылки на меню Help

public Menu getHelpMenu();

getMenu

Получение ссылки на меню с заданным номером

public Menu getMenu(int i);

remove

Удаление меню с заданным номером из главного меню

public void remove(int index);

Удаление компоненты меню

public void remove(MenuComponent m);

removeNotify

Извещение об удалении меню

public void removeNotify();

setHelpMenu

Установка меню Help

public void setHelpMenu(Menu m);




add

Добавление элемента меню

public MenuItem add(MenuItem mi);

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

public void add(String label);

addNotify

Вызов метода createMenu

public void addNotify();

addSeparator

Добавление разделителя в меню

public void addSeparator();

countItems

Определение количества строк в меню

public int countItems();

getItem

Получение ссылки на элемент меню с заданным номером

public MenuItem getItem(int index);

isTearOff

Проверка, остается ли меню на экране после того как пользователь отпустил клавишу мыши

public boolean isTearOff();

remove

Удаление заданного элемента меню

public void remove(int index);

Удаление заданной компоненты меню

public void remove(MenuComponent item);

removeNotify

Извещение об удалении меню

public void removeNotify();




addNotify

Вызов метода createMenuItem

public void addNotify();

disable

Блокирование элемента меню

public void disable();

enable

Разблокирование элемента меню

public void enable();

Блокирование или разблокирование элемента меню

public void enable(boolean cond);

getLabel

Получение текстовой строки меню

public String getLabel();

isEnabled

Проверка, является ли элемент меню заблокированным

public boolean isEnabled();

paramString

Получение строки параметров

public String paramString();

setLabel

Установка текстовой строки для элемента меню

public void setLabel(String label);

Назад Вперед



Контакты

О компании

Новости

Вакансии

Правовые аспекты

Условия использования

Торговые марки

Copyright 1994-2005 Sun Microsystems, Inc.

printmenus();

Программные продукты

Рабочие станции и тонкие клиенты

Серверы

Системы хранения данных

Посмотреть все

»

  

Solaris 10

Java 2 Standard Edition

Developer Tools

Top Downloads

New Downloads

Патчи и обновления

Посмотреть все

»

  

Каталог решений

Истории успеха

The Sun Grid

Партнерские программы

Посмотреть все

»

  

Гарантийное обслуживание

Программы SunSpectrum

Консалтинг

Услуги инсталляции

Поддержка ПО

Посмотреть все

»

  

Описание курсов

Сертификация

Авторизованные учебные центры

Посмотреть все

»

  

Проекты

События

Lab Downloads

Посмотреть все

»

  




addNotify

Вызов метода createDialog

public void addNotify();

getTitle

Получение строки заголовка диалоговой панели

public String getTitle();

isModal

Определение, является ли диалоговая панель модальной

public boolean isModal();

isResizable

Определение возможности изменения размеров окна диалоговой панели

public boolean isResizable();

paramString

Получение строки параметров

protected String paramString();

setResizable

Включение или выключение возможности изменения размеров окна диалоговой панели

public void setResizable(boolean resizable);

setTitle

Установка заголовка диалоговой панели

public void setTitle(String title);



Многомерные массивы


Теперь перейдем к рассмотрению многомерных массивов. Так, в следующем примере

int i[][]=new int[3][5];

переменная i ссылается на двумерный массив, который можно представить себе в виде таблицы 3х5. Суммарно в таком массиве содержится 15 элементов, к которым можно обращаться через комбинацию индексов от (0, 0) до (2, 4). Пример заполнения двумерного массива через цикл:

int pithagor_table[][]=new int[5][5]; for (int i=0; i<5; i++) { for (int j=0; j<5; j++) { pithagor_table[i][j]=i*j; System.out.print(pithagor_table[i][j] + "\t"); } System.out.println(); }

Результатом выполнения программы будет:

0 0 0 0 0 0 1 2 3 4 0 2 4 6 8 0 3 6 9 12 0 4 8 12 16

Однако такой взгляд на двумерные и многомерные массивы является неполным. Более точный подход заключается в том, что в Java нет двумерных, и вообще многомерных массивов, а есть массивы, базовыми типами которых являются также массивы. Например, тип int[] означает "массив чисел", а int[][] означает "массив массивов чисел". Поясним такую точку зрения.

Если создать двумерный массив и определить переменную x, которая на него ссылается, то, используя x и два числа в паре квадратных скобок каждое (например, x[0][0]), можно обратиться к любому элементу двумерного массива. Но в то же время, используя x и одно число в паре квадратных скобок, можно обратиться к одномерному массиву, который является элементом двумерного массива. Его можно проинициализировать новым массивом с некоторой другой длиной и таблица перестанет быть прямоугольной – она примет произвольную форму. В частности, можно одному из одномерных массивов присвоить даже значение null.

int x[][]=new int[3][5]; // прямоугольная таблица x[0]=new int[7]; x[1]=new int[0]; x[2]=null;

После таких операций массив, на который ссылается переменная x, назвать прямоугольным никак нельзя. Зато хорошо видно, что это просто набор одномерных массивов или значений null.

Полезно подсчитать, сколько объектов порождается выражением new int[3][5]. Правильный подсчет таков: создается один массив массивов (один объект) и три массива чисел, каждый длиной 5 (три объекта). Итого, четыре объекта.

В рассмотренном примере три из них (массивы чисел) были тут же переопределены новыми значениями. Для таких случаев полезно использовать упрощенную форму выражения создания массивов:


int x[][]=new int[3][];

Такая запись порождает один объект – массив массивов – и заполняет его значениями null. Теперь понятно, что и в этом, и в предыдущем варианте выражение x.length возвращает значение 3 – длину массива массивов. Далее можно с помощью выражений x[i].length узнать длину каждого вложенного массива чисел, при условии, что i неотрицательно и меньше x.length, а также x[i] не равно null. Иначе будут возникать ошибки во время выполнения программы.

Вообще, при создании многомерных массивов с помощью new необходимо указывать все пары квадратных скобок, соответственно количеству измерений. Но заполненной обязательно должна быть лишь крайняя левая пара, это значение задаст длину верхнего массива массивов. Если заполнить следующую пару, то этот массив заполнится не значениями по умолчанию null, а новыми созданными массивами с меньшей на единицу размерностью. Если заполнена вторая пара скобок, то можно заполнить третью, и так далее.

Аналогично, для создания многомерных массивов можно использовать инициализаторы. В этом случае применяется столько вложенных фигурных скобок, сколько требуется:

int i[][] = {{1,2}, null, {3}, {}};

В этом примере порождается четыре объекта. Это, во-первых, массив массивов длиной 4, а во-вторых, три массива чисел с длинами 2, 1, 0, соответственно.

Все рассмотренные примеры и утверждения одинаково верны для многомерных массивов, основанных как на примитивных, так и на ссылочных типах.


Объявление массивов


В качестве примера рассмотрим объявление переменной типа "массив, основанный на примитивном типе int":

int a[];

Как мы видим, сначала указывается базовый тип. Затем идет имя переменной, а пара квадратных скобок указывает на то, что используемый тип является именно массивом. Также допустима запись:

int[] a;

Количество пар квадратных скобок указывает на размерность массива. Для многомерных массивов допускается смешанная запись:

int[] a[];

Переменная a имеет тип "двумерный массив, основанный на int". Аналогично объявляются массивы с базовым объектным типом:

Point p, p1[], p2[][];

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

int a[]=new int[5]; Point[] p = new Point[10];

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

int array[]=new int[5]; for (int i=0; i<5; i++) { array[i]=i*i; } for (int j=0; j<5; j++) { System.out.println(j+"*"+j+"="+array[j]); }

Результатом выполнения программы будет:

0*0=0 1*1=1 2*2=4 3*3=9 4*4=16

И далее появится ошибка времени исполнения, так как индекс превысит максимально возможное для такого массива значение. Проверка, не выходит ли индекс за допустимые пределы, происходит только во время исполнения программы, т.е. компилятор не пытается выявить эту ошибку даже в таких явных случаях, как:

int i[]=new int[5]; i[-2]=0; // ошибка! индекс не может // быть отрицательным


Ошибка возникнет только на этапе выполнения программы.

Хотя при создании массива необходимо указывать его длину, это значение не входит в определение типа массива, важна лишь размерность. Таким образом, одна переменная может ссылаться на массивы разной длины:

int i[]=new int[5]; ... i=new int[7]; // переменная та же, длина // массива другая

Однако для объекта массива длина обязательно должна указываться при создании и уже никак не может быть изменена. В последнем примере для присвоения переменной ссылки на массив большей длины потребовалось создать новый экземпляр.

Поскольку для экземпляра массива длина является постоянной характеристикой, для всех массивов существует специальное поле length, позволяющее узнать ее значение. Например:

Point p[]=new Point[5]; for (int i=0; i<p.length; i++) { p[i]=new Point(i, i); }

Значение индекса массива всегда имеет тип int. При обращении к элементу можно также использовать byte, short или char, поскольку эти типы автоматически расширяются до int. Попытка задействовать long приведет к ошибке компиляции.

Соответственно, и поле length имеет тип int, а теоретическая максимально возможная длина массива равняется 231-1, то есть немногим больше 2 млрд.

Продолжая рассматривать тип массива, подчеркнем, что в качестве базового типа может использоваться любой тип Java, в том числе:

интерфейсы. В таком случае элементы массива могут иметь значение null или ссылаться на объекты любого класса, реализующего этот интерфейс;абстрактные классы. В этом случае элементы массива могут иметь значение null или ссылаться на объекты любого неабстрактного класса-наследника.

Поскольку массив является объектным типом данных, его значения могут быть приведены к типу Object или, что то же самое, присвоены переменной типа Object. Например,

Object o = new int[4];

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

Object arr[] = new Object[3]; arr[0]=new Object(); arr[1]=null; arr[2]=arr; // Элемент ссылается // на весь массив!


Окна и диалоговые панели


Назад Вперед

До сих пор мы рисовали только в окне аплета или в окнах панелей, расположенных внутри окна аплета. Однако есть и другая возможность - приложения Java, полноценные и аплеты, могут создавать обычные перекрывающиеся окна, такие, например, как окно браузера. Эти окна могут иметь меню (в отличие от окон аплетов). Пользователь может изменять размер таких окон при помощи мыши, перемещая рамку окна.

В составе библиотеки классов AWT имеется несколько классов, предназначенных для работы с окнами. Это класс Window, который произошел от класса Container, и его дочерние классы - Frame, Dialog и FileDialog (рис. 1).

Рис. 1. Иерархия классов, предназначенных для создания окон

Окно, созданное на базе класса Frame, больше всего похоже на главное окно обычного приложения Windows. Оно может иметь главное меню, для него можно устанавливать форму курсора. Внутри такого окна можно рисовать. Так как окно класса Frame (так же как и другие окна AWT) произошли от класса Container, вы можете добавлять в них различные компоненты и панели, как мы это делали с окнами аплетов и панелей.

На базе класса Dialog создаются окна диалоговых панелей, очень похожих на обычные диалоговые панели Windows. Такие панели не могут иметь меню и обычно предназначены для запроса какой-либо информации у пользователя.

Класс FileDialog предназначен для создания диалоговых панелей диалоговые панели, с помощью которых можно выбирать файлы на локальных дисках компьютера.

Что же касается класса Window, то непосредственно этот класс редко применяется для создания окон, так как классы Frame, Dialog и FileDialog более удобны и обеспечивают все необходимые возможности.

Назад Вперед



Контакты

О компании

Новости

Вакансии

Правовые аспекты

Условия использования

Торговые марки

Copyright 1994-2005 Sun Microsystems, Inc.

printmenus();

Программные продукты

Рабочие станции и тонкие клиенты

Серверы

Системы хранения данных

Посмотреть все

»

  

Solaris 10

Java 2 Standard Edition


Developer Tools

Top Downloads

New Downloads

Патчи и обновления

Посмотреть все
»

  
Каталог решений

Истории успеха

The Sun Grid

Партнерские программы

Посмотреть все
»

  
Гарантийное обслуживание

Программы SunSpectrum

Консалтинг

Услуги инсталляции

Поддержка ПО

Посмотреть все
»

  
Описание курсов

Сертификация

Авторизованные учебные центры

Посмотреть все
»

  
Проекты

События

Lab Downloads

Посмотреть все
»

  



Окна класса Frame


Назад Вперед

Ниже мы привели краткое описание класса Frame. Так как этот класс реализует интерфейс java.awt.MenuContainer, окно класса Frame может содержать меню.



Описание исходного текста приложения MenuApp


Назад Вперед

Как мы уже говорили, приложение MenuApp работает автономно. Поэтому мы импортируем только класс java.awt.*, необходимый для работы с окнами:

import java.awt.*;

В нашем приложении определено три класса - MenuApp, MainFrameWnd и MessageBox.



Ошибка ArrayStoreException


Преобразование между типами массивов, основанных на ссылочных типах, может стать причиной одной довольно неочевидной ошибки.

Рассмотрим пример:

Child c[] = new Child[5]; Parent p[]=c; p[0]=new Parent();

С точки зрения компилятора код совершенно корректен. Преобразование во второй строке допустимо. В третьей строке элементу массива типа Parent присваивается значение того же типа.

Однако при выполнении такой программы возникнет ошибка. Нельзя забывать, что преобразование не меняет объект, изменяется лишь способ доступа к нему. В свою очередь, объект всегда "помнит", от какого типа он был порожден. С учетом этих замечаний становится ясно, что в третьей строке делается попытка добавить в массив Child значение типа Parent, что некорректно.

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

c[0].onlyChildMethod();

где метод onlyChildMethod() определен только в классе Child. Данное обращение совершенно корректно, а значит, недопустима ситуация, когда элемент c[0] ссылается на объект, несовместимый с Child.

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

Может сложиться впечатление, что разобранная ситуация является надуманной,– зачем преобразовывать массив и тут же задавать для него неверное значение? Однако преобразование при присвоении значений является лишь примером. Рассмотрим объявление метода:

public void process(Parent[] p) { if (p!=null && p.length>0) { p[0]=new Parent(); } }

Метод выглядит абсолютно корректным, все потенциально ошибочные ситуации проверяются if-выражением. Однако следующий вызов этого метода все равно приводит к ошибке:

process(new Child[3]));

И это будет как раз ошибка ArrayStoreException.



Переменные типа массив и их значения


Завершим описание взаимосвязи типа переменной и типа значений, которые она может хранить.

Как обычно, массивы, основанные на простых и ссылочных типах, мы описываем раздельно.

Переменная типа массив примитивных величин может хранить значения только точно такого же типа, либо null.

Переменная типа "массив ссылочных величин" может хранить следующие значения:

null;значения точно такого же типа, что и тип переменной;все значения типа массив, основанный на типе, приводимом к базовому типу исходного массива.

Все эти утверждения непосредственно следуют из рассмотренных выше особенностей приведения типов массивов.

Еще раз напомним про исключительный класс Object. Переменные такого типа могут ссылаться на любые объекты, порожденные как от классов, так и от массивов.

Сведем все эти утверждения в таблицу.

Таблица Табл. 9.1.. Тип переменной и тип ее значения.

Тип переменнойДопустимые типы ее значения
Массив простых чисел

nullв точности совпадающий с типом переменной

Массив ссылочных значений

nullсовпадающий с типом переменноймассивы ссылочных значений, удовлетворяющих следующему условию: если тип переменной – массив на основе типа A, то значение типа массив на основе типа B допустимо тогда и только тогда, когда B приводимо к A

Object

nullлюбой ссылочный, включая массивы



Поля


С помощью полей класса Frame вы можете задавать для своего окна различные типы курсоров:

public final static int CROSSHAIR_CURSOR; public final static int DEFAULT_CURSOR; public final static int E_RESIZE_CURSOR; public final static int HAND_CURSOR; public final static int MOVE_CURSOR; public final static int N_RESIZE_CURSOR; public final static int NE_RESIZE_CURSOR; public final static int NW_RESIZE_CURSOR; public final static int S_RESIZE_CURSOR; public final static int SE_RESIZE_CURSOR; public final static int SW_RESIZE_CURSOR; public final static int TEXT_CURSOR; public final static int W_RESIZE_CURSOR; public final static int WAIT_CURSOR;



Поля класса MainFrameWnd


Поле mbMainMenuBar предназанчено для хранения ссылки на главное меню приложения, создаваемое как объект класса MenuBar:

MenuBar mbMainMenuBar;

Поля mnFile и mnHelp хранят ссылки на меню File и Help, соответственно:

Menu mnFile; Menu mnHelp;

Данные меню создаются на базе класса Menu.



Поля класса MessageBox


Внутри диалоговой панели мы расположили текстовое поле класса Label, предназначенное для отображения сообщения, и кнопку с надписью OK, с помощью которой можно завершить работу диалоговой панели.

Ссылка на текстовое поле хранится в поле lbMsg, на кнопку - в поле btnOK.



Преобразование типов для массивов


Теперь, когда массив введен как полноценный тип данных в Java, рассмотрим, какое влияние он окажет на преобразование типов.

Ранее подробно рассматривались переходы между примитивными и обычными (не являющимися массивами) ссылочными типами. Хотя массивы являются объектными типами, их также будет полезно разделить по базовому типу на две группы – основанные на примитивном или ссылочном типе.

Имейте в виду, что переходы между массивами и примитивными типами являются запрещенными. Преобразования между массивами и другими объектными типами возможны только для класса Object и интерфейсов Cloneable и Serializable. Массив всегда можно привести к этим трем типам, обратный же переход является сужением и должен производиться явным образом по усмотрению разработчика. Таким образом, интерес представляют только переходы между разными типами массивов. Очевидно, что массив, основанный на примитивном типе, принципиально нельзя преобразовать к типу массива, основанному на ссылочном типе, и наоборот.

Пока не будем останавливаться на этом подробно, однако заметим, что преобразования между типами массивов, основанных на различных примитивных типах, невозможны ни при каких условиях.

Для ссылочных же типов такого строгого правила нет. Например, если создать экземпляр массива, основанного на типе Child, то ссылку на него можно привести к типу массива, основанного на типе Parent.

Child c[] = new Child[3]; Parent p[] = c;

Вообще, существует универсальное правило: массив, основанный на типе A, можно привести к массиву, основанному на типе B, если сам тип A приводится к типу B.

// если допустимо такое приведение: B b = (B) new A(); // то допустимо и приведение массивов: B b[]=(B[]) new A[3];

Применяя это правило рекурсивно, можно преобразовывать многомерные массивы. Например, массив Child[][] можно привести к Parent[][], так как их базовые типы приводимы (Child[] к Parent[]) также на основе этого правила (поскольку базовые типы Child и Parent приводимы в силу правил наследования).

Как обычно, расширения можно проводить неявно (как в предыдущем примере), а сужения – только явным приведением.

Вернемся к массивам, основанным на примитивном типе. Невозможность их участия в преобразованиях типов связана, конечно, с различиями между простыми и ссылочными типами данных. Поскольку элементами объектных массивов являются ссылки, они легко могут участвовать в приведении. Напротив, элементы простых типов действительно хранят числовые или булевские значения. Предположим, такое преобразование осуществимо:

// пример вызовет ошибку компиляции byte b[]={1, 2, 3}; int i[]=b;

В таком случае, элементы b[0] и i[0] хранили бы значения разных типов. Стало быть, преобразование потребовало бы копирования с одновременным преобразованием типа всех элементов исходного массива. В результате был бы создан новый массив, элементы которого равнялись бы по значению элементам исходного массива.

Но преобразование типа не может порождать новые объекты. Такие операции должны выполняться только явным образом с применением ключевого слова new. По этой причине преобразования типов массивов, основанных на примитивных типах, запрещены.

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



Приложение MenuApp


Назад Вперед

Автономное приложение MenuApp, работающее под управлением интерпертатора Java, демонстрирует способы создания меню. В его окне (рис. 1) имеется панель с меню File и Help.

Рис. 1. Главное окно автономного приложения MenuApp

В меню File мы добавили строки New и Exit, а также разделитель в виде горизонтальной линии (рис. 2).

Рис. 2. Меню File

Меню Help (рис. 3) содержит строки Content и About. Между ними также имеется разделительная линия.

Рис. 3. Меню Help

Если выбрать любую строку, кроме строки Exit из меню File, на экране появится диалоговая панель с названием выбранной строки и кнопкой OK (рис. 4).

Рис. 4. Диалоговая панель, которая появляется при выборе строки New из меню File

Выбор строки Exit из меню File приводит к завершению работы приложения MenuApp.

Назад Вперед



Контакты

О компании

Новости

Вакансии

Правовые аспекты

Условия использования

Торговые марки

Copyright 1994-2005 Sun Microsystems, Inc.

printmenus();

Программные продукты

Рабочие станции и тонкие клиенты

Серверы

Системы хранения данных

Посмотреть все

»

  

Solaris 10

Java 2 Standard Edition

Developer Tools

Top Downloads

New Downloads

Патчи и обновления

Посмотреть все

»

  

Каталог решений

Истории успеха

The Sun Grid

Партнерские программы

Посмотреть все

»

  

Гарантийное обслуживание

Программы SunSpectrum

Консалтинг

Услуги инсталляции

Поддержка ПО

Посмотреть все

»

  

Описание курсов

Сертификация

Авторизованные учебные центры

Посмотреть все

»

  

Проекты

События

Lab Downloads

Посмотреть все

»

  



Применение класса Frame


Для того чтобы создать свое окно на базе класса Frame, вы должны определить свой класс, унаследовав его от класса Frame следующим образом:

class MainFrameWnd extends Frame { . . . public MainFrameWnd(String sTitle) { super(sTitle); . . . resize(400, 200); } . . . }

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

Обратите также внимание на вызов метода resize. Этот вызов необходим для задания размеров окна.

В конструкторе вы можете определить различные параметры создаваемого вами окна, например, указать форму курсора, пиктограмму, представляющую окно, задать меню, определить возможность изменения размеров окна и так далее. Мы остановимся подробнее на процедуре добавления меню к окну класса Frame, так как она требует пояснений. С изменением других характеристик окна вы справитесь самостоятельно.

При создании окна классов Frame и Dialog для них устанавливается режим размещения BorderLayout. Если вам нужен другой режим размещения, необходимо установить его явным образом.

Кроме того, созданное окно появится на экране только после вызова для него метода show.

Убрать окно с экрана вы можете методом hide. Этот метод прячет окно, но оставляет в памяти все связанные с ним ресурсы, поэтому вы сможете вновь отобразить спрятанное окно, вызвав метод show.

В отличие от метода hide, метод dispose удаляет окно и освобождает все связанные с ним ресурсы. Этот метод применяется для окончательного удаления окна с экрана и из памяти.

Еще одно замечание касается обработки операции уничтожения окна при помощи двойного щелчка левой клавиши мыши по системному меню окна или при помощи кнопки уничтожения окна, расположенной в правой части заголовка.



Работа с классом Menu


Метод addSeparator используется для добавления в меню разделительной строки. Аналогичный результат достигается и при добавлении в меню стоки "-":

mnHelp.add("-");

Заметим, что вы можете просто добавлять в меню строки по их названию, пользуясь методом add(String label), либо добавлять в меню элементы класса MenuItem, вызывая метод add(MenuItem mi).

Назад Вперед



Контакты

О компании

Новости

Вакансии

Правовые аспекты

Условия использования

Торговые марки

Copyright 1994-2005 Sun Microsystems, Inc.

printmenus();

Программные продукты

Рабочие станции и тонкие клиенты

Серверы

Системы хранения данных

Посмотреть все

»

  

Solaris 10

Java 2 Standard Edition

Developer Tools

Top Downloads

New Downloads

Патчи и обновления

Посмотреть все

»

  

Каталог решений

Истории успеха

The Sun Grid

Партнерские программы

Посмотреть все

»

  

Гарантийное обслуживание

Программы SunSpectrum

Консалтинг

Услуги инсталляции

Поддержка ПО

Посмотреть все

»

  

Описание курсов

Сертификация

Авторизованные учебные центры

Посмотреть все

»

  

Проекты

События

Lab Downloads

Посмотреть все

»

  



Работа с классом MenuBar


Для формирования главного меню окна вы должны создать объект класса MenuBar с помощью конструктора, а затем добавить в него отдельные меню.

Объект главного меню создается следующим образом:

MenuBar mbMainMenuBar; mbMainMenuBar = new MenuBar();

Отдельные меню создаются на базе класса Menu, например:

Menu mnFile; Menu mnHelp; mnFile = new Menu("File"); mnHelp = new Menu("Help");

Создав меню, вы должны добавить в них строки. Для этого нужно вызвать метод add, передав ему в качестве параметра текст строки меню, например:

mnFile.add("New"); mnFile.add("-"); mnFile.add("Exit");

mnHelp.add("Content"); mnHelp.add("-"); mnHelp.add("About");

Далее сформированные меню добавляются в главное меню:

mbMainMenuBar.add(mnFile); mbMainMenuBar.add(mnHelp);

И, наконец, теперь можно устанавливать главное меню в окне класса, созданного на базе класса Frame:

setMenuBar(mbMainMenuBar);

Назад Вперед



Контакты

О компании

Новости

Вакансии

Правовые аспекты

Условия использования

Торговые марки

Copyright 1994-2005 Sun Microsystems, Inc.

printmenus();

Программные продукты

Рабочие станции и тонкие клиенты

Серверы

Системы хранения данных

Посмотреть все

»

  

Solaris 10

Java 2 Standard Edition

Developer Tools

Top Downloads

New Downloads

Патчи и обновления

Посмотреть все

»

  

Каталог решений

Истории успеха

The Sun Grid

Партнерские программы

Посмотреть все

»

  

Гарантийное обслуживание

Программы SunSpectrum

Консалтинг

Услуги инсталляции

Поддержка ПО

Посмотреть все

»

  

Описание курсов

Сертификация

Авторизованные учебные центры

Посмотреть все

»

  

Проекты

События

Lab Downloads

Посмотреть все

»

  



Создание диалоговых панелей


Назад Вперед

Диалоговые панели создаются на базе класса Dialog, краткое описание которого приведено ниже.



В этой лекции было рассмотрено


В этой лекции было рассмотрено устройство массивов в Java. Подобно массивам в других языках, они представляют собой набор значений одного типа. Основным свойством массива является длина, которая в Java может равняться нулю. В противном случае, массив обладает элементами в количестве, равном длине, к которым можно обратиться, используя индекс, изменяющийся от 0 до величины длины без единицы. Длина задается при создании массива и у созданного массива не может быть изменена. Однако она не входит в определение типа, а потому одна переменная может ссылаться на массивы одного типа с различной длиной.
Создать массив можно с помощью ключевого слова new, поскольку все массивы, включая определенные на основе примитивных значений, имеют объектный тип. Другой способ – воспользоваться инициализатором и перечислить значения всех элементов. В первом случае элементы принимают значения по умолчанию (0, false, null).
Особым образом в Java устроены многомерные массивы. Они, по сути, являются одномерными, основанными на массивах меньшей размерности. Такой подход позволяет единообразно работать с многомерными массивами. Также он дает возможность создавать не только "прямоугольные" массивы, но и массивы любой конфигурации.
Хотя массив и является ссылочным типом, работа с ним зачастую имеет некоторые особенности. Рассматриваются правила приведения типа массива. Как для любого объектного типа, приведение к Object является расширяющим. Приведение массивов, основанных на ссылочных типах, во многом подчиняется обычным правилам. А вот примитивные массивы преобразовывать нельзя. С преобразованиями связано и возникновение ошибки ArrayStoreException, причина которой – невозможность точного отслеживания типов в преобразованном массиве для компилятора.
В заключение рассматриваются последние случаи взаимосвязи типа переменной и ее значения.
Наконец, изучается механизм клонирования, существующий с самых первых версий Java и позволяющий создавать точные копии объектов, если их классы позволяют это делать, реализуя интерфейс Cloneable. Поскольку стандартное клонирование порождает только один новый объект, это приводит к особым эффектам при работе с объектными полями классов и массивами.

Аплет Rectangles


Назад Вперед

В качестве примера многопоточного приложения мы приведем аплет Rectangles (рис. 1). Он создает три потока. Первый поток рисует в окне аплета прямоугольники случайного размера и цвета, второй - эллипсы, а третий управляет потоком рисования эллипсов.

Рис. 1. Окно аплета Rectangles

Расположение прямоугольников и эллипсов также выбирается случайно.

Назад Вперед



Контакты

О компании

Новости

Вакансии

Правовые аспекты

Условия использования

Торговые марки

Copyright 1994-2005 Sun Microsystems, Inc.

printmenus();

Программные продукты

Рабочие станции и тонкие клиенты

Серверы

Системы хранения данных

Посмотреть все

»

  

Solaris 10

Java 2 Standard Edition

Developer Tools

Top Downloads

New Downloads

Патчи и обновления

Посмотреть все

»

  

Каталог решений

Истории успеха

The Sun Grid

Партнерские программы

Посмотреть все

»

  

Гарантийное обслуживание

Программы SunSpectrum

Консалтинг

Услуги инсталляции

Поддержка ПО

Посмотреть все

»

  

Описание курсов

Сертификация

Авторизованные учебные центры

Посмотреть все

»

  

Проекты

События

Lab Downloads

Посмотреть все

»

  



Блоки и локальные переменные


Блок - это последовательность операторов, объявлений локальных классов или локальных переменных, заключенных в скобки. Область видимости локальных переменных и классов ограничена блоком, в котором они определены.

Операторы в блоке выполняются слева направо, сверху вниз. Если все операторы (выражения) в блоке выполняются нормально, то и весь блок выполняется нормально. Если какой-либо оператор (выражение) завершается ненормально, то и весь блок завершается ненормально.

Нельзя объявлять несколько локальных переменных с одинаковыми именами в пределах видимости блока. Приведенный ниже код вызовет ошибку времени компиляции.

public class Test {

public Test() { } public static void main(String[] args) { Test t = new Test(); int x; lbl: { int x = 0; System.out.println("x = " + x); } } }

В то же время не следует забывать, что локальные переменные перекрывают видимость переменных-членов. Так, следующий пример отработает нормально.

public class Test { static int x = 5; public Test() { } public static void main(String[] args) { Test t = new Test(); int x = 1; System.out.println("x = " + x); } }

На консоль будет выведено x = 1.

То же самое правило применимо к параметрам методов.

public class Test { static int x; public Test() { } public static void main(String[] args) { Test t = new Test(); t.test(5); System.out.println("Member value x = " + x); } private void test(int x){ this.x = x + 5; System.out.println("Local value x = " + x); } }

В результате работы этого примера на консоль будет выведено:

Local value x = 5 Member value x = 10

На следующем примере продемонстрируем, что область видимости локальной переменной ограничена областью видимости блока, или оператора, в пределах которого данная переменная объявлена.

public class Test { static int x = 5; public Test() { } public static void main(String[] args) { Test t = new Test(); { int x = 1; System.out.println("First block x = " + x); } { int x = 2; System.out.println("Second block x =" + x); } System.out.print("For cycle x = "); for(int x =0;x<5;x++) { System.out.print(" " + x); } } }


Данный пример откомпилируется без ошибок и на консоль будет выведен следующий результат:

First block x = 1 Second block x =2 For cycle x = 0 1 2 3 4

Следует помнить, что определение локальной переменной есть исполняемый оператор. Если задана инициализация переменой, то выражение исполняется слева направо и его результат присваивается локальной переменной. Использование неинициализированных локальных переменных запрещено и вызывает ошибку компиляции.

Следующий пример кода

public class Test { static int x = 5; public Test() { } public static void main(String[] args) { Test t = new Test(); int x; int y = 5; if( y > 3) x = 1; System.out.println(x); } }

вызовет ошибку времени компиляции, т.к. возможны условия, при которых переменная x может быть не инициализирована до ее использования (несмотря на то, что в данном случае оператор if(y > 3) и следующее за ним выражение x = 1; будут выполняться всегда).


Блокировка на заданный период времени


С помощью метода sleep можно заблокировать поток на заданный период времени:

try { Thread.sleep(500); } catch (InterruptedException ee) { . . . }

В данном примере работа потока Thread приостанавливается на 500 миллисекунд. Заметим, что во время ожидания приостановленный поток не отнимает ресурсы процессора.

Так как метод sleep может создавать исключение InterruptedException, необходимо предусмотреть его обработку. Для этого мы использовали операторы try и catch.



Блокировка потока


Синхронизированный поток, определенный как метод типа synchronized, может переходить в заблокированное состояние автоматически при попытке обращения к ресурсу, занятому другим синхронизированным методом, либо при выполнении некоторых операций ввода или вывода. Однако в ряде случаев полезно иметь более тонкие средства синхронизации, допускающие явное использование по запросу приложения.



Цикл do


Основная форма цикла do имеет следующий вид:

do повторяющееся выражение или блок; while(логическое выражение)

Цикл do будет выполняться до тех пор, пока логическое выражение будет истинным. В отличие от цикла while, этот цикл будет выполнен, как минимум, один раз.

Типичная конструкция цикла do:

int counter = 0; do { counter ++; System.out.println("Counter is " + counter); } while(counter < 5);

В остальном выполнение цикла do аналогично выполнению цикла while, включая использование операторов break и continue.



Цикл for


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

Основная форма цикла for выглядит следующим образом:

for(выражение инициализации; условие; выражение обновления) повторяющееся выражение или блок;

Ключевыми элементами данной языковой конструкции являются предложения, заключенные в круглые скобки и разделенные точкой с запятой.

Выражение инициализации выполняется до начала выполнения тела цикла. Чаще всего используется как некое стартовое условие (инициализация, или объявление переменной).

Условие должно быть логическим выражением и трактуется точно так же, как логическое выражение в цикле while(). Тело цикла выполняется до тех пор, пока логическое выражение истинно. Как и в случае с циклом while(), тело цикла может не исполниться ни разу. Это происходит, если логическое выражение принимает значение "ложь" до начала выполнения цикла.

Выражение обновления выполняется сразу после исполнения тела цикла и до того, как проверено условие продолжения выполнения цикла. Обычно здесь используется выражение инкрементации, но может быть применено и любое другое выражение.

Пример использования цикла for():

... for(counter=0;counter<10;counter++) { System.out.println("Counter is " + counter); }

В данном примере предполагается, что переменная counter была объявлена ранее. Цикл будет выполнен 10 раз и будут напечатаны значения счетчика от 0 до 9.

Разрешается определять переменную прямо в предложении:

for(int cnt = 0;cnt < 10; cnt++) { System.out.println("Counter is " + cnt); }

Результат выполнения этой конструкции будет аналогичен предыдущему. Однако нужно обратить внимание, что область видимости переменной cnt будет ограничена телом цикла.

Любая часть конструкции for() может быть опущена. В вырожденном случае мы получим оператор for с пустыми значениями

for(;;) { ... }

В данном случае цикл будет выполняться бесконечно. Эта конструкция аналогична конструкции while(true){}. Условия, в которых она может быть применена, мы рассмотрим позже.

Возможно также расширенное использование синтаксиса оператора for(). Предложение и выражение могут состоять из нескольких частей, разделенных запятыми.

for(i = 0, j = 0; i<5;i++, j+=2) { ... }

Использование такой конструкции вполне правомерно.



Цикл while


Основная форма цикла while может быть представлена так:

while(логическое выражение) повторяющееся выражение, или блок;

В данной языковой конструкции повторяющееся выражение, или блок будет исполняться до тех пор, пока логическое выражение будет иметь истинное значение. Этот многократно исполняемый блок называют телом цикла

Операторы continue и break могут изменять нормальное исполнение тела цикла. Так, если в теле цикла встретился оператор continue, то операторы, следующие за ним, будут пропущены и выполнение цикла начнется сначала. Если continue используется с меткой и метка принадлежит к данному while, то выполнение его будет аналогичным. Если метка не относится к данному while, его выполнение будет прекращено и управление будет передано на оператор, или блок, к которому относится метка.

Если встретился оператор break, то выполнение цикла будет прекращено.

Если выполнение блока было прекращено по какой-то другой причине (возникла исключительная ситуация), то выполнение всего цикла будет прекращено по той же причине.

Рассмотрим несколько примеров:

public class Test { static int x = 5; public Test() { } public static void main(String[] args) { Test t = new Test(); int x = 0; while(x < 5) { x++; if(x % 2 == 0) continue; System.out.print(" " + x); } } }

На консоль будет выведено

1 3 5

т.е. вывод на печать всех четных чисел будет пропущен.

public class Test { static int x = 5; public Test() { } public static void main(String[] args) { Test t = new Test(); int x = 0; int y = 0; lbl: while(y < 3) { y++; while(x < 5) { x++; if(x % 2 == 0) continue lbl; System.out.println("x=" + x + " y="+y); } } } }

На консоль будет выведено

x=1 y=1 x=3 y=2 x=5 y=3

т.е. при выполнении условия if(x % 2 == 0) continue lbl; цикл по переменной x будет прерван, а цикл по переменной y начнет новую итерацию.

Типичный вариант использования выражения while():

int i = 0; while( i++ < 5) { System.out.println("Counter is " + i); }

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

boolean b = false; while(b) { System.out.println("Executed"); }

В данном случае строка System.out.println("Executed"); выполнена не будет.



Именованные блоки


В реальной практике достаточно часто используются вложенные циклы. Соответственно, может возникнуть ситуация, когда из вложенного цикла нужно прервать внешний. Простое использование break или continue не решает этой задачи, однако в Java можно именовать блок кода и явно указать операторам, к какому из них относится выполняемое действие. Делается это путем присвоения метки операторам do, while, for.

Метка - это любая допустимая в данном контексте лексема, оканчивающаяся двоеточием.

Рассмотрим следующий пример:

... int array[][] = {...}; for(int i=0;i<5;i++) { for(j=0;j<4; j++) { ... if(array[i][j] == caseValue) break; ... } } ...

В данном случае при выполнении условия будет прервано выполнение цикла по j, цикл по i продолжится со следующего значения. Для того, чтобы прервать выполнение обоих циклов, используется метка:

... int array[][] = {:.}; outerLoop: for(int i=0;i<5;i++) { for(j=0;j<4; j++){ ... if(array[i][j] == caseValue) break outerLoop; ... } } ...

Оператор break также может использоваться с именованными блоками.

Между операторами break и continue есть еще одно существенное отличие. Оператор break может использоваться с любым именованным блоком, в этом случае его действие в чем-то похоже на действие goto. Оператор continue (как и отмечалось ранее) может быть использован только в теле цикла. То есть такая конструкция будет вполне приемлемой:

lbl:{ ... if( val > maxVal) break lbl; ... }

В то время как оператор continue здесь применять нельзя. В данном случае при выполнении условия if выполнение блока с меткой lbl будет прервано, то есть управление будет передано на оператор (выражение), следующий непосредственно за закрывающей фигурной скобкой.

Метки используют пространство имен, отличное от пространства имен классов и методов.

Так, следующий пример кода будет вполне работоспособным:

public class Test { public Test() { } public static void main(String[] args) { Test t = new Test(); t.test(); } void test() { Test: { test: for(int i =0;true;i++) { if(i % 2 == 0) continue test; if(i > 10) break Test; System.out.print(i + " "); } } } }

Для составления меток применяются те же синтаксические правила, что и для переменных, за тем исключением, что метки всегда оканчиваются двоеточием. Метки всегда должны быть привязаны к какому-либо блоку кода. Допускается использование меток с одинаковыми именами, но нельзя применять одинаковые имена в пределах видимости блока. Т.е. такая конструкция допустима:

lbl: { ... System.out.println("Block 1"); ... } ... lbl: { ... System.out.println("Block 2"); ... }

А такая нет:

lbl: { ... lbl: { ... } ... }



Исходные тексты аплета Rectangles


Назад Вперед

Исходные тексты аплета Rectangles приведены в листинге 1.

Листинг 1. Файл Rectangles,java

import java.applet.*; import java.awt.*;

public class Rectangles extends Applet { DrawRectangles m_DrawRectThread = null; DrawEllipse m_DrawEllipseThread = null; NotifyTask m_NotifyTaskThread = null

public String getAppletInfo() { return "Name: Rectangles"; }

public void paint(Graphics g) { Dimension dimAppWndDimension = getSize();

g.setColor(Color.yellow); g.fillRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1);

g.setColor(Color.black); g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); }

public void start() { if (m_DrawRectThread == null) { m_DrawRectThread = new DrawRectangles(this); m_DrawRectThread.start(); }

if (m_DrawEllipseThread == null) { m_DrawEllipseThread = new DrawEllipse(this); m_DrawEllipseThread.start(); }

if (m_NotifyTaskThread == null) { m_NotifyTaskThread = new NotifyTask(m_DrawEllipseThread); m_NotifyTaskThread.start(); } }

public void stop() { if (m_DrawRectThread != null) { m_DrawRectThread.stop(); m_DrawRectThread = null; }

if (m_DrawEllipseThread == null) { m_DrawEllipseThread.stop(); m_DrawEllipseThread = null; }

if (m_NotifyTaskThread != null) { m_NotifyTaskThread.stop(); m_NotifyTaskThread = null; } } }

class DrawRectangles extends Thread { Graphics g; Dimension dimAppWndDimension;

public DrawRectangles(Applet Appl) { g = Appl.getGraphics(); dimAppWndDimension = Appl.getSize(); }

public void run() { while (true) { int x, y, width, height; int rColor, gColor, bColor;

x = (int)(dimAppWndDimension.width * Math.random()); y = (int)(dimAppWndDimension.height * Math.random()); width = (int)(dimAppWndDimension.width * Math.random()) / 2; height = (int)(dimAppWndDimension.height * Math.random()) / 2;

rColor = (int)(255 * Math.random()); gColor = (int)(255 * Math.random()); bColor = (int)(255 * Math.random());

g.setColor(new Color(rColor, gColor, bColor)); g.fillRect(x, y, width, height);



Использование оператора throw


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

Например:

... public int calculate(int theValue) { if( theValue < 0) { throw new Exception( "Параметр для вычисления не должен быть отрицательным"); } } ...

В данном случае предполагается, что в качестве параметра методу может быть передано только положительное значение; если это условие не выполнено, то с помощью оператора throw порождается исключительная ситуация. (Для успешной компиляции также требуется в заголовке метода указать throws Exception - это выражение рассматривается ниже.)

Метод должен делегировать обработку исключительной ситуации вызвавшему его коду. Для этого в сигнатуре метода применяется ключевое слово throws, после которого должны быть перечислены через запятую все исключительные ситуации, которые может вызывать данный метод. То есть приведенный выше пример должен быть приведен к следующему виду:

... public int calculate(int theValue) throws Exception { if( theValue < 0) { throw new Exception( "Some descriptive info"); } } ...

Таким образом, создание исключительной ситуации в программе выполняется с помощью оператора throw с аргументом, значение которого может быть приведено к типу Throwable.

В некоторых случаях после обработки исключительной ситуации может возникнуть необходимость передать информацию о ней в вызывающий код.

В этом случае ошибка появляется вторично.

Например:

... try { ... } catch(IOException ex) { ... // Обработка исключительной ситуации ... // Повторное возбуждение исключительной // ситуации throw ex; }

Рассмотрим еще один случай.

Предположим, что оператор throw применяется внутри конструкции try-catch.

try { ... throw new IOException(); ... } catch(Exception e) { ... }

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



Конструкция try-catch


В общем случае конструкция выглядит так:

try { ... } catch(SomeExceptionClass e) { ... } catch(AnotherExceptionClass e) { ... }

Работает она следующим образом. Сначала выполняется код, заключенный в фигурные скобки оператора try. Если во время его выполнения не происходит никаких нештатных ситуаций, то далее управление передается за закрывающую фигурную скобку последнего оператора catch, ассоциированного с данным оператором try.

Если в пределах try возникает исключительная ситуация, то далее выполнение кода производится по одному из перечисленных ниже сценариев.

Возникла исключительная ситуация, класс которой указан в качестве параметра одного из блоков catch. В этом случае производится выполнение блока кода, ассоциированного с данным catch (заключенного в фигурные скобки). Далее, если код в этом блоке завершается нормально, то и весь оператор try завершается нормально и управление передается на оператор (выражение), следующий за закрывающей фигурной скобкой последнего catch. Если код в catch завершается не штатно, то и весь try завершается нештатно по той же причине.

Если возникла исключительная ситуация, класс которой не указан в качестве аргумента ни в одном catch, то выполнение всего try завершается нештатно.



Конструкция try-catch-finally


Оператор finally предназначен для того, чтобы обеспечить гарантированное выполнение какого-либо фрагмента кода. Вне зависимости от того, возникла ли исключительная ситуация в блоке try, задан ли подходящий блок catch, не возникла ли ошибка в самом блоке catch,- все равно блок finally будет в конце концов исполнен.

Последовательность выполнения такой конструкции следующая: если оператор try выполнен нормально, то будет выполнен блок finally. В свою очередь, если оператор finally выполняется нормально, то и весь оператор try выполняется нормально.

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

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

Во всех случаях, если блок finally завершается ненормально, то весь try завершится ненормально по той же причине.

Рассмотрим пример применения конструкции try-catch-finally.

try { byte [] buffer = new byte[128]; FileInputStream fis = new FileInputStream("file.txt"); while(fis.read(buffer) > 0) { ... обработка данных ... } } catch(IOException es) { ... обработка исключения ... } finally { fis.flush(); fis.close(); }

Если в данном примере поместить операторы очистки буфера и закрытия файла сразу после окончания обработки данных, то при возникновении ошибки ввода/вывода корректного закрытия файла не произойдет. Еще раз отметим, что блок finally будет выполнен в любом случае, вне зависимости от того, произошла обработка исключения или нет, возникло это исключение или нет.


В конструкции try-catch- finally обязательным является использование одной из частей оператора catch или finally. То есть конструкция

try { ... } finally { ... }

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

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


Конструктор класса DrawRectangles


В качестве параметра конструктору передается ссылка на класс аплета. Конструктор использует эту ссылку для получения и сохранения в полях класса контекста отображения и размеров окна аплета:

public DrawRectangles(Applet Appl) { g = Appl.getGraphics(); dimAppWndDimension = Appl.getSize(); }



Конструктор класса NotifyTask


Конструктор класса NotifyTask записывает в поле STask ссылку на задачу рисования эллипсов:

public NotifyTask(Thread SynchroTask) { STask = SynchroTask; }



Конструкторы


Создание нового объекта Thread

public Thread();

Создвание нового объекта Thread с указанием объекта, для которого будет вызываться метод run

public Thread(Runnable target);

Аналогично предыдущему, но дополнительно задается имя нового объекта Thread

public Thread(Runnable target, String name);

Создание объекта Thread с указанием его имени

public Thread(String name);

Создание нового объекта Thread с указанием группы потока и объекта, для которого вызывается метод run

public Thread(ThreadGroup group, Runnable target);

Аналогично предыдущему, но дополнительно задается имя нового объекта Thread

public Thread(ThreadGroup group, Runnable target, String name);

Создание нового объекта Thread с указанием группы потока и имени объекта

public Thread(ThreadGroup group, String name);



Метки


Любой оператор, или блок, может иметь метку. Метку можно указывать в качестве параметра для операторов break и continue. Область видимости метки ограничивается оператором, или блоком, к которому она относится. Так, в следующем примере мы получим ошибку компиляции:

public class Test { static int x = 5; static { } public Test() { } public static void main(String[] args) { Test t = new Test(); int x = 1; Lbl1: { if(x == 0) break Lbl1; }

Lbl2:{ if(x > 0) break Lbl1; } } }

В случае, если имеется несколько вложенных блоков и операторов, допускается обращение из внутренних блоков к меткам, относящимся к внешним.

Этот пример является вполне корректным:

public class Test { static int x = 5; static {

} public Test() { } public static void main(String[] args) { Test t = new Test(); int L2 = 0; Test: for(int i = 0; i< 10;i++) { test: for(int j = 0; j< 10;j++) { if( i*j > 50) break Test; } } } private void test() { ; } }

В этом же примере можно увидеть, что метки используют пространство имен, отличное от пространства имен переменных, методов и классов.

Традиционно использование меток не рекомендуется, особенно в объектно-ориентированных языках, поскольку серьезно усложняет понимание порядка выполнения кода, а значит, и его тестирование и отладку. Для Java этот запрет можно считать не столь строгим, поскольку самый опасный оператор goto отсутствует. В некоторых ситуациях (как в рассмотренном примере с вложенными циклами) использование меток вполне оправданно, но, конечно, их применение следует ограничивать лишь самыми необходимыми случаями.



Метод run класса DrawEllipse


Класс DrawEllipse очень похож на только что рассмотренный класс DrawRectangles. Отличие есть только в финальном фрагменте метода run, который мы и рассмотрим.

Вместо задержки на 50 миллисекунд метод run из класса DrawEllipse переходит в состояние ожидания извещения, вызывая метод wait:

try { this.wait(); } catch (InterruptedException e) { }

Это извещение создается управляющим потоком класса NotifyTask, к описанию которого мы переходим.



Метод run класса DrawRectangles


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

Для того чтобы рисовать, необходимо получить контекст отображения. Этот контекст был получен конструктором класса DrawRectangles и может быть использован методом run.

Вооружившись контекстом отображения и размерами окна аплета, поток входит в бесконечный цикл рисования прямоугольников.

В качестве генератора случайных чисел мы используем метод random из класса Math, который при каждом вызове возвращает новое случайное число типа double, лежащее в диапазоне значений от 0.0 до 1.0.

Координаты по осям X и Y рисуемого прямоугольника определяются простым умножением случайного числа, полученного от метода random, соответственно, на ширину и высоту окна аплета:

x = (int)(dimAppWndDimension.width * Math.random()); y = (int)(dimAppWndDimension.height * Math.random());

Аналогично определяются размеры прямоугольника, однако чтобы прямоугольники не были слишком крупными, мы делим полученные значения на 2:

width = (int)(dimAppWndDimension.width * Math.random()) / 2; height = (int)(dimAppWndDimension.height * Math.random()) / 2;

Так как случайное число имеет тип double, в обоих случаях мы выполняем явное преобразование результата вычислений к типу int.

Для случайного выбора цвета прямоугольника мы вычисляем отдельные цветовые компоненты, умножая значение, полученное от метода random, на число 255:

rColor = (int)(255 * Math.random()); gColor = (int)(255 * Math.random()); bColor = (int)(255 * Math.random());

Полученные значения цветовых компонент используются в конструкторе Color для получения цвета. Этот цвет устанавливается в контексте отображения методом setColor:

g.setColor(new Color(rColor, gColor, bColor));

Теперь все готово для рисования прямоугольника, которое мы выполняем при помощи метода fillRect:

g.fillRect(x, y, width, height);

После рисования прямоугольника метод run задерживает свою работу на 50 миллисекунд, вызывая метод sleep:

try { Thread.sleep(50); } catch (InterruptedException e) { stop(); }

Для обработки исключения InterruptedException, которое может возникнуть во время работы этого метода, мы предусмотрели блок try - catch. При возникновении указанного исключения работа потока останавливается вызовом метода stop.



Метод run класса NotifyTask


Метод run класса NotifyTask периодически разблокирует поток рисования эллипсов, вызывая для этого метод notify в цилке с задержкой 30 миллисекунд. Обращение к объекту STask, который хранит ссылку на поток рисования эллипсов, выполняется с использованием синхронизации:

public void run() { while (true) { try { Thread.sleep(30); } catch (InterruptedException e) { }

synchronized(STask) { STask.notify(); } } }

Назад Вперед



Контакты

О компании

Новости

Вакансии

Правовые аспекты

Условия использования

Торговые марки

Copyright 1994-2005 Sun Microsystems, Inc.

printmenus();

Программные продукты

Рабочие станции и тонкие клиенты

Серверы

Системы хранения данных

Посмотреть все

»

  

Solaris 10

Java 2 Standard Edition

Developer Tools

Top Downloads

New Downloads

Патчи и обновления

Посмотреть все

»

  

Каталог решений

Истории успеха

The Sun Grid

Партнерские программы

Посмотреть все

»

  

Гарантийное обслуживание

Программы SunSpectrum

Консалтинг

Услуги инсталляции

Поддержка ПО

Посмотреть все

»

  

Описание курсов

Сертификация

Авторизованные учебные центры

Посмотреть все

»

  

Проекты

События

Lab Downloads

Посмотреть все

»

  



Метод start класса Rectangles


Этот метод последовательно создает три потока и запускает их на выполнение:

if(m_DrawRectThread == null) { m_DrawRectThread = new DrawRectangles(this); m_DrawRectThread.start(); }

if(m_DrawEllipseThread == null) { m_DrawEllipseThread = new DrawEllipse(this); m_DrawEllipseThread.start(); }

if(m_NotifyTaskThread == null) { m_NotifyTaskThread = new NotifyTask(m_DrawEllipseThread); m_NotifyTaskThread.start(); }

В качестве параметра конструкторам классов DrawRectangles и DrawEllipse мы передаем ссылку на аплет Rectangles. Эта ссылка будет нужна для получения контекста отображения и рисования геометрических фигур.

Поток класса NotifyTask будет управлять работой потока DrawEllipse, поэтому мы передаем его конструктору ссылку на соответствующий объект m_DrawEllipseThread.



Метод stop класса Rectangles


Когда пользователь покидает страницу сервера Web с аплетом, метод stop класса Rectangles последовательно останавливает gjnjrb рисования прямоугольников и эллипсов, а также управляющий поток:

if(m_DrawRectThread != null) { m_DrawRectThread.stop(); m_DrawRectThread = null; }

if(m_DrawEllipseThread == null) { m_DrawEllipseThread.stop(); m_DrawEllipseThread = null; }

if(m_NotifyTaskThread != null) { m_NotifyTaskThread.stop(); m_NotifyTaskThread = null; }



Методы


activeCount

Текущее количество активных потоков в группе, к которой принадлежит поток

public static int activeCount();

checkAccess

Текущему потоку разрешается изменять объект Thread

public void checkAccesss();

countStackFrames

Определение количества фреймов в стеке

public int countStackFrames();

currentThread

Определение текущего работающего потока

public static Thread currentThread();

destroy

Принудительное завершение работы потока

public void destroy();

dumpStack

Вывод текущего содержимого стека для отладки

public static void dumpStack();

enumerate

Получение всех объектов Tread данной группы

public static int enumerate(Thread tarray[]);

getName

Определение имени потока

public final String getName();

getPriority

Определение текущего приоритета потока

public final int getPriority();

getThreadGroup

Определение группы, к которой принадлежит поток

public final ThreadGroup getThreadGroup();

interrupt

Прерывание потока

public void interrupt();

interrupted

Определение, является ли поток прерванным

public static boolean interrupted();

isAlive

Определение, выполняется поток или нет

public final boolean isAlive();

isDaemon

Определение, является ли поток демоном

public final boolean isDaemon();

isInterrupted

Определение, является ли поток прерванным

public boolean isInterrupted();

join

Ожидание завершения потока

public final void join();

Ожидание завершения потока в течение заданного времени. Время задается в миллисекундах

public final void join(long millis);

Ожидание завершения потока в течение заданного времени. Время задается в миллисекундах и наносекундах

public final void join(long millis, int nanos);

resume

Запуск временно приостановленного потока

public final void resume();

run

Метод вызывается в том случае, если поток был создан как объект с интерфейсом Runnable

public void run();

setDaemon

Установка для потока режима демона

public final void setDaemon(boolean on);

setName


Устаовка имени потока

public final void setName(String name);

setPriority

Установка приоритета потока

public final void setPriority(int newPriority);

sleep

Задержка потока на заднное время. Время задается в миллисекундах и наносекундах

public static void sleep(long millis);

Задержка потока на заднное время. Время задается в миллисекундах и наносекундах

public static void sleep(long millis, int nanos);

start

Запуск потока на выполнение

public void start();

stop

Остановка выполнения потока

public final void stop();

Аварийная остановка выполнения потока с заданным исключением

public final void stop(Throwable obj);

suspend

Приостановка потока

public final void suspend();

toString

Строка, представляющая объект-поток

public String toString();

yield

Приостановка текущего потока для того чтобы управление было передано другому потоку

public static void yield();


Методы класса Thread


В классе Thread определены три поля, несколько конструкторов и большое количество методов, предназначенных для работы с потоками. Ниже мы привели краткое описание полей, конструкторов и методов.

С помощью конструкторов вы можете создавать потоки различными способами, указывая при необходимости для них имя и группу. Имя предназначено для идентификации потока и является необязательным атрибутом. Что же касается групп, то они предназначены для организации защиты потоков друг от друга в рамках одного приложения.

Методы класса Thread предоставляют все необходимые возможности для управления потоками, в том числе для их синхронизации.



Многопоточность


Назад Вперед

Наверное, сегодня уже нет необходимости объяснять, что такое многопоточность. Все современные операционные системы, такие как Windows 95, Windows NT, OS/2 или UNIX способны работать в многопоточном режиме, повышая общую производительность системы за счет эффективного распараллеливания выполняемых потоков. Пока один поток находится в состоянии ожидания, например, завершения операции обмена данными с медленным периферийным устройством, другой может продолжать выполнять свою работу.

Пользователи уже давно привыкли запускать параллельно несколько приложений для того чтобы делать несколько дел сразу. Пока одно из них занимается, например, печатью документа на принтере или приемом электронной почты из сети Internet, другое может пересчитывать электронную таблицу или выполнять другую полезную работу. При этом сами по себе запускаемые приложения могут работать в рамках одного потока - операционная система сама заботится о распределении времени между всеми запущенными приложениями.

Создавая приложения для операционной системы Windows на языках программирования С или С++, вы могли решать многие задачи, такие как анимация или работа в сети, и без использования многопоточности. Например, для анимации можно было обрабатывать сообщения соответствующим образом настроенного таймера.

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

Назад Вперед



Контакты

О компании

Новости

Вакансии

Правовые аспекты

Условия использования

Торговые марки

Copyright 1994-2005 Sun Microsystems, Inc.

printmenus();

Программные продукты

Рабочие станции и тонкие клиенты

Серверы

Системы хранения данных

Посмотреть все

»

  

Solaris 10

Java 2 Standard Edition

Developer Tools

Top Downloads

New Downloads

Патчи и обновления

Посмотреть все

»

  

Каталог решений

Истории успеха



Нормальное и прерванное выполнение операторов


Последовательность выполнения операторов может быть непрерывной, а может и прерываться (при возникновении определенных условий). Выполнение оператора может быть прервано, если в потоке вычислений будут обнаружены операторы

break continue return

Тогда управление будет передано в другое место (в соответствии с правилами обработки этих операторов, которые мы рассмотрим позже).

Нормальное выполнение оператора может быть прервано также при возникновении исключительных ситуаций, которые тоже будут рассмотрены позднее. Явное возбуждение исключительной ситуации с помощью оператора throw также прерывает нормальное выполнение оператора и передает управление выполнением программы (далее просто управление) в другое место.

Прерывание нормального исполнения всегда вызывается определенной причиной. Приведем список таких причин:

break (без указания метки); break (с указанием метки); continue (без указания метки); continue (с указанием метки); return (с возвратом значения); return (без возврата значения);throw с указанием объекта Throwable, а также все исключения, вызываемые виртуальной машиной Java.

Выражения могут завершаться нормально и преждевременно (аварийно). В данном случае термин "аварийно" вполне применим, т.к. причиной необычной последовательности выполнения выражения может быть только возникновение исключительной ситуации.

Если в операторе содержится выражение, то в случае его аварийного завершения выполнение оператора тоже будет завершено преждевременно (т.е. нормальный ход выполнения оператора будет нарушен).

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



Оператор break


Этот оператор, как и оператор continue, изменяет последовательность выполнения, но не возвращает исполнение к началу цикла, а прерывает его.

public class Test { public Test() { } public static void main(String[] args) { Test t = new Test(); int [] x = {1,2,4,0,8}; int y = 8; for(int cnt=0;cnt < x.length;cnt++) { if(0 == x[cnt]) break; System.out.println("y/x = " + y/x[cnt]); } } }

На консоль будет выведено:

y/x = 8 y/x = 4 y/x = 2

При этом ошибки, связанной с делением на ноль, не произойдет, т.к. если значение элемента массива будет равно 0, то будет выполнено условие в строке 9 и выполнение цикла for будет прервано.

В качестве аргумента break может быть указана метка. Как и в случае с continue, нельзя указывать в качестве аргумента метки блоков, в которых оператор break не содержится.



Оператор continue


Оператор continue может использоваться только в циклах while, do, for. Если в потоке вычислений встречается оператор continue, то выполнение текущей последовательности операторов (выражений) должно быть прекращено и управление будет передано на начало блока, содержащего этот оператор.

... int x = (int)(Math.random()*10); int arr[10] = {....} for(int cnt=0;cnt<10;cnt++) { if(arr[cnt] == x) continue; ... }

В данном случае, если в массиве arr встретится значение, равное x, то выполнится оператор continue и все операторы до конца блока будут пропущены, а управление будет передано на начало цикла.

Если оператор continue будет применен вне контекста оператора цикла, то будет выдана ошибка времени компиляции. В случае использования вложенных циклов оператору continue, в качестве адреса перехода, может быть указана метка, относящаяся к одному из этих операторов.

Рассмотрим пример:

public class Test { public Test() { } public static void main(String[] args) { Test t = new Test(); for(int i=0; i < 10; i++){ if(i % 2 == 0) continue; System.out.print(" i=" + i); } } }

В результате работы на консоль будет выведено:

i=1 i=3 i=5 i=7 i=9

При выполнении условия в строке 7 нормальная последовательность выполнения операторов будет прервана и управление будет передано на начало цикла. Таким образом, на консоль будут выводиться только нечетные значения.



Оператор if


Пожалуй, наиболее распространенной конструкцией в Java, как и в любом другом структурном языке программирования, является оператор условного перехода.

В общем случае конструкция выглядит так:

if (логическое выражение) выражение или блок 1 else выражение или блок 2

Логическое выражение может быть любой языковой конструкцией, которая возвращает булевский результат. Отметим отличие от языка C, в котором в качестве логического выражения могут использоваться различные типы данных, где отличное от нуля выражение трактуется как истинное значение, а ноль - как ложное. В Java возможно использование только логических выражений.

Если логическое выражение принимает значение "истина", то выполняется выражение или блок 1, в противном случае - выражение или блок 2. Вторая часть оператора (else) не является обязательной и может быть опущена. Т.е. конструкция if(x == 5) System.out.println("Five") вполне допустима.

Операторы if-else могут каскадироваться.

String test = "smb"; if( test.equals("value1") { ... } else if (test.equals("value2") { ... } else if (test.equals("value3") { ... } else { ... }

Следует помнить, что оператор else относится к ближайшему к нему оператору if. В данном случае последнее условие else будет выполняться, только если не выполнено предыдущее. Заключительная конструкция else относится к самому последнему условию if и будет выполнена только в том случае, если ни одно из вышеперечисленных условий не будет истинным. Если хотя бы одно из условий выполнено, то все последующие выполняться не будут.

Например:

... int x = 5; if( x < 4) { System.out.println("Меньше 4"); } else if (x > 4) { System.out.println("Больше 4"); } else if (x == 5) { System.out.println("Равно 5"); } else{ System.out.println("Другое значение"); }

Предложение "Равно 5" в данном случае напечатано не будет.



Оператор return


Этот оператор предназначен для возврата управления из вызываемого метода в вызывающий. Если в последовательности операторов выполняется return, то управление немедленно (если это не оговорено особо) передается в вызывающий метод. Оператор return может иметь, а может и не иметь аргументов. Если метод не возвращает значений (объявлен как void), то в этом и только этом случае выражение return применяется без аргументов. Если возвращаемое значение есть, то return обязательно должен применяться с аргументом, чье значение и будет возвращено.

В качестве аргумента return может использоваться выражение

return (x*y +10) /11;

В этом случае сначала будет выполнено выражение, а затем результат его выполнения будет передан в вызывающий метод. Если выражение будет завершено ненормально, то и оператор return будет завершен ненормально. Например, если во время выполнения выражения в операторе return возникнет исключение, то никакого значения метод не вернет, будет обрабатываться ошибка.

В методе может быть более одного оператора return.



Оператор switch


Оператор switch() удобно использовать в случае необходимости множественного выбора. Выбор осуществляется на основе целочисленного значения.

Структура оператора:

switch(int value) { case const1: выражение или блок case const2: выражение или блок case constn: выражение или блок default: выражение или блок }

Причем, фраза default не является обязательной.

В качестве параметра switch может использоваться переменная типа byte, short, int, char или выражение. Выражение должно в конечном итоге возвращать параметр одного из указанных ранее типов. В операторе case не могут применяться значения примитивного типа long и ссылочных типов Long, String, Integer, Byte и т.д.

При выполнении оператора switch производится последовательное сравнение значения x с константами, указанными после case, и в случае совпадения выполняется выражение следующего за этим условием. Если выражение выполнено нормально и нет преждевременного его завершения, то производится сравнение для последующих case. Если же выражение, следующее за case, завершилось ненормально, то будет прекращено выполнение всего оператора switch.

Если не выполнен ни один оператор case, то выполнится оператор default, если он имеется в данном switch. Если оператора default нет и ни одно из условий case не выполнено, то ни одно из выражений switch также выполнено не будет.

Следует обратить внимание, что, в отличие от многозвенного if-else, если какое-либо условие case выполнено, то выполнение switch не прекратится, а будут проверяться следующие за ним условия. Если этого необходимо избежать, то после кода следующего за оператором case используется оператор break, прерывающий дальнейшее выполнение оператора switch.

После оператора case должен следовать литерал, который может быть интерпретирован как 32-битовое целое значение. Здесь не могут применяться выражения и переменные, если они не являются final static.

Рассмотрим пример:

int x = 2; switch(x) { case 1: case 2: System.out.println("Равно 1 или 2"); break; case 3: case 4: System.out.println("Равно 3 или 4"); break; default: System.out.println( "Значение не определено"); }

В данном случае на консоль будет выведен результат "Равно 1 или 2". Если же убрать операторы break, то будут выведены все три строки.

Вот такая конструкция вызовет ошибку времени компиляции.

int x = 5; switch(x) { case y: // только константы! ... break; }

В операторе switch не может быть двух case с одинаковыми значениями.

Т.е. конструкция

switch(x) { case 1: System.out.println("One"); break; case 1: System.out.println("Two"); break; case 3: System.out.println("Tree or other value"); }

недопустима.

Также в конструкции switch может быть применен только один оператор default.



Оператор synchronized


Этот оператор применяется для исключения взаимного влияния нескольких потоков при выполнении кода, он будет подробно рассмотрен в лекции 12, посвященной потокам исполнения.



Операторы break и continue


В некоторых случаях требуется изменить ход выполнения программы. В традиционных языках программирования для этих целей применяется оператор goto, однако в Java он не поддерживается. Для этих целей применяются операторы break и continue.



Описание исходных текстов аплета Rectangles


Назад Вперед

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

Что же касается основного класса аплета, то он унаследован, как обычно, от класса Applet и не реализует интерфейс Runnable:

public class Rectangles extends Applet { . . . }



Ошибки при работе программы. Исключения (Exceptions)


При выполнении программы могут возникать ошибки. В одних случаях это вызвано ошибками программиста, в других - внешними причинами. Например, может возникнуть ошибка ввода/вывода при работе с файлом или сетевым соединением. В классических языках программирования, например, в С, требовалось проверять некое условие, которое указывало на наличие ошибки, и в зависимости от этого предпринимать те или иные действия.

Например:

... int statusCode = someAction(); if (statusCode){ ... обработка ошибки } else { statusCode = anotherAction(); if(statusCode) { ... обработка ошибки ... } } ...

В Java появилось более простое и элегантное решение - обработка исключительных ситуаций.

try{ someAction(); anotherAction(); } catch(Exception e) { // обработка исключительной ситуации }

Легко заметить, что такой подход является не только изящным, но и более надежным и простым для понимания.



Особые случаи


Во время исполнения кода могут возникать ситуации, которые почти не описаны в литературе.

Рассмотрим такую ситуацию:

import java.io.*; public class Test {

public Test() { } public static void main(String[] args) { Test test = new Test(); try { test.doFileInput("bogus.file"); } catch (IOException ex) { System.out.println("Second exception handle stack trace"); ex.printStackTrace(); } }

private String doFileInput(String fileName) throws FileNotFoundException,IOException { String retStr = ""; java.io.FileInputStream fis = null; try { fis = new java.io.FileInputStream(fileName); } catch (FileNotFoundException ex) { System.out.println("First exception handle stack trace"); ex.printStackTrace(); throw ex; } return retStr; } }

Результат работы будет выглядеть следующим образом:

java.io.FileNotFoundException: bogus.file (The system cannot find the file specified) at java.io.FileInputStream.open(Native Method) at java.io.FileInputStream.<init>(FileInputStream.java:64) at experiment.Test.doFileInput(Test.java:33) at experiment.Test.main(Test.java:21) First exception handle stack trace java.io.FileNotFoundException: bogus.file (The system cannot find the file specified) at java.io.FileInputStream.open(Native Method) at java.io.FileInputStream.<init>(FileInputStream.java:64) at experiment.Test.doFileInput(Test.java:33) at experiment.Test.main(Test.java:21) Second exception handle stack trace

Так как при вторичном возбуждении используется один и тот же объект Exception, стек в обоих случаях будет содержать одну и ту же последовательность вызовов. То есть при повторном возбуждении исключения, если мы используем тот же объект, изменения его параметров не происходит.

Рассмотрим другой пример:

import java.io.*;

public class Test {

public Test() { } public static void main(String[] args) { Test test = new Test(); try { test.doFileInput(); } catch (IOException ex) { System.out.println("Exception hash code " + ex.hashCode()); ex.printStackTrace(); } }


private String doFileInput() throws FileNotFoundException,IOException{ String retStr = ""; java.io.FileInputStream fis = null; try { fis = new java.io.FileInputStream("bogus.file"); } catch (FileNotFoundException ex) { System.out.println("Exception hash code " + ex.hashCode()); ex.printStackTrace(); fis = new java.io.FileInputStream("anotherBogus.file"); throw ex; } return retStr; } }

java.io.FileNotFoundException: bogus.file ( The system cannot find the file specified) at java.io.FileInputStream.open(Native Method) at java.io.FileInputStream.<init>(FileInputStream.java:64) at experiment.Test.doFileInput(Test.java:33) at experiment.Test.main(Test.java:21) Exception hash code 3214658

java.io.FileNotFoundException: (The system cannot find the path specified) at java.io.FileInputStream.open(Native Method) at java.io.FileInputStream.<init>(FileInputStream.java:64) at experiment.Test.doFileInput(Test.java:38) at experiment.Test.main(Test.java:21) Exception hash code 6129586

Несложно заметить, что, хотя последовательность вызовов одна и та же, в вызываемом и вызывающем методах обрабатываются разные объекты исключений.


Ожидание извещения


Если вам нужно организовать взаимодействие потоков таким образом, чтобы один поток управлял работой другого или других потоков, вы можете воспользоваться методами wait, notify и notifyAll, определенными в классе Object.

Метод wait может использоваться либо с параметром, либо без параметра. Этот метод переводит поток в состояние ожидания, в котором он будет находиться до тех пор, пока для потока не будет вызван извещающий метод notify, notifyAll, или пока не истечет период времени, указанный в параметре метода wait.

Как пользоваться методами wait, notify и notifyAll?

Метод, который будет переводиться в состояние ожидания, должен быть синхронизированным, то есть его следует описать как synchronized:

public synchronized void run() { while (true) { . . . try { this.wait(); } catch (InterruptedException e) { } } }

В этом примере внутри метода run определен цикл, вызывающий метод wait без параметров. Каждый раз при очередном проходе цикла метод run переводится в состояние ожидания до тех пор, пока другой поток не выполнит извещение с помощью метода notify.

Ниже мы привели пример потока, вызывающией метод notify:

public void run() { while (true) { try { Thread.sleep(30); } catch (InterruptedException e) { }

synchronized(STask) { STask.notify(); } } }

Этот поток реализован в рамках отдельного класса, конструктору которого передается ссылка на поток, вызывающую метод wait. Эта ссылка хранится в поле STask.

Обратите внимание, что хотя сам метод run не синхронизированный, вызов метода notify выполняется в синхронизированном режиме. В качестве объекта синхронизации выступает поток, для которого вызывается метод notify.



Ожидание завершения потока


С помощью метода join вы можете выполнять ожидание завершения работы потока, для которой этот метод вызван.

Существует три определения метода join:

public final void join(); public final void join(long millis); public final void join(long millis, int nanos);

Первый из них выполняет ожидание без ограничения во времени, для второго ожидание будет прервано принудительно через millis миллисекунд, а для третьего - через millis миллисекунд и nanos наносекунд. Учтите, что реально вы не сможете указывать время с точностью до наносекунд, так как дискретность системного таймера компьютера намного больше.

Назад Вперед



Контакты

О компании

Новости

Вакансии

Правовые аспекты

Условия использования

Торговые марки

Copyright 1994-2005 Sun Microsystems, Inc.

printmenus();

Программные продукты

Рабочие станции и тонкие клиенты

Серверы

Системы хранения данных

Посмотреть все

»

  

Solaris 10

Java 2 Standard Edition

Developer Tools

Top Downloads

New Downloads

Патчи и обновления

Посмотреть все

»

  

Каталог решений

Истории успеха

The Sun Grid

Партнерские программы

Посмотреть все

»

  

Гарантийное обслуживание

Программы SunSpectrum

Консалтинг

Услуги инсталляции

Поддержка ПО

Посмотреть все

»

  

Описание курсов

Сертификация

Авторизованные учебные центры

Посмотреть все

»

  

Проекты

События

Lab Downloads

Посмотреть все

»

  



Переопределение методов и исключения


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

public class BaseClass{ public void method () throws IOException { ... } }

public class LegalOne extends BaseClass { public void method () throws IOException { ... } }

public class LegalTwo extends BaseClass { public void method () { ... } }

public class LegalTree extends BaseClass { public void method () throws EOFException,MalformedURLException { ... } }

public class IllegalOne extends BaseClass { public void method () throws IOException,IllegalAccessException { ... } }

public class IllegalTwo extends BaseClass { public void method () { ... throw new Exception(); } }

В данном случае:

определение класса LegalOne будет корректным, так как переопределение метода method() верное (список ошибок не изменился); определение класса LegalTwo будет корректным, так как переопределение метода method() верное (новый метод не может выбрасывать ошибок, а значит, не расширяет список возможных ошибок старого метода); определение класса LegalTree будет корректным, так как переопределение метода method() будет верным (новый метод может создавать исключения, которые являются подклассами исключения, возбуждаемого в старом методе, то есть список сузился);определение класса IlegalOne будет некорректным, так как переопределение метода method() неверно (IllegalAccessException не является подклассом IOException, список расширился); определение класса IlegalTwo будет некорректным: хотя заголовок method() объявлен верно (список не расширился), в теле метода бросается исключение, не указанное в throws.



Поля


Три статических поля предназначены для назначения приоритетов потокам.

NORM_PRIORITY

Нормальный

public final static int NORM_PRIORITY;

MAX_PRIORITY

Максимальный

public final static int MAX_PRIORITY;

MIN_PRIORITY

Минимальный

public final static int MIN_PRIORITY;



Поля класса DrawRectangles


Класс DrawRectangles определен для потока рисования прямоугольников:

class DrawRectangles extends Thread { . . . }

В поле g класа хранится контекст отображения окна аплета, а в поле dimAppWndDimension - размеры этого окна:

Graphics g; Dimension dimAppWndDimension;

Значения этих полей определяются конструктором класса по ссылке на главный класс аплета.



Поля класса NotifyTask


В классе NotifyTask мы определили одно поле STask класса Thread. Это поле которое хранит ссылку на поток, работой которого управляет данный класс:

class NotifyTask extends Thread { Thread STask; . . . }



Поля класса Rectangles


В классе Rectangles мы определили три поля с именами m_DrawRectThread, m_DrawEllipseThread и m_NotifyTaskThread:

DrawRectangles m_DrawRectThread = null; DrawEllipse m_DrawEllipseThread = null; NotifyTask m_NotifyTaskThread = null

Эти поля являются ссылками на классы, соответственно DrawRectangles, DrawEllipse и NotifyTask . Первый из них создан для рисования прямоугольников, второй - эллипсов, а третий - для управления потоком рисования эллипсов.

Указанные поля инициализируются занчением null, что соответствует неработающим или несозданным задачам.



Поток


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

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



Потоки-демоны


Назад Вперед

Вызвав для потока метод setDaemon, вы превращаете обычную поток в поток-демон. Такой поток работает в фоновом режиме независимо от породившего его потока. Если поток-демон создает другие потоки, то они также станут получат статус потока-демона.

Заметим, что метод setDaemon необходимо вызывать после создания потока, но до момента его запуска, то есть перед вызовом метода start.

С помощью метода isDaemon вы можете проверить, является поток демоном, или нет.

Назад Вперед



Контакты

О компании

Новости

Вакансии

Правовые аспекты

Условия использования

Торговые марки

Copyright 1994-2005 Sun Microsystems, Inc.

printmenus();

Программные продукты

Рабочие станции и тонкие клиенты

Серверы

Системы хранения данных

Посмотреть все

»

  

Solaris 10

Java 2 Standard Edition

Developer Tools

Top Downloads

New Downloads

Патчи и обновления

Посмотреть все

»

  

Каталог решений

Истории успеха

The Sun Grid

Партнерские программы

Посмотреть все

»

  

Гарантийное обслуживание

Программы SunSpectrum

Консалтинг

Услуги инсталляции

Поддержка ПО

Посмотреть все

»

  

Описание курсов

Сертификация

Авторизованные учебные центры

Посмотреть все

»

  

Проекты

События

Lab Downloads

Посмотреть все

»

  



Причины возникновения ошибок


Существует три причины возникновения исключительных ситуаций.

Попытка выполнить некорректное выражение. Например, деление на ноль, или обращение к объекту по ссылке, равной null, попытка использовать класс, описание которого (class-файл) отсутствует, и т.д. В таких случаях всегда можно точно указать, в каком месте произошла ошибка, - именно в некорректном выражении. Выполнение оператора throw Этот оператор применяется для явного порождения ошибки. Очевидно, что и здесь можно указать место возникновения исключительной ситуации. Асинхронные ошибки во время исполнения программы.

Причиной таких ошибок могут быть сбои внутри самой виртуальной машины (ведь она также является программой), или вызов метода stop() у потока выполнения Thread.

В этом случае невозможно указать точное место программы, где происходит исключительная ситуация. Если мы попытаемся остановить поток выполнения (вызвав метод stop()), нам не удастся предсказать, при выполнении какого именно выражения этот поток остановится.

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

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


Аналогичная ситуация наблюдается и в случае с принудительной остановкой потоков исполнения. Поскольку это действие выполняется операционной системой, никогда нельзя предсказать, в каком именно месте остановится поток. Это означает, что программа может многократно отработать корректно, а потом неожиданно дать сбой просто из-за того, что поток остановился в каком-то другом месте. По этой причине принудительная остановка не рекомендуется. В лекции 12 рассматриваются примеры корректного управления жизненным циклом потока.

При возникновении исключительной ситуации управление передается от кода, вызвавшего исключительную ситуацию, на ближайший блок catch (или вверх по стеку) и создается объект, унаследованный от класса Throwable, или его потомков (см. диаграмму иерархии классов-исключений), который содержит информацию об исключительной ситуации и используется при ее обработке. Собственно, в блоке catch указывается именно класс обрабатываемой ситуации. Подробно обработка ошибок рассматривается ниже.

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

метод, то управление будет передаваться в то место, где данный метод был вызван;конструктор, то управление будет передаваться туда, где попытались создать объект (как правило, применяя оператор new); статический инициализатор, то управление будет передано туда, где произошло первое обращение к классу, потребовавшее его инициализации.

Допускается создание собственных классов исключительных ситуаций. Осуществляется это с помощью механизма наследования, то есть класс пользовательской исключительной ситуации должен быть унаследован от класса Throwable, или его потомков.


Применение многопоточности для анимации


Назад Вперед

Одно из наиболее распространенных применений аплетов - это создание анимационных эффектов типа бегущей строки, мерцающих огней или аналогичных, привлекающих внимание пользователя. Для того чтобы достичь такого эффекта, необходим какой либо механизм, позволяющий выполнять перерисовку всего окна аплета или его части периодически с заданным временным интервалом.

Работа аплетов, так же как и обычных приложений операционной системы Windows, основана на обработке событий. Для классического приложения Windows событие - это приход сообщения в функцию окна. Основной класс аплета обрабатывает события, переопределяя те или иные методы базового класса Applet.

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

Напомним, что перерисовка окна аплета выполняется методом paint, который вызывается виртуальной машиной Java асинхронно по отношению к выполнению другого кода аплета.

Можно ли воспользоваться методом paint для периодической перерисовки окна аплета, организовав в нем, например, бесконечный цикл с задержкой?

К сожалению, так поступать ни в коем случае нельзя. Метод paint после перерисовки окна аплета должен сразу возвратить управление, иначе работа аплета будет заблокирована.

Единственный выход из создавшейся ситуации - создание потока (или нескльких потоков), которые будут выполнять рисование в окне аплета асинхронно по отношению к работе кода аплета. Например, вы можете создать поток, который периодически обновляет окно аплета, вызывая для этого метод repaint, или рисовать из потока непосредственно в окне аплета, получив предварительно для этого окна контекст отображения.



Приоритеты потоков в приложениях Java


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

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

Распределение времени выполняется по прерываниям системного таймера. Поэтому каждому потоку дается определенный интервал времени, в течении которого он находится в активном состоянии.

Заметим, что распределение времени выполняется для потоков, а не для процессов. Потоки, созданные разными процессами, конкурируют между собой за получение процессорного времени.

Каким именно образом?

Приложения Java могут указывать три значения для приоритетов потоков. Это NORM_PRIORITY, MAX_PRIORITY и MIN_PRIORITY.

По умолчанию вновь созданный поток имеет нормальный приоритет NORM_PRIORITY. Если остальные потоки в системе имеют тот же самый приоритет, то все потоки пользуются процессорным времени на равных правах.

При необходимости вы можете повысить или понизить приоритет отдельных потоков, определив для них значение приоритета, соответственно, MAX_PRIORITY или MIN_PRIORITY. Потоки с повышенным приоритетом выполняются в первую очередь, а с пониженным - только при отсутствии готовых к выполнению потоков, имеющих нормальный или повышенный приоритет.

Назад Вперед



Контакты

О компании

Новости

Вакансии

Правовые аспекты

Условия использования

Торговые марки

Copyright 1994-2005 Sun Microsystems, Inc.

printmenus();

Программные продукты

Рабочие станции и тонкие клиенты

Серверы

Системы хранения данных

Посмотреть все

»

  

Solaris 10

Java 2 Standard Edition

Developer Tools

Top Downloads

New Downloads

Патчи и обновления

Посмотреть все

»

  

Каталог решений



Процесс


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



Процессы, потоки и приоритеты


Назад Вперед

Прежде чем приступить к разговору о многопоточности, следует уточнить некоторые термины.

Обычно в любой многопоточной операционной системе выделяют такие объекты, как процессы и потоки. Между ними существует большая разница, которую следует четко себе представлять.



Проверяемые и непроверяемые исключения


Все исключительные ситуации можно разделить на две категории: проверяемые (checked) и непроверяемые (unchecked).

Все исключения, порождаемые от Throwable, можно разбить на три группы. Они определяются тремя базовыми типами: наследниками Throwable - классами Error и Exception, а также наследником Exception - RuntimeException.

Ошибки, порожденные от Exception (и не являющиеся наследниками RuntimeException), являются проверяемыми. Т.е. во время компиляции проверяется, предусмотрена ли обработка возможных исключительных ситуаций. Как правило, это ошибки, связанные с окружением программы (сетевым, файловым вводом-выводом и др.), которые могут возникнуть вне зависимости от того, корректно написан код или нет. Например, открытие сетевого соединения или файла может привести к возникновению ошибки и компилятор требует от программиста предусмотреть некие действия для обработки возможных проблем. Таким образом повышается надежность программы, ее устойчивость при возможных сбоях.

Исключения, порожденные от RuntimeException, являются непроверяемыми и компилятор не требует обязательной их обработки.

Как правило, это ошибки программы, которые при правильном кодировании возникать не должны (например, IndexOutOfBoundsException - выход за границы массива, java.lang.ArithmeticException - деление на ноль). Поэтому, чтобы не загромождать программу, компилятор оставляет на усмотрение программиста обработку таких исключений с помощью блоков try-catch.

Исключения, порожденные от Error, также не являются проверяемыми. Они предназначены для того, чтобы уведомить приложение о возникновении фатальной ситуации, которую программным способом устранить практически невозможно (хотя формально обработчик допускается). Они могут свидетельствовать об ошибках программы, но, как правило, это неустранимые проблемы на уровне JVM. В качестве примера можно привести StackOverflowError (переполнение стека), OutOfMemoryError (нехватка памяти).

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

try { ... } catch(Exception e) { ... } catch(IOException ioe) { ... } catch(UserException ue) { ... }



Рис. 10.1.  Иерархия классов стандартных исключений.

В данном примере при возникновении исключительной ситуации (класс, порожденный от Exception) будет выполняться всегда только первый блок catch. Остальные не будут выполнены ни при каких условиях. Эта ситуация отслеживается компилятором, который сообщает об UnreachableCodeException (ошибка - недостижимый код). Правильно данная конструкция будет выглядеть так:

try { ... } catch(UserException ue) { ... } catch(IOException ioe) { ... } catch(Exception e) { ... }

В этом случае будет выполняться последовательная обработка исключений. И в случае, если не предусмотрена обработка того типа исключения, которое возникло (например, AnotherUserException), будет выполнен блок catch(Exception e){:}

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


Пустой оператор


Точка с запятой (;) является пустым оператором. Данная конструкция вполне применима там, где не предполагается выполнение никаких действий. Преждевременное завершение пустого оператора невозможно.



Реализация интерфейса Runnable


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

Идея заключается в том, что основной класс аплета, который является дочерним по отношению к классу Applet, дополнительно реализует интерфейс Runnable, как это показано ниже:

public class MultiTask extends Applet implements Runnable { Thread m_MultiTask = null; . . . public void run() { . . . }

public void start() { if (m_MultiTask == null) { m_MultiTask = new Thread(this); m_MultiTask.start(); } }

public void stop() { if (m_MultiTask != null) { m_MultiTask.stop(); m_MultiTask = null; } } }

Внутри класса необходимо определить метод run, который будет выполняться в рамках отдельного потока. При этом можно считать, что код аплета и код метода run работают одновременно как разные потоки.

Для создания потока используется оператор new. Поток создается как объект класса Thread, причем конструктору передается ссылка на класс аплета:

m_MultiTask = new Thread(this);

При этом, когда поток запустится, управление получит метод run, определенный в классе аплета.

Как запустить поток?

Запуск выполняется, как и раньше, методом start. Обычно поток запускается из метода start аплета, когда пользователь отображает страницу сервера Web, содержащую аплет. Остановка потока выполняется методом stop.

Назад Вперед



Контакты

О компании

Новости

Вакансии

Правовые аспекты

Условия использования

Торговые марки

Copyright 1994-2005 Sun Microsystems, Inc.

printmenus();

Программные продукты

Рабочие станции и тонкие клиенты

Серверы

Системы хранения данных

Посмотреть все

»

  

Solaris 10

Java 2 Standard Edition

Developer Tools

Top Downloads

New Downloads

Патчи и обновления

Посмотреть все

»

  

Каталог решений

Истории успеха



Реализация многопоточности в Java


Назад Вперед

должны воспользоваться классом java.lang.Thread. В этом классе определены все методы, необходимые для создания потоков, управления их состоянием и синхронизации.

Как пользоваться классом Thread?

Есть две возможности.

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

Во-вторых, ваш класс может реализовать интерфейс Runnable. При этом в рамках вашего класса необходимо определить метод run, который будет работать как отдельный поток.

Второй способ особенно удобен в тех случаях, когда ваш класс должен быть унаследован от какого-либо другого класса (например, от класса Applet) и при этом вам нужна многопоточность. Так как в языке программирования Java нет множественного наследования, невозможно создать класс, для которого в качестве родительского будут выступать классы Applet и Thread. В этом случае реализация интерфейса Runnable является единственным способом решения задачи.



Синхронизация методов


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

Чтобы воспользоваться защелками, вы можете объявить соответствующий метод как synchronized, сделав его синхронизированным:

public synchronized void decrement() { . . . }

При вызове синхронизированного метода соответствующий ему объект (в котором он определен) блокируется для использования другими синхронизированными методами. В результате предотвращается одновременная запись двумя методами значений в область памяти, принадлежащую данному объекту.

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

Заметим, что не обязательно синхронизовать весь метод - можно выполнить синхронизацию только критичного фрагмента кода.

. . . synchronized(Account) { if(Account.check(3000000)) Account.decrement(3000000); } . . .

Здесь синхронизация выполняется для объекта Account.



Синхронизация потоков


Назад Вперед

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

Для чего и когда она нужна?

Однопоточная программа, такая, например, как программа MS-DOS, при запуске получает в монопольное распоряжение все ресурсы компьютера. Так как в однопоточной системе существует только один процесс, он использует эти ресурсы в той последовательности, которая соответствует логике работы программы. Процессы и потоки, работающие одновременно в многопоточной системе, могут пытаться обращаться одновременно к одним и тем же ресурсам, что может привести к неправильной работе приложений.

Поясним это на простом примере.

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

на первом шаге проверяется общая сумма денег, которая хранится на счету;

если общая сумма равна или превышает размер снимаемой суммы денег, общая сумма уменьшается на необходимую величину;

значение остатка записывается на текущий счет.

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

Допустим, события разворачиваются следующим образом:

первый процесс проверяет состояние текущего счета и убеждается, что на нем хранится 5 млн. долларов;

второй процесс проверяет состояние текущего счета и также убеждается, что на нем хранится 5 млн. долларов;

первый процесс уменьшает счет на 3 млн. долларов и записывает остаток (2 млн. долларов) на текущий счет;



Создание дочернего класса на базе класса Thread


Рассмотрим первый способ реализации многопоточности, основанный на наследовании от класса Thread. При использовании этого способа вы определяете для потока отдельный класс, например, так:

class DrawRectangles extends Thread { . . . public void run() { . . . } }

Здесь определен класс DrawRectangles, который является дочерним по отношению к классу Thread.

Обратите внимание на метод run. Создавая свой класс на базе класса Thread, вы должны всегда определять этот метод, который и будет выполняться в рамках отдельного потока.

Заметим, что метод run не вызывается напрямую никакими другими методами. Он получает управление при запуске потока методом start.

Как это происходит?

Рассмотрим процедуру запуска потока на примере некоторого класса DrawRectangles.

Вначале ваше приложение должно создать объект класса Thread:

public class MultiTask2 extends Applet { Thread m_DrawRectThread = null; . . . public void start() { if (m_DrawRectThread == null) { m_DrawRectThread = new DrawRectangles(this); m_DrawRectThread.start(); } } }

Создание объекта выполняется оператором new в методе start, который получает управление, когда пользователь открывает документ HTML с аплетом. Сразу после создания поток запускается на выполнение, для чего вызывается метод start.

Что касается метода run, то если поток используется для выполнения какой либо периодической работы, то этот метод содержит внутри себя бесконечный цикл. Когда цикл завершается и метод run возвращает управление, поток прекращает свою работу нормальным, не аварийным образом. Для аварийного завершения потока можно использовать метод interrupt.

Остановка работающего потока выполняется методом stop. Обычно остановка всех работающих потоков, созданных аплетом, выполняется методом stop класса аплета:

public void stop() { if (m_DrawRectThread != null) { m_DrawRectThread.stop(); m_DrawRectThread = null; } }

Напомним, что этот метод вызывается, когда пользователь покидает страницу сервера Web, содержащую аплет.



Создание пользовательских классов исключений


Как уже отмечалось, допускается создание собственных классов исключений. Для этого достаточно создать свой класс, унаследовав его от любого наследника java.lang.Throwable (или от самого Throwable).

Пример:

public class UserException extends Exception { public UserException() { super(); } public UserException(String descry) { super(descry); } }

Соответственно, данное исключение будет создаваться следующим образом:

throw new UserException( "Дополнительное описание");



Управление циклами


В языке Java имеется три основных конструкции управления циклами:

цикл while; цикл do; цикл for.



Управление ходом программы


Управление потоком вычислений является фундаментальной основой всего языка программирования. В данной лекции будут рассмотрены основные языковые конструкции и способы их применения.

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

Порядок выполнения программы определяется операторами. Операторы могут содержать другие операторы или выражения.



Временная приостановка и возобновление работы


Методы suspend и resume позволяют, соответственно, временно приостанавливать и возобновлять работу потока.

В следующем фрагменте кода поток m_Rectangles приостанавливает свою работу, когда курсор мыши оказывается над окном аплета:

public boolean mouseEnter(Event evt, int x, int y) { if (m_Rectangles != null) { m_Rectangles.suspend(); } return true; }

Работа потока возобновляется, когда курсор мыши покидает окно аплета:

public boolean mouseExit(Event evt, int x, int y) { if (m_Rectangles != null) { m_Rectangles.resume(); } return true; }



В данной лекции рассмотрены основные


В данной лекции рассмотрены основные языковые конструкции.
Для организации циклов в Java предназначены три основных конструкции: while, do, for. Для изменения порядка выполнения операторов применяются continue и break (с меткой или без). Также существуют два оператора ветвления: if и switch.
Важной темой является обработка ошибок, поскольку без нее не обходится ни одна программа, ведь причиной сбоев может служить не только ошибка программиста, но и внешние события, например, разрыв сетевого соединения. Основной конструкцией обработки исключительных ситуаций является try-catch-finally. Для явной инициализации исключительной ситуации служит ключевое слово throw.
Ошибки делятся на проверяемые и непроверяемые. Чтобы повысить надежность программы, компилятор требует обработки исключений, классы которых наследуются от Exception, кроме классов-наследников RuntimeException. Предполагается, что такие ошибки могут возникать не столько по ошибке разработчика, сколько по внешним неконтролируемым причинам.
Классы, унаследованные от RuntimeException, описывают программные сбои. Ожидается, что программист сведет вероятность таких ошибок к минимуму, а потому, чтобы не загромождать код, они являются непроверяемыми, компилятор оставляет обработку на усмотрение разработчика. Ошибки-наследники Error свидетельствуют о фатальных сбоях, поэтому их также необязательно обрабатывать.
Методы, код которых может порождать проверяемые исключения, должны либо сами их обрабатывать, либо в заголовке метода должно быть указано ключевое слово throws с перечислением необрабатываемых проверяемых исключений. На непроверяемые ошибки это правило не распространяется.
Переопределенный (overridden) метод не может расширять список возможных исключений исходного метода.