Абстрактные классы
Многие программисты, использующие язык Си++, уже знакомы с понятием абстрактных классов. В языке программирования Java абстрактные классы весьма схожи со своими "родственниками" из Си++ и отличаются лишь деталями реализации. Но тем не менее давайте рассмотрим абстрактные классы Java подробно.
Предположим, вы создаете некую модель бытовой техники и вам требуются отдельные классы для описания каждой детали этого устройства. Логично создать единую электронную форму для занесения в нее сведений о любом аппарате, в которой будет стоять ссылка на некий класс "Бытовое устройство". Теперь вы можете смело ссылаться из этой формы на любой класс, унаследованный от класса "Бытовое устройство". Это может быть "Пылесос", "Электрогриль" и т. п. При этом все ссылки будут работать корректно, несмотря на то, что они могут указывать на совершенно разные классы. Главное, чтобы у них был некий общий предок. Но вот что интересно. Когда мы начинаем пользоваться классами, мы создаем их экземпляры с помощью вызовов new. Остается неясным, зачем создавать экземпляр класса "Бытовое устройство", ведь товара с таким наименованием просто не существует! Какой покупатель станет платить за непонятный агрегат "Бытовое устройство"! Стало быть, это всего лишь удобный способ задания общего класса-предка. А раз не требуется его реализация, то можно просто создать пустой класс, в котором будут описаны (но не реализованы!) некоторые общие методы для работы с данными внутри класса, например методы "Установить величину напряжения питания" или "Включить устройство". Полученный класс будет называться абстрактным. Все классы - потомки абстрактного класса унаследуют его данные и методы, но должны будут сами предоставить код тех методов, которые класс-предок оставил нереализованными, т. е. абстрактными. Резонно спросить, почему базовый абстрактный класс не реализует методы самостоятельно, а отдает это на откуп своим потомкам. Ответ прост: для каждого устройства потребуется своя собственная методика включения и установки напряжения питания. К примеру, пылесос можно включить нажатием на кнопку, а сушилка для рук запускается автоматически, когда под нее подставляют руки. Такие тонкости может знать только класс самого устройства, а значит, ему и отвечать за реализацию соответствующих методов.
Другой часто приводимый пример - программа рисования геометрических фигур. Есть некоторая программа, задача которой состоит в рисовании точки, круга и квадрата. На ее примере мы и рассмотрим, как создавать абстрактные классы Java. Для начала создадим абстрактный класс Shape - предок всех фигур. Для каждой фигуры потребуются одинаковые данные: цвет (Color) и начальная точка (StartPoint). Чтобы нарисовать фигуру, необходимо создать метод Draw. Как вы, наверное, уже поняли, метод Draw абстрактного класса Shape будет пустым. Непосредственной его реализацией займется класс каждой фигуры. Для объекта класса "Точка" (Point) нужно нарисовать точку, для объекта класса "Круг" (Circle) - круг, а для объекта класса "Квадрат" (Square) - квадрат.
На языке Java абстрактный класс Shape будет описан следующим образом:
// Абстрактный класс "Фигура"
abstract public class Shape
{
// Цвет фигуры
int Color;
// Начальная точка фигуры
Coordinates StartPoint;
// Нарисовать фигуру
abstract public void Draw();
}
Обратите внимание на модификатор abstract в описании класса Shape и его метода Draw. Этим модификатором необходимо отмечать все абстрактные методы и классы. На тип Coordinates не обращайте внимания. Он взят лишь для примера. Предполагается, что где-то ранее он был описан как тип для задания координат x и y фигуры.
Теперь унаследуем от класса Shape необходимые нам классы фигур.
// Конкретный класс "Точка"
class Point extends Shape
{
// Цвет точки
int Color;
// Координаты точки
Coordinates StartPoint;
// Нарисовать точку
public void Draw()
{
// Здесь рисуется точка
}
}
// Конкретный класс "Круг"
class Circle extends Shape
{
// Цвет круга
int Color;
// Координаты центра круга
Coordinates StartPoint;
// Нарисовать круг
public void Draw()
{
// Здесь рисуется круг
}
}
// Конкретный класс "Квадрат"
class Square extends Shape
{
// Цвет квадрата
int Color;
// Координаты верхнего левого угла
Coordinates StartPoint;
// Нарисовать квадрат
public void Draw()
{
// Здесь рисуется квадрат
}
}
Теперь у нас есть все необходимое для рисования фигур. Можно создавать их в оперативной памяти и рисовать на экране:
Point point = new Point();
Circle circle = new Circle();
Square square = new Square();
point.Draw();
circle.Draw();
square.Draw();
В качестве дополнительного "бесплатного пирожка" мы получили возможность хранить все унаследованные от Shape объекты в одном массиве, не обращая внимания на их тип:
// Создаем массив
Shape[] shape = new Shape[3];
shape[0] = new Point();
shape[1] = new Circle();
shape[2] = new Square();
Мы даже можем вызывать методы Draw для всех элементов этого массива, не заботясь об их типе. Руководствуясь идеей полиморфизма, Java сам отследит тип объектов и вызовет корректный метод:
shape[0].Draw(); // Вызывает Point.Draw();
shape[1].Draw(); // Вызывает Circle.Draw();
shape[2].Draw(); // Вызывает Square.Draw();
Я специально упростил пример, оставив лишь голый шаблон, и не указал конструкторов классов, методы инициализации, методики рисования и т. п. Если хотите, в качестве упражнения можете дописать эту программу до конца.
Два замечания напоследок:
абстрактный метод не имеет тела, поскольку мы не знаем, что будет в нем;
если унаследовать класс от абстрактного, но оставить нереализованным хотя бы один его абстрактный метод, то унаследованный класс также будет абстрактным. Чтобы избавиться от "абстрактности", необходимо реализовать код для всех абстрактных методов абстрактного класса-предка.
Button
Класс Button представляет на экране кнопку. У этого класса имеется два типа конструктора. Первый из них создает кнопку без надписи, второй - с надписью:
Label first = new Label(); Label second = new Label("Some text"); Label third = new Label("Some text", Label.CENTER);
В любой момент можно создать или изменить надпись на кнопке, вызвав метод setLabel() и передав ему в качестве аргумента строку, которая будет написана на кнопке, а вызывая методы disable() и enable(), кнопку можно отключать (т.е. запрещать обработку нажатий) и включать.
Canvas
Если вам требуется создать область для вывода данных (изображений и текста) или рисования, то необходимо воспользоваться классом Canvas. Это виртуальный "холст" для художника и писателя. Класс Canvas также можно использовать как базовый класс для создания своих элементов интерфейса, например собственной кнопки.
Создание любого наследника от Canvas сводится к реализации методов paint() для прорисовки изображения и текста на рабочей поверхности и методов minimumSize() и preferredSize(), управляющих ее размерами.
Checkbox
Класс Checkbox отвечает за создание и отображение кнопок с независимой фиксацией. Это кнопки, имеющие два состояния: "включено" и "выключено". Щелчок на такой кнопке приводит к тому, что ее состояние меняется на противоположное. Если разместить несколько кнопок с независимой фиксацией внутри элемента класса CheckboxGroup, то вместо них мы получим кнопки с зависимой фиксацией, то есть группу кнопок, среди которых в один и тот же момент может быть включена только одна. Если нажать какую-либо кнопку из группы, то ранее нажатая кнопка будет отпущена. Вы наверняка сталкивались с такими группами в различных Windows-программах.
Ниже приведены фрагменты, иллюстрирующие создание как независимых кнопок, так и кнопок с независимой фиксацией:
// Создать две независимые отмечаемые // кнопки Checkbox first, second; first = new Checkbox("First checkbox"); first.setState(true); // Включить кнопку second = new Checkbox("Second checkbox"); ... // Создать две связанные радиокнопки Checkbox first, second; CheckboxGroup group = new CheckboxGroup(); first = new Checkbox //true - кнопка включена ("First radiobutton", group, true); second = new Checkbox //false - кнопка выключена ("Second radiobutton", group, false;
Когда потребуется программно установить активную кнопку, можно вызвать метод setCurrent(). Для определения активной в настоящий момент кнопки, вызывайте метод getCurrent().
Choice
Когда требуется создать раскрывающийся список, можно прибегнуть к помощи класса Choice. Создать его достаточно просто. К примеру, так выглядит реализация списка из трех пунктов:
Choice choice = new Choice(); choice.addItem("First"); choice.addItem("Second"); choice.addItem("Third");
Полезные методы класса Choice:
countItems() - считать количество пунктов в списке;
getItem(int) - возвратить строку с определенным номером в списке;
select(int) - выбрать строку с определенным номером;
select(String) - выбрать определенную строку текста из списка.
DataInputStream и DataOutputStream
DataInputStream и DataOutputStream относятся к так называемым фильтровым классам, то есть классам, задающим фильтры для чтения и записи определенных форматов данных. Фильтровые классы не работают сами по себе, а принимают или отсылают данные простым потокам FileInputStream, FileOutputStream и т. д. Обычное создание потока вывода данных на базе класса DataOutputStream сводится к одной строке:
DataOutStream is = new DataOutStream ( new FileOutputStream ( "data.dat" ));
После того как поток создан, в него можно выводить форматированные данные. Для этого в арсенале класса DataOutputStream имеется целый набор методов writeXXX() для записи различных данных, где XXX - название типа данных. Вот так выглядит фрагмент кода для вывода в созданный нами поток data.dat:
dos.writeDouble(doubleVar); dos.writeInt(intVar); dos.writeChars(StringVar); dos.close();
Мне кажется, комментарии излишни, поскольку имена методов сами говорят о том, какой тип данных они выводят.
Ну а теперь проверим, как записались наши данные в data.dat, и заодно посмотрим, какие методы для чтения имеются в фильтровом потоке ввода данных DataInputStream:
DataInputStream dis = new DataInputStream ( new FileInputStream ("data.dat" )); doubleVar = dis.readDouble(); intVar = dis.readInt(); StringVar = dis.readLine(); dis.close();
Как видно из примера, методы чтения readXXX() класса DataInputStream практически полностью соответствуют методам writeXXX() класса DataOutputStream, за исключением методов writeChars и readLine, имеющим по неясным мне причинам различные названия.
Dialog
Для поддержания связи с пользователем применяется класс Dialog, на основе которого можно создавать диалоговые панели. В отличие от простых окон диалоговые панели зависят от того или иного окна, и поэтому в их конструкторах присутствует параметр-ссылка на окно класса Frame, владеющее этой диалоговой панелью. Как и в случае с классом Frame, класс Dialog сам по себе практически не применяется. Обычно от него наследуется новый класс, экземпляр которого и создается:
class NewDialog extends Dialog
class NewDialog extends Dialog { ... NewDialog(Frame frame, String title) { super(dw, title, false); } ... }
Поскольку диалоговые панели могут быть модальными (блокирующими работу с другими окнами) и немодальными, в конструкторах класса Dialog последний параметр определяет модальность. Если он равен true, то диалоговое окно создается модальным, в противном случае оно позволяет переключиться на другое окно приложения.
Помимо общих для всех окон методов getTitle(), setTitle(), isResizable() и setResizable() у класса Dialog имеется метод isModal(), возвращающий true, если диалоговая панель модальна.
FileInputStream и FileOutputStream
Большинству программистов не просто оперировать сравнительно новым понятием "потоки". Однако два класса - FileInputStream и FileOutputStream - обычно понятны всем, поскольку это ни что иное, как потоки ввода из файла и вывода в него. Мы начнем с рассмотрения FileInputStream. Это довольно универсальный класс, открывающий поток ввода по имени файла. Замечательной особенностью этого класса можно считать возможность создания потока ввода данных по объекту класса File и файловому дескриптору FileDescriptor. Вот, оказывается, какое у этих двух классов применение!
Второй класс, FileOutputStream, служит для записи данных в файл и во многом схож с FileInputStream. Объекты класса FileOutputStream также создаются по имени файла или по объектам File или FileDescriptor. Вот так выглядит простейшая программа на языке Java, копирующая содержимое одного файла в другой файл:
import java.io.*; class CopyFile { public static void main (String[] args) { try { File inFile = new File("infile.dat"); File outFile = new File("outfile.dat"); FileInputStream inStream = new FileInputStream(inFile); FileOutputStream outStream = new FileOutputStream(outFile);
int c; while ((c = inStream.read()) != -1) { outStream.write(c); } inStream.close(); outStream.close(); } catch (FileNotFoundException ex) {} } }
В данном случае мы использовали метод создания потоков с промежуточными объектами класса File как пример использования, но ничего не мешает сделать это более простым способом:
FileInputStream inStream = new FileInputStream("infile.dat"); FileOutputStream outStream = new FileOutputStream("outfile.dat");
Результат будет одним и тем же - будут созданы файловые потоки infile.dat и outfile.dat.
Frame
Одним из самых важных классов пользовательского интерфейса можно считать класс Frame. С его помощью реализуются окна для Java-программ и аплетов. В отличие от других классов пользовательского интерфейса, экземпляры класса Frame создаются редко. Обычно от него наследуется новый класс, а уже затем создается экземпляр нового класса:
public class NewWindow extends Frame { TextArea output; public NewWindow (String title) { super(title); } ... public static void main (String args[]) { // Создание экземпляра нового класса NewWindow win = new NewWindow("New Window Class"); // Показать его на экране win.show(); } }
Полезные методы класса Frame:
pack() - изменить размер компонентов в окне так, чтобы их размер был максимально приближен к желаемому;
getTitle() - возвратить заголовок окна;
setTitle(String) - установить заголовок окна;
getIconImage() - возвратить пиктограмму окна;
setIconImage(Image) - установить пиктограмму окна;
getMenuBar() - возвратить объект меню окна;
setMenuBar(MenuBar) - установить меню окна;
remove(MenuComponent) - убрать определенный компонент из меню окна;
isResizable() - возвратить true, если размер окна можно изменять, иначе - false;
setResizable(boolean) - разрешить изменение размеров окна;
getCursorType() - возвратить текущий тип курсора мыши для окна;
setCursor(int) - установить тип курсора мыши для окна.
Friendly
В Java существует еще один модификатор доступа - friendly. Этот модификатор не пишется явно, но подразумевается, если не указан никакой другой модификатор доступа. При использовании friendly к данным может обратиться любой класс и метод в той же самой упаковке.
* Ранее я упоминал о Java как об интерпретируемом языке. Это касается только времени исполнения. А для получения готовых к исполнению классов исходные тексты на Java должны быть откомпилированы в промежуточный код, называемый байт-кодом.
InputStream и OutputStream
Два класса, InputStream и OutputStream, из упаковки java.io служат предками для большинства классов потоков ввода/вывода языка Java, поэтому понимание их структуры и возможностей важно для программиста.
Абстрактный класс InputStream предоставляет начальный интерфейс к потоку ввода данных и частично реализует его. С помощью набора методов, реализуемого классом InputStream, можно читать байты или массивы байтов, узнавать количество доступных для чтения данных, отмечать место в потоке, где в настоящий момент происходит чтение, сбрасывать указатель текущей позиции в потоке и пропускать ненужные байты в потоке.
Открыть поток ввода можно, создав объект класса InputStream. Закрыть его можно двумя способами: дождаться, пока сборщик мусора (garbage collector) Java будет искать в памяти компьютера неиспользуемые классы и закроет ваш поток, или же закрыть его методом close(), как обычно и делается.
Для создания потоков ввода применяется другой класс - OutputStream, который, как и InputStream, является абстрактным. Методы, предоставляемые OutputStream, позволяют записывать байты и массивы байтов в поток вывода. Как и InputStream, поток OutputStream открывается, когда вы его создаете, и закрывается либо сборщиком мусора, либо методом close().
На базе двух упомянутых выше классов InputStream и OutputStream наследуются несколько классов с конкретной спецификой применения, как, например, классы FileInputStream и FileOutputStream для записи и чтения данных из файла.
Интерфейсы
Java предоставляет программисту еще одно средство, родственное классам, - интерфейсы. Интерфейс - это набор абстрактных методов, которые не содержат никакого кода. По своему предназначению интерфейсы похожи на абстрактные классы, хотя между ними имеются некоторые существенные различия. Так, например, интерфейсы, в отличие от абстрактных классов, могут быть только public или private. Методы, описанные внутри интерфейсов, всегда доступны (public) и абстрактны (abstract). Данные, декларированные в интерфейсе, изначально имеют атрибуты final, public и static, т. е. неизменяемы. Иногда это удобно, а иногда накладывает серьезные ограничения на применение интерфейсов. Но тут уж ничего не поделаешь - таковы правила языка.
Интерфейсы дают возможность программисту описывать наборы методов, которые должен реализовать класс. К примеру, стандартный интерфейс для создания многопоточных приложений Runnable задается следующим образом:
shape[0].Draw(); // Вызывает Point.Draw();
shape[1].Draw(); // Вызывает Circle.Draw();
shape[2].Draw(); // Вызывает Square.Draw();
Данное описание устанавливает прототип для метода Run, необходимого для запуска нового потока выполнения.
Для того чтобы использовать интерфейсы, от них должен быть унаследован класс, который реализует все шаблоны абстрактных методов, определенных в интерфейсе. Это можно сделать, использовав ключевое слово implements. Так, описание класса потока может выглядеть следующим образом:
public NewThread implements Runnable
{
public void run()
{
// Здесь запускается новый поток
// выполнения
}
}
Обратите внимание: ключевое слово implements (реализует) стоит в том месте, где обычно располагается ключевое слово extends, описывающее отношение наследования. Но встречаются и случаи, когда какой-нибудь класс наследует методы другого класса и одновременно реализует какой-нибудь интерфейс:
public class MyApplet
extends Applet implements Runnable
После такого упрощенного введения позволю себе описать понятия и синтаксис интерфейсов снова, но уже более формально. Итак, как уже было сказано, интерфейс - это набор описаний методов без реализации и констант. Такое средство может понадобиться для организации наследования из любого места иерархии. Описав, к примеру, интерфейс CustomLook с методом CustomPaint для создания элементов интерфейса с новым внешним видом, мы можем создавать по-новому выглядящие элементы на базе стандартных. При этом можно с одинаковым успехом создать на базе интерфейса CustomLook новый вид кнопки или новую строку ввода, и при этом не имеет значения, что кнопка и строка ввода располагаются в разных местах иерархии классов. Главное то, что их объединяет, - необходимость реализовать собственный метод CustomPaint для нестандартного отображения элемента. В связи с этим отметим следующие случаи применения интерфейсов:
если различные классы, расположенные в разных местах иерархии, имеют некую общность;
если несколько классов должны реализовать некий общий набор методов;
если требуется создать интерфейс без раскрытия деталей реализации класса.
Интерфейсы описываются по такой схеме:
public interface CustomLook
{
public abstract void NotifyStartPaint();
public abstract void CustomPaint ();
}
После того как интерфейс декларирован, его имя можно использовать наряду со стандартными типами и классами. Возвращаясь к примеру создания элементов пользовательского интерфейса с новым внешним видом, можно сказать, что вы имеете право создавать переменные типа CustomLook. Возникает интересная возможность: вы можете хранить в массиве элементов CustomLook любые классы, унаследованные от него (полиморфизм), и передавать эти классы в качестве параметра типа CustomLook, иначе говоря, приводить их к типу базового интерфейса, не теряя при этом их особенностей. И все это можно проделывать для классов, никак не связанных в рамках иерархии. Разве такое возможно в Си++?
Для облегчения понимания рассмотрим простой пример - создание элементов пользовательского интерфейса нестандартного вида. Сначала уточним задачу. Имеются несколько стандартных элементов интерфейса пользователя: кнопка (OldButton), строка ввода (OldInputLine) и пункт меню (OldMenuItem). Все эти элементы унаследованы от разных классов, никак не связанных между собой. Требуется создать на базе указанных выше элементов новые, отличающиеся по внешнему виду. Для этого нам потребуется, чтобы каждый новый элемент установил метод, отслеживающий начало рисования элемента на экране NotifyStartPaint, и новый метод рисования своего интерфейса CustomPaint. Оформим все новые требования как интерфейс CustomLook:
public interface CustomLook
{
public abstract void NotifyStartPaint();
public abstract void CustomPaint ();
}
На базе интерфейса CustomLook и старых элементов мы создаем новые элементы: кнопку (NewButton), строку ввода (NewInputLine) и пункт меню (NewMenuItem). Вот окончательный вариант каркаса программы:
public class NewButton
extends OldButton implements CustomLook
{
public void NotifyStartPaint()
{
// Код для перехвата начала рисования
}
public void CustomPaint ();
}
{
// Код для рисования кнопки нового
// внешнего вида
}
}
public class NewInputLine
extends OldInputLine implements CustomLook
{
public void NotifyStartPaint()
{
// Код для перехвата начала рисования
}
public void CustomPaint ();
}
{
// Код для рисования строки ввода
// нового внешнего вида
}
}
public class NewMenuItem
extends OldMenuItem implements CustomLook
{
public void NotifyStartPaint()
{
// Код для перехвата начала рисования
}
public void CustomPaint ();
}
{
// Код для рисования пункта меню нового
// внешнего вида
}
}
Таким образом, мы получили новые классы, как и раньше, не связанные между собой, но имеющие одинаковую функциональность. Их можно сохранить в массиве элементов типа CustomLook, несмотря на то, что все они имеют разных предков.
Кратко напомним ключевые моменты использования интерфейсов:
программы, выполненные на языке Java, могут использовать интерфейсы, если нежелательно использование общего предка или добавление новых методов к общему абстрактному классу-предку Object;
переменные типа какого-либо интерфейса могут содержать ссылки на классы, унаследованные от этого интерфейса;
недостаточно, чтобы класс реализовал методы интерфейса; кроме того, из описания должно быть ясно, что класс представляет собой реализацию некоего интерфейса, иначе считается, что этот класс не реализует интерфейс;
если класс, который наследует интерфейс, не полностью реализует набор его методов, он становится абстрактным и к нему применимы все правила для абстрактных классов.
Использование компонентов интерфейса
Теперь рассмотрим ключевые моменты в использовании классов каждого компонента в отдельности. Отметим, что здесь не приводятся способы обработки сообщений от компонентов, поскольку этим мы будем заниматься в наших практических занятиях.
Элементарные события в Java-программе
Действия пользователя | Событие | Вызов обработчика события | |||
Пользователь произвел некоторое действие, требующее получения некоторого результата, например, нажал кнопку | ACTION_EVENT | public boolean action(Event evt /*Событие, которое вызвало действие*/, Object what /*Ссылка на объект, который вызвал появление события*/) | |||
Указатель мыши переместился внутрь окна Java-программы | MOUSE_ENTER | public boolean mouseEnter(Event evt /*Сообщение, вызывавшее обработчик*/, int x /*x-координата указателя мыши*/, int y /*y-координата указателя мыши*/) | |||
Указатель мыши переместился за пределы окна Java-программы или переместился на какой-либо элемент пользовательского интерфейса | MOUSE_EXIT | public boolean mouseExit(Event evt /*Сообщение, вызывавшее обработчик*/, int x /*x-координата указателя мыши*/, int y /*y-координата указателя мыши*/) | |||
Указатель мыши движется в окне Java-программы | MOUSE_MOVE | public boolean mouseMove(Event evt /*Сообщение, вызывавшее обработчик*/, int x /*x-координата указателя мыши*/, int y /*y-координата указателя мыши*/) | |||
Произошло нажатие кнопки мыши в окне Java-программы | MOUSE_DOWN | public boolean mouseDown(Event evt /*Сообщение, вызывавшее обработчик*/, int x /*x-координата указателя мыши*/, int y /*y-координата указателя мыши*/) | |||
Произошло отпускание нажатой ранее кнопки мыши | MOUSE_UP | public boolean mouseUp(Event evt /*Сообщение, вызывавшее обработчик*/, int x /*x-координата указателя мыши*/, int y /*y-координата указателя мыши*/) | |||
Кнопка мыши нажата, и указатель мыши движется в окне Java-программы | MOUSE_DRAG | public boolean mouseDrag(Event evt /*Сообщение, вызывавшее обработчик*/, int x /*x-координата указателя мыши*/, int y /*y-координата указателя мыши*/) | |||
Нажата кнопка на клавиатуре | KEY_PRESS | public boolean keyDown(Event evt /*Сообщение, вызывавшее обработчик*/, int key /*Код нажатой клавиши*/) | |||
Нажата функциональная клавиша, например <F1> или "стрелка вправо" | KEY_ACTION | public boolean keyDown(Event evt /*Сообщение, вызывавшее обработчик*/, int key /*Код нажатой клавиши*/) | |||
Отпущена ранее нажатая кнопка клавиатуры | KEY_RELEASE | public boolean keyUp(Event evt /*Сообщение, вызывавшее обработчик*/, int key /*Код отпущенной клавиши*/) | |||
Отпущена ранее нажатая функциональная клавиша | KEY_ACTION_ | public boolean keyUp(Event evt /*Сообщение, вызывавшее RELEASE обработчик*/, int key /*Код отпущенной клавиши*/) | |||
Компонент получил фокус, т. е. любое действие пользователя переназначено теперь на этот компонент | GOT_FOCUS | public boolean gotFocus(Event evt /*Событие, которое вызвало получение фокуса*/, Object what /*Почти всегда равно нулю*/) | |||
Компонент "потерял" фокус, т. е. любое действие пользователя переназначено теперь на другой компонент | LOST_FOCUS | public boolean lostFocus(Event evt /*Событие, которое вызвало потерю фокуса*/, Object what /*Почти всегда равно нулю*/) |
Классы
Рассмотрим теперь, как описываются основные базовые строительные блоки языка Java - классы. Схема синтаксиса описания класса такова:
[Модификаторы]
class ИмяКласса
[extends ИмяСуперкласса]
[implements ИменаИнтерфейсов]
{
Данные класса;
Методы;
}
где:
Модификаторы - ключевые слова типа static, public и т.п., модифицирующие поведение класса по умолчанию;
ИмяКласса - имя, которое вы присваиваете классу;
ИмяСуперкласса - имя класса, от которого наследуется ваш класс;
ИменаИнтерфейсов - имена интерфейсов, которые реализуются данным классом (об этом в следующем занятии).
Типичный пример класса мы уже приводили ранее. Это класс аплета, выводящего строку на экран.
Схема описания методов класса сродни описанию простых функций в языках C и C++:
[Модификаторы]
ВозвращаемыйТип ИмяМетода
(Список Параметров)
{
[Тело Метода]
}
В показанном ранее примере описан всего один общедоступный метод Paint, возвращающий тип void и принимающий один параметр graph типа Graphics.
Как и в C++, в классах Java имеются конструкторы. Их назначение полностью совпадает с назначением аналогичных методов C++. Конструкторы могут быть перегружены (overload), т. е. в одном классе может быть несколько конструкторов, отличающихся передаваемыми параметрами.
В отличие от C++ в языке Java предусмотрен единственный способ распределения памяти - оператором new. В отношении выделения блоков памяти во многом действуют те же правила, что и в C++. Но есть и исключение: в Java имеется возможность динамического задания имени создаваемого класса, как говорится, "на лету":
ClassVar = new ("Class" + "Name");
Здесь операцией конкатенации (объединения) строк создается новая строка "ClassName", которая передается оператору new в качестве параметра. В свою очередь, new создает класс с именем ClassName типа Object (каждый раз, когда тип создаваемого объекта не указан, предполагается тип Object). По мнению автора, разработчики Java позаимствовали операцию конкатенации строк "+" из языка программирования Pascal. Кстати, о типе Object. Это базовый тип для любого класса в Java (заимствовано из идеологии языка SmallTalk). Даже когда вы, создавая новый класс, явно не указываете его предка, предполагается, что это класс Object.
Классы и их отдельные члены могут быть статическими. В этом случае они помечаются ключевым словом static. Преимущество статических членов состоит в том, что они становятся разделяемыми между всеми классами-потомками и экземплярами класса. Это значит, что, ссылаясь на несколько унаследованных классов или несколько экземпляров, на самом деле вы ссылаетесь на один и тот же член класса, расположенный в одном и том же участке памяти. В дополнение к стандартным статическим определениям в Java есть инициализаторы - блоки кода, помеченные ключевым словом static. Их задача - инициализация статических переменных. При загрузке класса сначала выполняются блоки инициализации, а уже потом начинается присвоение значений простым переменным, которые инициализируются в порядке их описания. То же справедливо и для блоков инициализации. В примере, показанном ниже, переменные инициализируются в следующем порядке: xxx, yyy.
class StaticClass
{
short zzz = 10;
static int xxx;
static float yyy;
static
{
xxx = 12345;
yyy = 3.1415;
}
}
Далее выполняется блок инициализации, и уже потом производится инициализация переменной zzz.
Классы элементов меню
Едва ли какое-то современное приложение сможет обойтись без полосы меню в окне. Поэтому в языке Java имеются сразу несколько классов для создания меню, унаследованных от класса MenuComponent. Первый из них, MenuBar, это основной класс всей системы меню, служащий контейнером для других классов. Когда вы создаете окно, то в качестве ссылки на добавляемое меню нужно передать ссылку на класс MenuBar.
Следующий класс Menu на полосе меню отображается как пункт выбора, который, если по нему щелкнуть, раскрывается в виде странички с пунктами выбора (pop-up menu). Сами же элемены выбора меню обычно реализуются как экземпляры классов MenuItem (простой элемент выбора) и CheckboxMenuItem (отмечаемый элемент выбора). Взгляните на пример создания полнофункциональной полосы меню:
// Добавление полосы меню в окно класса Frame public class NewWindow extends Frame { public NewWindow() { // Создаем полосу меню MenuBar menuBar = new MenuBar(); // Создаем первое меню // Второй аргумент true говорит // о том, что меню отрываемое. // Эта опция пока не работает // в Windows Menu menu1 = new Menu("Menu 1", true); menuBar.add(menu1); // Создать и добавить первый пункт // первого меню // Это обычный элемент меню MenuItem item1_1 = new MenuItem("Item #1"); menu1.add(item1_1); // Создать и добавить второй пункт // первого меню // Это отмечаемый элемент меню CheckboxMenuItem item1_2 = CheckboxMenuItem("Item #2"); menu1.add(item1_2); // Создать и добавить второе меню Menu menu2 = new Menu("Menu 2"); menuBar.add(menu2); // Создать и добавить меню // следующего уровня Menu nextLevel = New Menu("Next Level Menu"); menu2.add(nextLevel); } ... }
Как видите, создание меню хотя и муторный, но вовсе не сложный процесс. Если вы обратили внимание, то во второе меню добавляется не пункт выбора класса MenuItem, а меню класса Menu. Это приводит к тому, что при нажатии на пункт 2 полосы меню рядом появляется следующее меню, выбрав из которого Next Level Menu получили очередное меню. Таким способом в Java реализовано каскадирование меню.
Контейнеры
Любой компонент, требующий показа на экране, должен быть добавлен в класс-контейнер. Контейнеры служат хранилищем для визульных компонентов интерфейса и других контейнеров. Простейший пример контейнера - класс Frame, объекты которого отображаются на экране как стандартные окна с рамкой.
Чтобы показать компонент пользовательского интерфейса в окне, требуется создать объект-контейнер, например окно класса Frame, создать требуемый компонент и добавить его в контейнер, а уже затем отобразить его на экране. Несмотря на столь длинный список действий, в исходном тексте этот процесс занимает всего несколько строк:
// Создается текстовый объект с надписью "Строка" Label text = new Label("Строка"); // Объект добавляется в некий контейнер SomeContainer.add (text); // Отображается контейнер SomeContainer.Show(); ...
Все достаточно просто. Лишь поясним метод add(). Этим методом и производится собственно добавление элемента интерфейса в окно контейнера. В нашем примере мы использовали самый простой вариант этого метода, принимающий единственный аргумент-ссылку на вставляемый объект. Но есть еще два варианта метода add() с двумя аргументами. В первом из них передаются порядковый номер в списке управляющих элементов контейнера, куда будет вставлен добавляемый элемент, и ссылка на вставляемый объект. Во втором варианте первый аргумент - строка, указывающая место в окне, где должен быть размещен вставляемый объект интерфейса, а второй - ссылка на вставляемый объект. Строк, допустимых в качестве первого аргумента, всего пять: North, South, East, West и Center. Подробнее мы рассмотрим их в разделе, посвященном раскладкам.
// Вставить элемент в окно контейнера add(someControl); // Вставить элемент после других // элементов в контейнере add(-1, someControl); // Вставить элемент в окно контейнера // у его верхней границы add("North", someControl);
Само собой разумеется, коли есть методы для добавления визуальных элементов, имеются и противоположные им методы, удаляющие элементы из окна контейнера. Их два: метод удаления конкретного элемента remove(), принимающий в качестве параметра ссылку на удаляемый объект, и метод удаления всех визуальных компонентов removeAll().
Коротко о Java
Язык Java - это объектно-ориентированный язык программирования, ведущий свою историю от известного языка C++. Но в отличие от последнего Java является языком интерпретируемым, программы, написанные на нем, способны работать в разных местах сети и не зависят от платформы, на которой выполняются написанные на нем приложения. Java сознательно избегает арифметики с указателями и прочих ненадежных элементов, которыми изобилует C++, поэтому, разрабатывая на нем приложения, вы предотвратите многие проблемы, обычные при создании программного обеспечения.
В терминах языка Java маленькое приложение, которое встраивается в страницу Web, называется аплет. Собственно говоря, создание аплетов - основное применение для Java. Аплеты снискали себе звание подлинных украшений для Web. Аплет может быть и окном анимации, и электронной таблицей, и всем, что только можно себе представить. Но это не значит, что на Java нельзя писать нормальные приложения с окнами. Этот язык программирования изначально был создан для обычных приложений, выполняющихся в Internet и интрасетях, и уж потом стал использоваться для изготовления аплетов.
Элементарные строительные блоки в Java называются классами (как и в C++). Класс состоит из данных и кода для работы с ними. В средствах для разработки на языке Java все стандартные классы, доступные программисту, объединены для удобства в упаковки - еще одни элементарные блоки Java-программ.
Вот простейшая программа, приводимая во многих учебниках по Java:
class JavaTest
{
public static void main(String args[])
{
System.out.println("Hello, World!");
}
}
Запустим компилятор Java под названием javac и получим готовый класс Java - JavaTest.class. Если вы хотите посмотреть, как этот класс работает, выполните его при помощи команды java JavaTest. При этом необходимо набрать имя запускаемого класса точно так, как оно написано в исходном тексте программы, т.е. с соблюдением регистра, иначе вы получите сообщение об ошибке.
Рассмотрим поэлементно исходный текст нашего примера. Вся программа состоит из одного класса с именем JavaTest. У этого класса имеется единственный метод main, аналогичный функции main в языках программирования C и C++ и определяющий место, с которого программа начинает выполняться (так называемая точка входа). Модификатор доступа public перед именем метода main указывает на то, что этот метод доступен всем классам, желающим его вызвать, независимо от прав доступа и от места их расположения. Модификатор static говорит о том, что для всех экземпляров класса CafeTest и в наследуемых от него классах существует лишь один метод main, разделяемый между всеми классами, которые, возможно, будут унаследованы от JavaTest. Это помогает избежать появления множества точек входа в программу, что вызовет ошибку.
Через переменную-массив args типа String (строка) передаются параметры командной строки класса. В Java первый элемент списка параметров соответствует первому параметру, а не имени запускаемой программы, как это принято в языках C и C++. Доступ к нему можно осуществить через выражение args[0]. Строка System.out.println("Hello, World!") посылает строку текста в стандартный поток вывода, т. е. на экран. Мы отправляем сообщение стандартному классу System, который отвечает за основные системно-независимые операции, например вывод текста на консоль. А уже из этого класса мы вызываем класс стандартного потока вывода. Следом идет вызов метода println, который, собственно, и отображает строку текста на экране монитора, по завершению чего переводит курсор на следующую строку. В Java все методы класса описываются только внутри него. Таким образом, отпадает необходимость в передвижении по тексту в поиске методов классов.
Обработанные классы компилятор записывает в отдельные файлы. Так, если вы опишете в одном исходном файле сразу несколько классов, то в результате компиляции получите несколько файлов с расширением class, по одному для каждого класса, и каждый из них будет иметь то же имя, что и соответствующий класс.
Если вы освоите содержимое упаковки Java с именем java.awt, которая расшифровывается как Abstract Windowing Toolkit (Набор абстрактной работы с оконной системой), то вам откроются неисчислимые возможности по созданию интерфейсов и оконной графики. Эта упаковка обеспечивает машинно-независимый интерфейс управления оконной системой любой оконной операционной системы. В состав java.awt входят более 40 классов, отвечающих за элементы графической среды пользователя (GUI). В основном awt применяется при написании аплетов для страниц Web. При просмотре страницы на Web-сервере аплет передается на машину пользователя, где и запускается на выполнение.
Теперь мы можем рассмотреть аплет, который делает то же самое, что и уже рассмотренный ранее пример, т. е. выводит строку на экран:
import java.awt.*;
public class JavaTest
extends java.applet.Applet
{
public void init() {}
public void paint(Graphics graph)
{
graph.drawString("Hello, World!",20,30);
}
}
Первой строкой в аплет включаются все необходимые классы из упаковки java.awt, о которой мы только что говорили. Ключевое слово import имеет приблизительно то же значение, что и оператор #include языков C и C++. Далее следует описание класса нашего аплета, предваряемое модификатором доступа public. Его задача - дать возможность использовать наш класс извне, т. е. запускать его из внешних программ. Если этого слова не будет, компилятор выдаст сообщение об ошибке, указав, что аплету требуется описание интерфейса доступа. Далее следует ключевое слово extends и название класса. Так в Java обозначается процесс наследования. Этим словом мы указываем компилятору унаследовать (расширить) стандартный класс java.applet.Applet, отвечающий за создание и работу аплета. Метод init вызывается в процессе инициализации аплета. Сейчас этот метод пуст, но впоследствии, возможно, вы будете пользоваться им для своих нужд. За отображение строки отвечает другой метод - paint. Он вызывается в тот момент, когда требуется перерисовать данные на экране. Здесь с помощью метода drawString стандартного класса Graphics рисуется строка "Hello, World!" с экранными координатами 20,30.
Label
С помощью класса Label можно создавать текстовые строки в окне Java-программ и аплетов. По умолчанию текст будет выровнен влево, но, используя метод setAlignment с параметрами Label.CENTER и Label.RIGHT, можно выровнять строку по центру или по правому краю. Вы можете задать выводимый текст либо при создании объекта класса Label, либо создать пустой объект и уже затем определить его текст вызовом метода setText(). То же касается и выравнивания, для управления которым имеется метод setAlignment(). Вот примеры использования трех различных конструкторов для объекта класса Label:
Label first = new Label(); Label second = new Label("Some text"); Label third = new Label("Some text", Label.CENTER);
Вы можете узнать о текущем тексте и его выравнивании, вызвав методы getText() и getAlignment() соответственно.
List
Класс List (список) по назначению очень похож на класс Choice, но предоставляет пользователю не раскрывающийся список, а окно с полосами прокрутки, внутри которого находятся пункты выбора. Любой из этих пунктов можно выбрать двойным щелчком мыши или, указав на него мышью, нажать клавишу <Enter>. Причем можно сделать так, что станет возможным выбор нескольких пунктов одновременно.
Создание объекта класса List может происходить двумя способами. Вы можете создать пустой список и добавлять в него пункты, вызывая метод addItem. При этом размер списка будет расти при добавлении пунктов. Другой способ позволяет сразу ограничить количество видимых в окне списка пунктов. Остальные пункты выбора можно увидеть, прокрутив список. Вот пример для обоих способов:
// Создание списка без ограничения // размера List list1 = new List(); list.addItem("First"); list.addItem("Second"); list.addItem("Third"); list.addItem("Forth"); // Создание списка, ограниченного // размером в два пункта выбора List list2 = new List(2,true); list.addItem("First"); list.addItem("Second"); list.addItem("Third"); list.addItem("Forth");
Полезные методы класса List:
getItem(int) - считать текст пункта выбора;
countItems() - посчитать количество пунктов выбора в списке;
replaceItem(String, int) - заменить элемент выбора в указанной позиции;
clear() - очистить список;
delItem(int) - убрать из списка определенный пункт;
delItems(int, int) - убрать элементы выбора с номерами, входящими в интервал от номера, указанного первым параметром, до номера, указанного вторым параметром;
getSelectedIndex() - узнать порядковый номер выделенного пункта; если возвращается -1, то выбрано несколько пунктов;
getSelectedIndexes() - возвратить массив индексов выделенных пунктов;
getSelectedItem() - прочитать текст выделенного пункта выбора;
getSelectedItems() - возвратить массив строк текста выделенных пунктов;
select(int) - выделить пункт с определенным номером;
deselect(int) - снять выделение с определенного пункта;
isSelected(int) - возвратить значение true, если пункт с указанным номером выделен, иначе возвратить false;
getRows() - возвратить количество видимых в списке строк выбора;
allowsMultipleSelections() - возвратить true, если список позволяет множественный выбор;
setMultipleSelections() - включить режим разрешения множественного выбора;
makeVisible(int) - сделать элемент с определенным номером видимым в окне списка;
getVisibleIndex() - возратить индекс элемента выбора, который последним после вызова метода makeVisible() стал видимым в окне списка.
Мир сообщений
Как известно, работа любого Windows-приложения основана на обработке сообщений. Сообщения - это асинхронные (т. е. они могут произойти в любой момент) вызовы специальных методов, называемых обработчиками сообщений. Через вызовы обработчиков сообщений система уведомляет, что в программе произошло некоторое событие, например в окне программы был произведен щелчок мышью. Если система Windows имеет список из сотен событий, которые могут произойти в процессе работы программы, то Java-приложения в этом плане гораздо проще. Всего лишь 13 элементарных событий могут случиться в программе Java.
Эти так называемые элементарные события в свою очередь могут вызвать возникновение более сложных событий. К примеру, если пользователь щелкнул мышью в окне Java-программы, то происходит событие MOUSE_DOWN, но если он это сделал на полосе прокрутки, то порождается другое событие, скажем, SCROLL_LINE_UP. Это уже вторичное событие.
Пусть вас не пугает, что описания сообщений выглядят несколько запутанно, - вам очень редко придется иметь с ними дело. Сам язык Java предлагает вам посильную помощь, вызывая для каждого из описанных выше собщений отдельный метод-обработчик. Все, что вам требуется, это описать в своем классе нужный обработчик события, а виртуальная машина Java сама вызовет его в тот момент, когда это событие произойдет. Единственное условие при написании таких методов обработки событий - соблюдайте синтаксис, который задан спецификацией языка Java. Если же вы ошибетесь в описании, то в лучшем случае компилятор сообщит об этом, в худшем - будет создан перегруженный метод (помните, Java, как и Си++, допускает перегруженные методы), который не будет ни разу вызван.
Заметим, что аргумент evt типа Event - это то самое событие, которое заставило систему вызвать обработчик того или иного события. Этот параметр редко используется, если только нет необходимости узнать какие-то частности относительно произошедшего события, как, например, точное время, когда оно произошло.
Еще один важный момент, который необходимо помнить, это возвращаемое обработчиками значение. Если сообщение обработано вашим методом, то он возвращает значение true, иначе сообщение будет передаваться по цепочке вверх по иерархии компонентов, пока не будет обработано. За редким исключением в обработчиках событий своих программ всегда будет возвращаться true.
Модификаторы доступа
В языке C++ определены три модификатора доступа: private, protected и public. Язык Java обладает тем же набором модификаторов, но расширенным модификатором friendly. Однако все эти модификаторы ведут себя несколько по-другому, в основном из-за того, что в семантику Java были введены новые модули - упаковки (packages), о которых мы уже говорили. Каждая упаковка содержит в себе набор классов и интерфейсов для выполнения какой-либо определенной задачи. Так, например, упаковка java.applet отвечает за работу аплетов, что явствует из ее названия. Соответственно упаковка java.io хранит в себе все необходимое для выполнения операций ввода-вывода и т. д. Модификаторы доступа стали контекстно-чувствительными, т. е. зависят от того, размещается ли класс, к которому производится доступ, в одной упаковке с вызывающим его классом или нет.
В таблице отражена возможность доступа к данным из того или иного класса. Расшифруем теперь то, что здесь изображено. В первом столбце приводятся модификаторы доступа данных и методов, к которым производится обращение. Столбец "Класс" говорит нам, что сам класс имеет право обращаться к своим данным и методам независимо от того, какой модификатор доступа им присвоен. Следующий столбец "Наследник" объясняет, что класс-наследник может обращаться к данным и методам своего предка, исключительно если они имеют спецификатор доступа protected или public, причем в случае protected оговаривается, что доступ к методам и данным protected класса предка возможен, лишь если класс-наследник располагается с ним в той же самой упаковке, в противном случае компилятор не позволит вам доступ. Столбец "Упаковка" говорит о том, что все классы, располагающиеся в одной и той же упаковке, могут обращаться к данным и методам друг друга, если только они не объявлены как private. При этом совершенно не имеет значения иерархия наследования. И последний столбец показывает, что классы, расположенные на одной машине сети, могут обращаться лишь к общедоступным данным и методам, размещенным на другой сетевой машине. Ну а теперь несколько подробнее о каждом из модификаторов.
Начинаем программировать на языке Java.
Часть 2
Разобравшись с классами языка Java и с тем, как они описываются непосредственно в программе, это занятие мы посвятим частному случаю классов - абстрактным классам, а также элементам, родственным классам, называемым интерфейсами. Кроме того, вы познакомитесь с таким замечательным средством языка Java, как обработка исключительных ситуаций.
Часть 3
Вот мы и подошли вплотную к важнейшему этапу - созданию элементов ввода/вывода. Для этого язык Java предлагает целую гамму классов потоков ввода/вывода. Именно их мы и рассмотрим на этом занятии.
Для языков Си++ и Java характерно использование потоков ввода/вывода вместо файлов, как это делалось ранее. Поток ввода/вывода - это некоторый условный канал, по которому отсылаются и получаются данные. При этом совершенно не важно, что стоит за конкретным потоком: файл, блок памяти, экран и т. д. С точки зрения программиста, поток представляет собой ленточный транспортер, на который можно последовательно помещать куски данных, а лента доставит их по назначению. Остальные детали реализации не важны. Такая концепция помогает унифицировать методы работы со всеми устройствами ввода/вывода, сводя все к методам открытия потока, его закрытия, чтения данных из потока и запись данных в него. Конечно же, существуют исключения, но в целом разработчики языка Java старались соблюдать условия унифицированного интерфейса управления потоком. Во всю эту концепцию не вписываются лишь два класса: File и FileDescriptor. Первый осуществляет системные операции, как-то: создание, удаление, переименование файлов, и часто служит промежуточным звеном при использовании потоков. А вот второй, FileDescriptor, - это совсем отдельный случай. Он хранит три дескриптора стандартных файловых потоков (ввода, вывода и сообщений об ошибке) и содержит средства, позволяющие проверить правильность любого дескриптора файла.
Часть 4
Сегодня мы рассмотрим самый большой и, наверное, самый полезный раздел языка Java, связанный с реализацией пользовательского интерфейса. Для этого мы изучим базовые классы упаковки java.awt (Abstract Window Toolkit).
Итак, что же такое awt? Это набор классов Java, каждый из которых отвечает за реализацию функций и отображение того или иного элемента графического интерфейса пользователя (GUI). Практически все классы визуальных компонентов являются потомками абстрактного класса Component. Лишь визуальные элементы меню наследуются от другого класса - MenuComponent. Управляющие элементы представлены следующими классами: Button (кнопка), Checkbox (кнопка с независимой фиксацией), Choice (раскрывающийся список Windows), Label (строка), List (список выбора Windows) и Scrollbar (полоса прокрутки). Это достаточно простые классы, наследуемые от абстрактного класса Component напрямую.
Однако в составе java.awt имеются классы интерфейсных элементов, имеющие промежуточного предка. Хорошим примером тому является класс Panel для создания различных панелей. У него имеется промежуточный абстрактный класс-предок Container, служащий родоначальником многих классов-контейнеров, способных содержать в себе другие элементы интерфейса. От этого же класса наследуется класс окна Window, представляющий на экране простейшее окно без меню и рамки. У этого класса есть два часто используемых потомка: Dialog, название которого говорит само за себя, и Frame - стандартное окно Windows. Еще один промежуточный класс TextComponent порождает два полезнейших в работе класса - TextField (аналог строки ввода Windows) и многострочное окно текстового ввода TextArea. Особняком от всех элементов стоит класс Canvas. Его визуальное представление - пустой квадрат, на котором можно выполнять рисование и который может обрабатывать события нажатия кнопок мыши.
От своего предка Component все визуальные элементы перенимают общее для них всех поведение, связанное с их визуальной и функциональной сторонами. Вот список основных, выполняемых компонентами, функций и методов для их реализации:
изменение шрифта - методы getFont, setFont, getFontMetrics;
изменение цвета шрифта - методы setForeground(Color) и getForeground() - для установки и чтения цвета самого шрифта, а также setBackground(Color) и getBackground() для установки, а также чтения цвета фона, на котором отображается текст;
размер и позиция на экране - методы preferredSize() и minimumSize() сообщают менеджеру раскладок о предпочтительном и минимальном размерах компонента, соответственно;
отображение компонента - методы paint(), update() и repaint();
обработка сообщений - методы handleEvent(), action(), keyDown(), keyUp(), mouseDown(), mouseUp(), mouseDrag(), mouseMove(), mouseEnter() и mouseExit().
Часть 5
Как и было обещано, с этого занятия мы начинаем практиковаться в написании программ на языке Java. Иногда, правда, по ходу дела мы коснемся некоторых теоретических моментов, если это будет необходимо для понимания работы программы. На этом занятии мы будем разрабатывать аплет, который поможет понять механизм сообщений языка Java. Поэтому для начала рассмотрим основные события, происходящие в процессе работы Java-программ, и методы их перехвата.
Обработка исключительных ситуаций
Если ваша программа нарушит семантические правила языка Java, то виртуальная машина Java (JVM) немедленно отреагирует на это выдачей ошибки под названием "исключительная ситуация". Пример такой ситуации - выход за рамки массива. Она может возникнуть при попытке обратиться к элементу за пределами границы массива. Некоторые языки программирования никак не "реагируют" на ошибки программиста и позволяют ошибочным программам выполняться. Но Java не относится к таким языкам. И поэтому программа тщательно проверяет все места, где может возникнуть потенциальная ошибка, а при обнаружении ошибки возбуждаются (throw) исключительные ситуации. Если имеются обработчики таких ситуаций, они перехватывают их (catch) и обрабатывают надлежащим образом.
Программы на языке Java могут самостоятельно возбуждать исключительные ситуации, используя для этого оператор throw. В точке метода, где встречается throw, выполнение метода прерывается и управление передается в тот метод, который вызвал ошибочный. Если исключительная ситуация может быть обработана методом, то вызывается его обработчик. Если же это невозможно, то поток управления передается дальше, и так происходит до того момента, когда исключительная ситуация не будет перехвачена или пока ее не перехватит виртуальная машина Java. В последнем случае выполнение программы прерывается и выводится сообщение об ошибке.
В языке Java каждая исключительная ситуация реализуется как экземпляр класса Throwable или его наследников. Когда в программе нужно отследить возможную исключительную ситуацию, в ней устанавливается обработчик (несколько обработчиков). На практике это оформляется в виде так называемого блока try-catch:
try{
// Здесь возможно возбуждение
// исключительной ситуации
} catch (ТипИсключительнойСитуации)
{
// Здесь производится обработка
// перехваченной исключительной
// ситуации
}
Но не всегда исключительные ситуации - это фатальный сбой. Может, к примеру, оказаться, что программа просто не нашла какой-то файл в каталоге. В этом случае можно перехватить такую ошибку и в обработчик исключительной ситуации вставить оператор вызова диалоговой панели, где пользователь укажет местоположение этого файла. После разрешения этой проблемы программа может быть запущена с того места, где ее выполнение было прервано.
Обычно все методы, в которых может возникнуть исключительная ситуация, описываются особым образом. Например:
static void SomeMethod ()
throws FileNotFoundException {-}
В этом описании оператор throws обозначает, что метод потенциально может создать/вызвать исключительную ситуацию FileNotFoundException, поскольку не найден какой-либо файл. Теперь любой вызов этого метода в программе должен быть обрамлен описанием блока try-catch, иначе компилятор выдаст ошибку и не обработает исходный текст вашей программы. Корректное решение проблемы выглядит примерно следующим образом:
try{
..
static void SomeMethod ();
..
} catch (FileNotFoundException exception){
// Предпринимаем действия
// по устранению ошибки
}
Конечно, может показаться, что этот способ несколько расточителен и трудоемок. Но зато он гарантирует, что вы обработаете нештатную ситуацию, а не оставите ее "в подарок" пользователю вашей программы.
Существуют и более сложные понятия, например идеология обработки исключений или блоки try-finally. Однако того, что вы прочитали, в большинстве случаев вполне достаточно для повседневной работы.
Panel
Класс Panel (панель) - это простой контейнер, в который могут быть добавлены другие контейнеры или элементы интерфейса. Обычно он используется в тех случаях, когда необходимо выполнить сложное размещение элементов в окне Java-программы и аплета.
Создание объекта класса Panel элементарно:
Panel p1 = new Panel();
Как и в случае с другими контейнерами, элементы интерфейса могут быть добавлены методом add().
PipedInputStream и PipedOutputStream
Интересное применение могут найти специализированные потоковые классы. Так, например, два класса, PipedInputStream и PipedOutputStream, введены в иерархию классов Java для создания каналов (pipes), передачи данных от одной программы к другой или от одного потока выполнения (thread) к другому. Каналы широко используются в операционных системах UNIX.
Каналы удобны как средство переопределения потоков, как это делается операторами ">", ">>" или "<" операционной системы для переназначения ввода и вывода данных для программы. В Java создание такого канала сводится к двум строкам исходного текста:
outPipe = new PipedOutputStream (); inPipe = new PipedInputStream(outPipe);
Private
Из названия private (частный, собственный) следует то, что к данным и методам, отмеченным этим ключевым словом, доступ отсутствует. Это позволено лишь самому классу - владельцу данных и методов. Если же кто-то попытается обратиться к private-данным или методам, то компилятор Java немедленно выдаст сообщение об ошибке компиляции. Если ваш класс не будет в дальнейшем наследоваться, то лучше использовать модификатор private, а не protected.
Protected
Модификатор доступа protected позволяет обращаться к данным и методам класса лишь самому классу, классам, хранящимся в этой же упаковке, и унаследованным классам, но лишь в том случае, если они находятся в одной упаковке с классом-предком. Обычно такой модификатор применяют для того, чтобы закрыть доступ к данным и методам для тех классов, которые не состоят в "родственных отношениях" с защищаемым классом. Обратите внимание на то, что в Java классы считаются родственными, не только если они унаследованы друг от друга, но и просто хранятся в одной и той же упаковке.
Предположим, что в упаковке Nums имеется некий класс First и что он содержит переменную и метод, объявленные protected:
package Nums;
class First
{
protected int protVar;
protected void protMethod()
{
System.out.println("protMeth called!");
}
}
Если теперь в той же упаковке описать другой класс с именем Second, то он сможет свободно обращаться к методам и данным класса First, не обращая внимания на то, что Second не был унаследован от First:
package Nums;
class Second
{
void protAccessMethod()
{
First ap = new First();
ap.protVar = 345;
ap.protMethod();
}
}
Напомню, что унаследованные классы могут беспрепятственно обращаться к данным и методам, отмеченным модификатором protected, только в том случае, если класс-предок располагается в той же упаковке, что и сами классы-наследники.
Public
Любой класс может обратиться к данным и методам другого класса из любого компьютера сети, если он имеет модификатор доступа. Постарайтесь описывать методы как public лишь в крайнем случае, когда это действительно необходимо. А объявлять переменные внутри класса как public не стоит - для этого надо бы предусмотреть отдельные методы, которые и должны делать это за вас.
RandomAccessFile
Класс произвольного доступа к файлу RandomAccessFile может реализовывать интерфейсы как к DataInput, так и к DataOutput. Это означает, что класс RandomAccessFile может быть использован как для ввода данных из файла, так и для вывода в файл.
Для того чтобы создать объект класса RandomAccessFile, необходимо вызвать его конструктор с двумя параметрами: именем файла для ввода/вывода и режимом доступа к открываемому файлу. Так может выглядеть открытие файла для чтения информации:
new RandomAccessFile ("some.dat", "r");
А в следующем примере файл открывается как для чтения, так и для записи:
new RandomAccessFile ("some.dat", "rw");
После того как файл открыт, вы можете использовать любые методы readXXX() и writeXXX() для ввода и вывода.
Основным же преимуществом класса RandomAccessFile является его способность читать и записывать данные в произвольное место файла. Программисты, работающие на Си и Си++, легко обнаружат, что в основе управления файлом лежит уже знакомый им файловый указатель, отмечающий текущую позицию, где происходит чтение или запись данных. В момент создания объекта класса RandomAccessFile файловый указатель устанавливается в начало файла и имеет значение 0. Вызовы методов readXXX() и writeXXX() обновляют позицию файлового указателя, сдвигая его на количество прочитанных (записанных) байтов. Для произвольного сдвига файлового указателя на некоторое количество байтов можно применить метод skipBytes(), или же установить файловый указатель в определенное место файла вызовом метода seek(). Для того чтобы узнать текущую позицию, в которой находится файловый указатель, нужно вызвать метод getFilePointer().
Помимо классов потоков ввода/вывода, описанных на этом занятии, существуют еще несколько классов, о которых мы не сказали. Они не так часто употребляются в программах Java, и поэтому вы можете отыскать их и освоить самостоятельно в тот момент, когда они вам понадобятся.
Раскладки
Остался один вопрос, которого мы с вами не коснулись, - раскладки компонентов. Для того чтобы управлять расположением элементов внутри окон-контейнеров, в Java существует менеждер раскладок (layout manager). От него наследуются пять классов, определяющих тот или иной тип расположения компонентов пользовательского интерфейса в окне. Когда вам требуется изменить тип расположения, вы создаете тот или иной класс раскладки, отвечающий вашим запросам, и передаете его в вызываемый метод setLayout(), изменяющий текущую раскладку:
// Установить расположение элементов // вдоль рамки окна setLayout( new BorderLayout() );
Дадим краткую характеристику классам-раскладкам.
FlowLayout. Это простейший способ расположения элементов один за другим, применяемый по умолчанию. Когда в одной строке уже не помещаются новые элементы, заполнение продолжается с новой строки.
CardLayout. При раскладке этого типа элементы размещаются один за другим, как карты в колоде. Обычно такой расклад удобен, если вам необходимо динамически изменять интерфейс окна. Кроме того, вы можете делать элементы, находящиеся один над другим по очереди.
BorderLayout. Эта раскладка размещает элементы либо рядом с выбранным краем окна, либо в центре. Для этого после установки BorderLayout добавление элементов в окно-контейнер производится методом add() с дополнительным параметром, задаваемым строками North, South, East, West и Center. Каждая из них означает тот край окна, к которому необходимо прижать вставляемый элемент.
GridLayout. GridLayout располагает элементы один за другим внутри некоторой условной таблицы. Все элементы будут одинакового размера. Размер ячеек можно программно изменять.
GridBagLayout. Это самая мудреная, но в то же время и самая мощная раскладка. Она располагает элементы в условной таблице, как это делается в случае с GridLayout. Но в отличие от последней, можно варьировать размер каждого элемента в отдельности. Правда, придется набрать дополнительно не одну строчку исходного текста.
* * *
Ну вот и вся теория. Вам остается лишь воплотить ее на практике.
Scrollbar
Класс Scrollbar представляет на экране знакомую всем полосу прокрутки. С помощью этого элемента можно прокручивать изображение и текст в окне либо устанавливать некоторые значения. Чтобы создать полосу прокрутки, необходимо вызвать конструктор объекта класса Scrollbar. Это можно сделать тремя способами:
// Создать полосу прокрутки // с параметрами по умолчанию new Scrollbar(); // Создать полосу прокрутки // с ориентацией,в данном случае - // вертикальной new Scrollbar(Scrollbar.VERTICAL);
Третий конструктор создает объект класса Scrollbar, сразу задавая все необходимые параметры:
new Scrollbar ( <ориентация>, <текущее значение>, <видно>, <мин. значение>, <макс. значение>);
где
<ориентация> - ориентация полосы, задаваемая константами Scrollbar.HORIZONTAL и Scrollbar.VERTICAL;
<текущее значение> -начальное значение, в которое помещается движок полосы прокрутки;
<видно> - сколько пикселов прокручиваемой области видно, и насколько эта область будет прокручена при щелчке мышью на полосе прокрутки;
<мин. значение> - минимальная координата полосы прокрутки;
<макс. значение> - максимальная координата полосы прокрутки.
Обычно в качестве прокручиваемой области выступает объект класса Canvas или порожденный от него объект. При создании такого класса его конструктору необходимо передать ссылки на полосы прокрутки.
SequenceInputStream
Если необходимо объединить в один поток данные из нескольких потоков, на помощь придет класс SequenceInputStream. Он очень прост в использовании: достаточно передать ему список файлов, выполненных в виде класса, унаследованного от интерфейса Enumeration. Задача значительно упрощается, если требуется объединить всего два потока. Создайте объект класса SequenceInputStream, вызвав другой его конструктор, принимающий два аргумента типа InputStream. Для примера создадим класс списка файлов и передадим его объекту класса SequenceInputStream:
import ileLjava.util.*; import java.io.*; class FileList implements Enumeration { String[] fist; int count = 0; FileList (String[] listOfFiles) { this.fileList = listOfFiles;} public boolean hasMoreElements() { if (current < fileList.length) return true; else return false; } public Object nextElement() { InputStream is = null; if (!hasMoreElements()) throw new NoSuchElementException ("No more files."); else { String nextElement = fileList[current]; current++; is = new FileInputStream (nextElement); } return is; } }
Теперь, когда в нашем распоряжении имеется класс-список, на его основе можно создать единый поток данных из нескольких отдельных потоков:
import java.io.*; class Example { public static void main (String[] args) { ListOfFiles mylist = new ListOfFiles(args); SequenceInputStreamis = new SequenceInputStream(mylist); int c; // Здесь производятся некоторые действия над полученным потоком s.close(); } }
TextField и TextArea
Два родственных класса, TextField и TextArea позволяют отображать текст с возможностью его выделения и редактирования. По своей сути это маленькие редакторы: однострочный (TextField) и многострочный (TextArea). Создать объекты этих классов очень просто: нужно лишь передать размер в символах для класса TextField и размер в количестве строк и символов для класса TextArea:
TextField tf = new TextField(50); TextArea ta = new TextArea(5, 30);
Можно запретить редактировние текста в окне:
tf.setEditable(false); ta.setEditable(false);
Полезные методы классов TextField и TextArea:
getText() - считать текст;
setText() - отобразить текст;
selectAll() - выделить весь текст;
getSelectedText() - считать выделенный текст;
isEditable() - проверить, разрешено ли редактирование текста;
getSelectionStart() - возвратить начало выделения;
getSelectionEnd() - возвратить окончание выделения;
select() - выделить текст между начальной и конечной позициями;
getColumns() - возвратить количество символов в строке редактирования.
В свою очередь, класс TextField имеет дополнительные методы:
setEchoChar() - установить символ маски; применяется при введении паролей;
char getEchoChar() - узнать символ маски;
echoCharIsSet() - узнать, установлен ли символ маски.
Для класса TextArea добавляются другие методы:
int getRows() - считать количество строк в окне;
insertText(String, int) - вставить текст в определенной позиции;
replaceText(String, int, int) - заменить текст между заданными начальной и конечной позициями.
Типы
Идентификаторы языка Java должны начинаться с буквы любого регистра или символов "_" и "$". Далее могут следовать и цифры. Например, _Java - правильный идентификатор, а 1_$ - нет. Еще одно ограничение Java проистекает из его свойства использовать для хранения символы кодировки Unicode, т. е. можно применять только символы, которые имеют порядковый номер более 0xC0 в раскладке символов Unicode.
В стандарте языка Java имеются три типа комментариев:
/*Comment*/;
//Comment;
/** Comment*/.
Первые два представляют собой обычные комментарии, применяемые как в Java, так и в C++. Последний - особенность Java и введен в этот язык для автоматического документирования. После написания исходного текста утилита автоматической генерации документации собирает тексты таких комментариев в один файл.
Цифровые литералы схожи с аналогичными в языке C++. Правила для целых чисел предельно просты. Если у цифры нет суффикса и префикса, то это десятичное число. У восьмеричных чисел перед цифрой стоит ноль, а для шестнадцатеричных префикс состоит из ноля и буквы X (0x или 0X). При добавлении к цифре буквы L числу присваивается тип long. Примеры: 23 (десятичное), 0675 (восьмеричное), 0x9FA (шестнадцатеричное), 456L (длинное целое).
Теперь обратимся к числам с плавающей точкой. Для них предусмотрены два вида описаний: обычное и экспоненциальное. Обычные числа с плавающей точкой записываются в такой же форме, как и те числа, которые мы пишем на бумаге от руки: 3.14, 2.73 и т.д. Это же относится и к экспоненциальному формату: 2.67E4, 5.0E-10. При добавлении суффиксов D и F получаются числа типов double и float соответственно. Например, 2.71D и 0.981F.
Коснемся подробнее числовых типов. В языке Java появился новый 8-битный тип - byte. Тип int, в отличие от аналогичного в C++, имеет длину 32 бит. А для 16-битных чисел предусмотрен тип short. В соответствии со всеми этими изменениями тип long увеличился, став 64-битным.
В стандарт Java был введен тип boolean, которого так долго ждали программисты, использующие C++. Он может принимать лишь два значения: true и false.
По сравнению с C++ массивы Java претерпели значительные изменения. Во-первых, изменились правила их описания. Массив теперь может быть описан двумя следующими способами:
type name[];
type[] name;
При этом массив не создается, лишь описывается. Следовательно, для резервирования места под его элементы надо воспользоваться динамическим выделением с помощью ключевого слова new, например:
char[] arrayName;
arrayName[] = new char[100];
или совместить описание массива с выделением под него памяти:
char array[] = new char[100];
Многомерных массивов в Java нет, поэтому приходится прибегать к ухищрениям. Например, создать многомерный массив можно как массив массивов:
float matrix[][] = new float[5][5];
В Java возможно приведение разнообразных типов к типу "массив":
varName = (array_type[]) other_varName;
За работу!
Самое время написать какой-нибудь аплет. Я пользуюсь в своей работе набором Java Developer Kit 1.1 (JDK). И не потому, что это удобно. Как раз наоборот: Symantec Visual Cafe или Microsoft Visual J++ гораздо лучше подходят для работы. Зато JDK - это стандартное средство, и к тому же его можно получить совершенно бесплатно с сервера http://java.sun.com.
Чтобы разобраться с тем, как сообщения курсируют по программе, напишем такой аплет, который будет говорить нам обо всех происходящих событиях. Для показа сообщений будем использовать стандартный поток вывода, т. е. экран монитора. Вывод сообщений будет производиться вызовами функций вывода System. out.println(), которые часто используются для трассировки различных данных во время отладки. System. out.println() показывает на экране заданную вами строку и переводит курсор на следующую строку. Если перевод курсора не требуется, то можно воспользоваться функцией System.out.print().
Наберите следующий исходный текст в файл с именем Events.java:
import java.applet.*; // Импортировать данные для класса Applet import java.awt.*; // Импортировать данные для классов // визуальных элементов import java.io.*; // Нужно для вызовов // System.out.println // Создание класса аплета public class Events extends Applet { Button button; MyTextField text;
public Events() // Конструктор класса // Events { // Вывести сообщение на дисплей System.out.println("--> Constructor called..."); // Создать объект "кнопка" // с надписью "Button" button = new Button("Button"); // Создать объект // "строка редактирования" text = new TextField(); // Добавить компоненты в окно аплета add(button); add(text); // Проверка правильности // расположения компонентов validate(); }
// Вызывается при начальной // инициализации аплета public void init() { System.out.println("--> init() called..."); // this.requestFocus(); }
// Обработчик перерисовки public void paint(Graphics g) { System.out.println("--> paint() called..."); // Разместить кнопку с координата- // ми (10,10) и сделать ее размером // 100x20 button.reshape(10, 10, // 100, 20); разместить строку // ввода с координатами (10,40) // и сделать ее размером 100x20 text.reshape(10, 40, 100, 20); }
public boolean mouseDown( Event evt, int x, int y) { System.out.println("--> mouseDown() called..."); return true; }
public boolean mouseUp(Event evt, int x, int y) { System.out.println("--> mouseUp() called..."); return true; }
public boolean mouseDrag(Event evt, int x, int y) { System.out.println("--> mouseDrag() called..."); return true; }
public boolean mouseMove(Event evt, int x, int y) { System.out.println("--> mouseMove() called..."); return true; }
public boolean mouseEnter(Event evt, int x, int y) { System.out.println("--> mouseEnter() called..."); return true; }
public boolean mouseExit(Event evt, int x, int y) { System.out.println("--> mouseExit() called..."); return true; }
public boolean keyDown(Event evt, int key) { System.out.println("--> keyDown() called..."); return true; }
public boolean keyUp(Event evt, int key) { System.out.println("--> keyUp() called..."); return true; }
public boolean gotFocus(Event evt, Object o) { System.out.println("--> gotFocus() called..."); return true; }
public boolean lostFocus(Event evt, Object o) { System.out.println("--> lostFocus() called..."); return true; }
public boolean action(Event evt, Object o) { System.out.println("--> action() called..."); return true; } }
Если вы будете использовать JDK, то скомпилируйте исходный текст командой
javac.exe Events.java
Разумеется, вы должны проследить за тем, чтобы ваш проект был виден компилятору, иначе компилятор не сможет найти файл с исходным текстом.
Следующий ход - создание Web-страницы со ссылкой на полученный аплет. Сделаем файл Events.html и наберем следующий исходный текст на языке HTML:
<html> <head> <title>Events</title> </head> <body> <applet code=Events.class width=150 height=200 > </applet> </body> </html>
Все готово? Тогда запускаем наш аплет:
appletviewer.exe Events.html
Поэкспериментируйте, нажимая кнопки мыши и клавиатуры. Посмотрите в DOS-окно на протокол, полученный в результате нажатий. Обратите внимание на некоторые странности в поведении нашего аплета. Во-первых, при попадании указателя мыши на элементы управления возникает событие MOUSE_EXIT, а при переводе указателя назад в окно аплета следует событие MOUSE_ENTER.
Вывод: области, занятые элементами интерфейса, исключаются из окна аплета, и события от мыши в этих областях не обрабатываются.
Во-вторых, не происходит ни одного вызова обработчиков нажатий и отпусканий клавиш клавиатуры. Это говорит о том, что аплет не имеет фокуса ввода. Нет фокуса и у элементов интерфейса. Если нажать клавишу <Tab>, то передачи фокуса не происходит.
Вывод: после создания аплета фокуса ввода нет ни у одного элемента интерфейса.
Если вы хотите, чтобы нажатия клавиш отслеживались аплетом, вам необходимо сделать так, чтобы аплет получил фокус. На самом деле такая строка уже есть:
// this.requestFocus();
Все, что от вас требуется, это убрать признак комментария - две косые черты перед строкой, и перекомпилировать проект. Если теперь запустить аплет и начать нажимать кнопки клавиатуры, то вы увидите, что обработчики нажатий и отпусканий клавиш начали вызываться. Если нажать клавишу <Tab>, фокус ввода перейдет к кнопке. При этом вы увидите, что вызываются обработчики lostFocus() и gotFocus(), отмечающие потерю фокуса ввода одним элементом и получение его другим.
Кстати говоря, мы с вами еще не открыли тайну обработчика action(), а ведь он один из основных. Он вызывается в том случае, когда происходит главное функциональное событие элемента интерфейса. Для классов Button и Checkbox это будет щелчок мыши, для классов Choice, List и MenuItem - выбор элемента, для класса TextField - нажатие клавиши Return. В большинстве случаев вам как программисту будет достаточно именно этого обработчика при решении практически всех задач. Для того чтобы узнать, какой именно элемент интерфейса вызвал обработчик action(), нужно проверить параметр target переданного класса Event. Это ссылка на сработавший объект. Проверить правильность этого факта можно, добавив в обработчик action() пару строчек:
if(evt.target==button) System.out.println("--> Button pressed"); if(evt.target==text) System.out.println("--> Text edited");
Запустите перекомпилированный аплет, понажимайте на кнопку и понабирайте текст в строке ввода, завершая набор нажатием клавиши Return. Сообщения не замедлят появиться в DOS-окне.
Основная работа, связанная с обработкой сообщений аплета, приходится на метод handleEvent(). Для того чтобы убедиться в этом, напишем свой обработчик сообщений handleEvent():
public boolean handleEvent(Event evt) { System.out.println ("-- > handleEvent() called_"); return false; }
Последняя строка возвращает константу false, говорящую о том, что сообщение не обработано. Откомпилируем и запустим модифицированный вариант аплета, наблюдая за диагностическими сообщениями в окне DOS. Как вы можете видеть, теперь весь процесс обработки сообщений сводится к вызовам метода handleEvent(), после которого ничего не происходит. Изменим строку
return false
на вызов метода handleEvent() класса Applet, который является предком для нашего аплета:
return super.handleEvent(evt);
Напомню, что ключевое слово super обозначает ссылку на непосредственного предка класса. В принципе это эквивалентно следующей строке:
return Applet.handleEvent(evt);
К сожалению, компилятор Java выдаст на это выражение сообщение об ошибке, отказываясь делать статическую ссылку на метод объекта. Поэтому все равно придется использовать слово super.
После того как мы изменили содержимое метода handleEvent(), вызовы обработчиков элементарных сообщений (например, mouseMove() и т. п.) снова посыпались как из рога изобилия. Также восстановился поток вызовов метода action(). Обратите внимание, что каждому вызову обработчика элементарного сообщения предшествует вызов метода handleEvent().
Вывод: любое сообщение передается методу handleEvent(), который должен либо обработать его самостоятельно, либо передать методу handleEvent() класса-предка. Последний уже производит разбор сообщений на элементарные и вызывает соответствующие им обработчики. Метод handleEvent() класса-предка также производит диспетчеризацию вызовов метода action().
Следующий этап нашего эксперимента - проверка пересылки сообщений между элементами пользовательского интерфейса и аплетом. Создадим свой класс кнопки и назовем его MyButton. Добавьте в файл Events.java следующий исходный текст:
class MyButton extends Button { public MyButton(String text) { super(text); }
public boolean mouseDown(Event evt, int x, int y) { System.out.println ("--> MyButton.mouseDown() called..."); return true; }
public boolean mouseUp(Event evt, int x, int y) { System.out.println("--> MyButton.mouseUp() called..."); return true; }
public boolean mouseDrag(Event evt, int x, int y) { System.out.println("--> MyButton.mouseDrag() called..."); return true; }
public boolean mouseMove(Event evt, int x, int y) { System.out.println("--> MyButton.mouseMove() called..."); return true; }
public boolean mouseEnter(Event evt, int x, int y) { System.out.println("--> MyButton.mouseEnter() called..."); return true; }
public boolean mouseExit(Event evt, int x, int y) { System.out.println("--> MyButton.mouseExit() called..."); return true; }
public boolean keyDown(Event evt, int key) { System.out.println("--> MyButton.keyDown() called..."); }
public boolean keyUp(Event evt, int key) { System.out.println("--> MyButton.keyUp() called..."); return true; }
public boolean gotFocus(Event evt, Object o) { System.out.println("--> MyButton.gotFocus() called..."); return true; }
public boolean lostFocus (Event evt, Object o) { System.out.println("--> MyButton.lostFocus() called..."); return true; } }
Замените также в исходном тексте аплета все упоминания класса Button на MyButton. Перекомпилируйте и запустите аплет. Попробуйте поэкспериментировать с нажатиями на кнопку. Как вы заметили, все события стали передаваться непосредственно классу кнопки. Это же происходит и с нажатиями на кнопки клавиатуры.
Вывод: все события сначала передаются элементу интерфейса, с которым работает пользователь, и лишь потом, если разрешит обработчик события, передаются классу контейнера, в котором этот элемент содержится. Для такой передачи сообщения обработчик должен вернуть значение false.
Еще одна странность наблюдается при установленном на кнопке фокусе ввода. Если теперь нажать клавишу <Tab>, то ничего не произойдет. Фокус останется на кнопке и не будет передан на строку ввода, как это ожидается. Секрет прост: наш класс не пропускает нажатие <Tab> далее. Чтобы исправить ситуацию, вставим следующую строку в обработчик keyDown():
return key == 9 ? false : true;
Теперь, если код нажатой клавиши равен коду клавиши <Tab>, т. е. равен 9, то мы не обрабатываем событие, а возвращаем false, передавая его дальше в систему. Вывод: в Java-программах нет различия между функциональными клавишами и обычными. Поэтому вы должны сами отслеживать сомнительные моменты.
На сегодня достаточно. Вам предоставляется возможность самостоятельно поэкспериментировать с готовым аплетом.