Аплет FormLayout
Назад Вперед
В окне аплета FormLayout (рис. 2) мы расположили те же самые органы управления, которые были использованы в предыдущем аплете FormDemo. Однако для указания способа размещения компонент мы выполнили настройку системы Layout Manager, выбрав режим GridLayout.
Рис. 2. Окно аплета FormLayout
Для того чтобы увидеть рисунок в увеличенном виде, сделайте щелчок мышью по изображению |
И хотя пока еще внешний вид нашей формы оставляет желать лучшего, расположение отдельных компонент не изменяется при изменении размеров окна аплета.
Проблема заключается в том, что в режиме GridLayout не удается управлять размерами компонент. Для устранения этого недостатка следует использовать режим GridBagLayout. Так как этот режим сложен для использования без визуального проектирования, мы отложим дальнейшее совершенстовование нашей формы до тех пор, пока не займемся изучением соответствующих средств Java WorkShop.
Бинарное числовое расширение
Это преобразование расширяет все примитивные числовые типы, кроме double, до типов int, long, float, double по правилам расширения примитивных типов. Бинарное числовое расширение происходит при числовых операторах, имеющих два аргумента, по следующим правилам:
если любой из аргументов имеет тип double, то и второй приводится к double;иначе, если любой из аргументов имеет тип float, то и второй приводится к float;иначе, если любой из аргументов имеет тип long, то и второй приводится к long;иначе оба аргумента приводятся к int.
Бинарное числовое расширение может выполняться при следующих операциях:
арифметические операции +, -, *, /, %;операции сравнения <, <=, >, >=, ==, !=;битовые операции &, |, ^;в некоторых случаях для операции с условием ? :.
Примеры работы всех этих операторов с учетом расширения подробно рассматривались в предыдущих лекциях.
Числовое расширение
Наконец, последний вид преобразований применяется при числовых операциях, когда требуется привести аргумент(ы) к типу длиной в 32 или 64 бита для проведения вычислений. Таким образом, при числовом расширении осуществляется только расширение примитивных типов.
Различают унарное и бинарное числовое расширение.
Исходный текст аплета FormLayout
Исходный текст аплета FormLayout практически повторяет исходный текст аплета FormDemo, рассмотренный в нашей предыдущей статье. Единственное отличие заключается в том, что в методе init мы выполнили настройку системы Layout Manager, установив режим GridLayout:
public void init() { setLayout(new GridLayout(4, 3)); . . . }
Здесь для размещения компонент в окне аплета создается таблица из четырех строк и трех столбцов.
Полный исходный текст аплета FormLayout вы найдете в листинге 1.
Листинг 1. Файл FormLayout.java
import java.applet.Applet; import java.awt.*; import java.util.*;
public class FormLayout extends Applet { Button btReady;
Checkbox chbox1; Checkbox chbox2;
CheckboxGroup grRadio; Checkbox rd1; Checkbox rd2; Checkbox rd3;
Choice ch1;
Label lbFirstName; Label lbSecondName;
TextField txtFirstName; TextField txtSecondName; TextArea txta;
public void init() { setLayout(new GridLayout(4, 3));
chbox1 = new Checkbox("First"); add(chbox1);
lbFirstName = new Label( "Enter your first name:"); add(lbFirstName);
txtFirstName = new TextField(" ", 30); add(txtFirstName);
chbox2 = new Checkbox("Second"); add(chbox2);
lbSecondName = new Label( "Enter your second name:"); add(lbSecondName);
txtSecondName = new TextField(" ", 30); add(txtSecondName);
grRadio = new CheckboxGroup(); rd1 = new Checkbox("Mode 1", grRadio, true); rd2 = new Checkbox("Mode 2", grRadio, false); rd3 = new Checkbox("Mode 3", grRadio, false);
add(rd1); add(rd2); add(rd3);
ch1 = new Choice(); ch1.addItem("White"); ch1.addItem("Green"); ch1.addItem("Yellow");
add(ch1);
setBackground(Color.yellow);
lbFirstName.setBackground( Color.yellow); lbSecondName.setBackground( Color.yellow);
rd1.setBackground(Color.yellow); rd2.setBackground(Color.yellow); rd3.setBackground(Color.yellow);
chbox1.setBackground(Color.yellow); chbox2.setBackground(Color.yellow);
txta = new TextArea("", 6, 45); add(txta); txta.setBackground(Color.white);
btReady = new Button("Ready"); add(btReady); }
public String getAppletInfo() { return "Name: FormDemo"; }
public void paint(Graphics g) { Dimension dimAppWndDimension = getSize();
g.setColor(Color.black); g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); }
public boolean action(Event evt, Object obj) { Button btn; String str1, str2;
if(evt.target instanceof Button) { if(evt.target.equals(btReady)) { btn = (Button)evt.target;
str1 = txtFirstName.getText(); str2 = txtSecondName.getText();
if(chbox1.getState()) txta.append(str1);
if(chbox2.getState()) txta.append(str2);
if(rd1.getState()) txta.append("\nMode 1\n");
if(rd2.getState()) txta.append("\nMode 2\n");
if(rd3.getState()) txta.append("\nMode 3\n"); } else { return false; } return true; } else if(evt.target instanceof Choice) { if(evt.target.equals(ch1)) { if(ch1.getSelectedIndex() == 0) txta.setBackground(Color.white);
if(ch1.getSelectedIndex() == 1) txta.setBackground(Color.green);
if(ch1.getSelectedIndex() == 2) txta.setBackground(Color.yellow); } } return false; } }
Исходный текст документа HTML, созданный для нашего аплета системой Java WorkShop, представлен в листинге 2.
Листинг 2. Файл FormLayout.tmp.html
<applet name="FormLayout" code="FormLayout" codebase= "file:/e:/sun/articles/vol7/src/FormLayout" width="500" height="600" align="Top" alt="If you had a java-enabled browser, you would see an applet here."> </applet>
Назад Вперед
О компании
Новости
Вакансии
Правовые аспекты
Условия использования
Торговые марки
Copyright 1994-2005 Sun Microsystems, Inc.
printmenus();
Программные продукты
Рабочие станции и тонкие клиенты
Серверы
Системы хранения данных
Посмотреть все »
Java 2 Standard Edition
Developer Tools
Top Downloads
New Downloads
Патчи и обновления
Использование режима размещения CardLayout
Как пользоваться режимом размещения CardLayout?
Обычно в окне аплета создается две панели, одна из которых предназначена для показа страниц блокнота в режиме размещения CardLayout, а вторая содержит органы управления перелистыванием страниц, например, кнопки.
Такие методы, как first, last, next и previous позволяют отображать, соответственно, первую, последнюю, следующую и предыдущую страницу блокнота. Если вызвать метод next при отображении последней страницы, в окне появится первая страница. Аналогично, при вызове метода previous для первой страницы блокнота вы увидите последнюю страницу.
А как отобразить произвольную страницу, не перебирая их по одной методами next и previous?
Для этого существует метод show. Учтите, что этот метод позволяет отображать только такие страницы, при добавлении которых методом add было указано имя, например:
pCardPanel.add("BackgroundColor", pBackgroundColor); pCardPanel.add("ForegroundColor", pForegroundColor); pCardPanel.add("Font", pFont);
Здесь в панель pCardPanel добавляются панели pBackgroundColor, pForegroundColor и pFont, имеющие имена, соответственно, "BackgroundColor", "ForegroundColor" и "Font".
Назад Вперед
Контакты
О компании
Новости
Вакансии
Правовые аспекты
Условия использования
Торговые марки
Copyright 1994-2005 Sun Microsystems, Inc.
printmenus();
Программные продуктыРабочие станции и тонкие клиенты
Серверы
Системы хранения данных
Посмотреть все
»Java 2 Standard Edition
Developer Tools
Top Downloads
New Downloads
Патчи и обновления
Посмотреть все
»Истории успеха
The Sun Grid
Партнерские программы
Посмотреть все
»Программы SunSpectrum
Консалтинг
Услуги инсталляции
Поддержка ПО
Посмотреть все
»Сертификация
Авторизованные учебные центры
Посмотреть все
»События
Lab Downloads
Посмотреть все
»Явное приведение
Явное приведение уже многократно использовалось в примерах. При таком преобразовании слева от выражения, тип значения которого необходимо преобразовать, в круглых скобках указывается целевой тип. Если преобразование пройдет успешно, то результат будет точно указанного типа. Примеры:
(byte)5 (Parent)new Child() (Flat)getCity().getStreet( ).getHouse().getFlat()
Если комбинация типов образует запрещенное преобразование, возникает ошибка компиляции. Допускаются тождественные преобразования, расширения простых и объектных типов, сужения простых и объектных типов. Первые три всегда выполняются успешно. Последние два могут стать причиной ошибки исполнения, если значения оказались несовместимыми. Как следствие, выражение null всегда может быть успешно преобразовано к любому ссылочному типу. Но можно найти способ все-таки закодировать запрещенное преобразование.
Child c=new Child(); // Child2 c2=(Child2)c; // запрещенное преобразование Parent p=c; // расширение Child2 c2=(Child2)p; // сужение
Такой код будет успешно скомпилирован, однако, разумеется, при исполнении он всегда будет генерировать ошибку в последней строке. "Обманывать" компилятор смысла нет.
Класс FlowLayout
Ниже мы привели краткое описание класса FlowLayout:
Конструкторы
Без указания выравнивания и зазора между компонентами
public FlowLayout();
С указанием выравнивания
public FlowLayout(int align);
С указанием выравнивания и зазора между компонентами по вертикали и горизонтали
public FlowLayout(int align, int hgap, int vgap);
Обычно приложения не вызывают методы класса FlowLayout, устанавливая варианты компоновки при помощи конструкторов.
Первый конструктор класса FlowLayout не имеет параметров. Он устанавливает по умолчанию режим центрирования компонент и зазор между компонентами по вертикали и горизонтали, равный 5 пикселам. Именно этот режим и использовался раньше во всех наших аплетах, так как именно он применяется по умолчанию объектами класса Panel, от которого наследуется класс Applet.
С помощью второго конструктора вы можете выбрать режим размещения с заданным выравниванием компонент в окне контейнера по горизонтали. В качестве параметров этому конструктору необходимо передавать значения FlowLayout.LEFT, FlowLayout.RIGHT, или FlowLayout.CENTER. Зазор между компонентами будет при этом равен по умолчанию 5 пикселам.
И, наконец, третий конструктор допускает раздельное указание режима выравнивания, а также зазоров между компонентами по вертикали и горизонтали в пикселах.
Конструкторы класса BorderLayout
Ниже приведено краткое описание конструкторов класса BorderLayout.
public BorderLayout(); public BorderLayout(int hgap, int vgap);
Эти конструкторы предназначены для создания схемы размещения, без зазора между компонентами и с зазором заданной величины,соответственно.
Конструкторы класса CardLayout
Режим без зазоров
public CardLayout();
Режим с зазорами по вертикали и горизонтали между компонентами и окном контейнера
public CardLayout(int hgap, int vgap);
Методы
addLayoutComponent
Не используется
public void addLayoutComponent( String name, Component comp);
layoutContainer
Предназначен для того чтобы компоненты могли установить для себя предпочтительный размер
public void layoutContainer( Container target);
minimumLayoutSize
Определение минимального размера окна контейнера, необходимого для размещения всех компонент
public Dimension minimumLayoutSize( Container target);
preferredLayoutSize
Определение предпочтительного размера окна контейнера, необходимого для размещения всех компонент
public Dimension preferredLayoutSize( Container target);
removeLayoutComponent
Удаление компоненты из контейнера
public void removeLayoutComponent( Component comp);
toString
Получение строки названия метода компоновки
public String toString();
Назад Вперед
Контакты
О компании
Новости
Вакансии
Правовые аспекты
Условия использования
Торговые марки
Copyright 1994-2005 Sun Microsystems, Inc.
printmenus();
Программные продуктыРабочие станции и тонкие клиенты
Серверы
Системы хранения данных
Посмотреть все
»Java 2 Standard Edition
Developer Tools
Top Downloads
New Downloads
Патчи и обновления
Посмотреть все
»Истории успеха
The Sun Grid
Партнерские программы
Посмотреть все
»Программы SunSpectrum
Консалтинг
Услуги инсталляции
Поддержка ПО
Посмотреть все
»Сертификация
Авторизованные учебные центры
Посмотреть все
»События
Lab Downloads
Посмотреть все
»Методы класса BorderLayout
Перечислим также методы класса BorderLayout:
public void addLayoutComponent( String name, Component comp); public void layoutContainer( Container target); public Dimension minimumLayoutSize( Container target); public Dimension preferredLayoutSize( Container target); public void removeLayoutComponent( Component comp); public String toString();
Методы класса CardLayout
addLayoutComponent
Добавление компоненты с указанием имени
public void addLayoutComponent( String name, Component comp);
first
Отображение первой страницы блокнота
public void first(Container target);
last
Отображение последней страницы блокнота
public void last(Container target);
next
Отображение следующей страницы блокнота
public void next(Container target);
previous
Отображение предыдущей страницы блокнота
public void previous(Container target);
layoutContainer
Выполнение размещения компонент
public void layoutContainer( Container target);
minimumLayoutSize
Определение минимальных размеров окна, необходимых для размещения компонент
public Dimension minimumLayoutSize( Container target);
preferredLayoutSize
Определение предпочтительных размеров окна, необходимых для размещения компонент
public Dimension preferredLayoutSize( Container target);
removeLayoutComponent
Удаление заданной компоненты
public void removeLayoutComponent( Component comp);
show
Отображение произвольной страницы блокнота по ее имени
public void show( Container target, String name);
toString
Получение текстовой строки названия режима размещения
public String toString();
Оператор конкатенации строк
Этот оператор уже рассматривался достаточно подробно. Если обоими его аргументами являются строки, то происходит обычная конкатенация. Если же тип String имеет лишь один из аргументов, то второй необходимо преобразовать в текст. Это единственная операция, при которой производится универсальное приведение любого значения к типу String.
Это одно из свойств, выделяющих класс String из общего ряда.
Правила преобразования уже были подробно описаны в этой лекции, а оператор конкатенации рассматривался в лекции "Типы данных".
Небольшой пример:
int i=1; double d=i/2.; String s="text"; print("i="+i+", d="+d+", s="+s);
Результатом будет:
i=1, d=0.5, s=text
Поля
Следующие три поля задают способы выравнивания:
CENTER
Центрирование
public final static int CENTER;
LEFT
По левой границе
public final static int LEFT;
RIGHT
По правой границе
public final static int RIGHT;
Преобразование к строке
Это преобразование уже не раз упоминалось. Любой тип может быть приведен к строке, т.е. к экземпляру класса String. Такое преобразование является исключительным в силу того, что охватывает абсолютно все типы, в том числе и boolean, про который говорилось, что он не может участвовать ни в каком другом приведении, кроме тождественного.
Напомним, как преобразуются различные типы.
Числовые типы записываются в текстовом виде без потери точности представления. Формально такое преобразование происходит в два этапа. Сначала на основе примитивного значения порождается экземпляр соответствующего класса-"обертки", а затем у него вызывается метод toString(). Но поскольку эти действия снаружи незаметны, многие JVM оптимизируют их и преобразуют примитивные значения в текст напрямую.Булевская величина приводится к строке "true" или "false" в зависимости от значения.Для объектных величин вызывается метод toString(). Если метод возвращает null, то результатом будет строка "null".Для null-значения генерируется строка "null".
Преобразование примитивных типов (расширение и сужение)
Очевидно, что следующие четыре вида приведений легко представляются в виде таблицы 7.1.
простой тип, расширение | ссылочный тип, расширение |
простой тип, сужение | ссылочный тип, сужение |
Что все это означает? Начнем по порядку. Для простых типов расширение означает, что осуществляется переход от менее емкого типа к более емкому. Например, от типа byte (длина 1 байт) к типу int (длина 4 байта). Такие преобразования безопасны в том смысле, что новый тип всегда гарантированно вмещает в себя все данные, которые хранились в старом типе, и таким образом не происходит потери данных. Именно поэтому компилятор осуществляет его сам, незаметно для разработчика:
byte b=3; int a=b;
В последней строке значение переменной b типа byte будет преобразовано к типу переменной a (то есть, int) автоматически, никаких специальных действий для этого предпринимать не нужно.
Следующие 19 преобразований являются расширяющими:
от byte к short, int, long, float, doubleот short к int, long, float, doubleот char к int, long, float, doubleот int к long, float, doubleот long к float, doubleот float к double
Обратите внимание, что нельзя провести преобразование к типу char от типов меньшей или равной длины (byte, short), или, наоборот, к short от char без потери данных. Это связано с тем, что char, в отличие от остальных целочисленных типов, является беззнаковым.
Тем не менее, следует помнить, что даже при расширении данные все-таки могут быть в особых случаях искажены. Они уже рассматривались в предыдущей лекции, это приведение значений int к типу float и приведение значений типа long к типу float или double. Хотя эти дробные типы вмещают гораздо большие числа, чем соответствующие целые, но у них меньше значащих разрядов.
Повторим этот пример:
long a=111111111111L; float f = a; a = (long) f; print(a);
Результатом будет:
111111110656
Обратное преобразование - сужение - означает, что переход осуществляется от более емкого типа к менее емкому. При таком преобразовании есть риск потерять данные. Например, если число типа int было больше 127, то при приведении его к byte значения битов старше восьмого будут потеряны. В Java такое преобразование должно совершаться явным образом, т.е. программист в коде должен явно указать, что он намеревается осуществить такое преобразование и готов потерять данные.
Следующие 23 преобразования являются сужающими:
от byte к charот short к byte, charот char к byte, shortот int к byte, short, charот long к byte, short, char, intот float к byte, short, char, int, longот double к byte, short, char, int, long, float
Очевидно, что следующие четыре вида приведений легко представляются в виде таблицы 7.1.
простой тип, расширение | ссылочный тип, расширение |
простой тип, сужение | ссылочный тип, сужение |
Что все это означает? Начнем по порядку. Для простых типов расширение означает, что осуществляется переход от менее емкого типа к более емкому. Например, от типа byte (длина 1 байт) к типу int (длина 4 байта). Такие преобразования безопасны в том смысле, что новый тип всегда гарантированно вмещает в себя все данные, которые хранились в старом типе, и таким образом не происходит потери данных. Именно поэтому компилятор осуществляет его сам, незаметно для разработчика:
byte b=3; int a=b;
В последней строке значение переменной b типа byte будет преобразовано к типу переменной a (то есть, int) автоматически, никаких специальных действий для этого предпринимать не нужно.
Следующие 19 преобразований являются расширяющими:
от byte к short, int, long, float, doubleот short к int, long, float, doubleот char к int, long, float, doubleот int к long, float, doubleот long к float, doubleот float к double
Обратите внимание, что нельзя провести преобразование к типу char от типов меньшей или равной длины (byte, short), или, наоборот, к short от char без потери данных. Это связано с тем, что char, в отличие от остальных целочисленных типов, является беззнаковым.
Тем не менее, следует помнить, что даже при расширении данные все-таки могут быть в особых случаях искажены. Они уже рассматривались в предыдущей лекции, это приведение значений int к типу float и приведение значений типа long к типу float или double. Хотя эти дробные типы вмещают гораздо большие числа, чем соответствующие целые, но у них меньше значащих разрядов.
Повторим этот пример:
long a=111111111111L; float f = a; a = (long) f; print(a);
Результатом будет:
111111110656
Обратное преобразование - сужение - означает, что переход осуществляется от более емкого типа к менее емкому. При таком преобразовании есть риск потерять данные. Например, если число типа int было больше 127, то при приведении его к byte значения битов старше восьмого будут потеряны. В Java такое преобразование должно совершаться явным образом, т.е. программист в коде должен явно указать, что он намеревается осуществить такое преобразование и готов потерять данные.
Следующие 23 преобразования являются сужающими:
от byte к charот short к byte, charот char к byte, shortот int к byte, short, charот long к byte, short, char, intот float к byte, short, char, int, longот double к byte, short, char, int, long, float
При сужении целочисленного типа к более узкому целочисленному все старшие биты, не попадающие в новый тип, просто отбрасываются. Не производится никакого округления или других действий для получения более корректного результата:
print((byte)383); print((byte)384); print((byte)-384);
Результатом будет:
127 -128 -128
Видно, что знаковый бит при сужении не оказал никакого влияния, так как был просто отброшен - результат приведения обратных чисел (384 и -384) оказался одинаковым. Следовательно, может быть потеряно не только точное абсолютное значение, но и знак величины.
Это верно и для типа char:
char c=40000; print((short)c);
Результатом будет:
-25536
Сужение дробного типа до целочисленного является более сложной процедурой. Она проводится в два этапа.
На первом шаге дробное значение преобразуется в long, если целевым типом является long, или в int - в противном случае (целевой тип byte, short, char или int). Для этого исходное дробное число сначала математически округляется в сторону нуля, то есть дробная часть просто отбрасывается.
Например, число 3,84 будет округлено до 3, а -3,84 превратится в -3. При этом могут возникнуть особые случаи:
если исходное дробное значение является NaN, то результатом первого шага будет 0 выбранного типа (т.е. int или long);если исходное дробное значение является положительной или отрицательной бесконечностью, то результатом первого шага будет, соответственно, максимально или минимально возможное значение для выбранного типа (т.е. для int или long);наконец, если дробное значение было конечной величиной, но в результате округления получилось слишком большое по модулю число для выбранного типа (т.е. для int или long), то, как и в предыдущем пункте, результатом первого шага будет, соответственно, максимально или минимально возможное значение этого типа. Если же результат округления укладывается в диапазон значений выбранного типа, то он и будет результатом первого шага.
На втором шаге производится дальнейшее сужение от выбранного целочисленного типа к целевому, если таковое требуется, то есть может иметь место дополнительное преобразование от int к byte, short или char.
Проиллюстрируем описанный алгоритм преобразованием от бесконечности ко всем целочисленным типам:
Преобразование ссылочных типов (расширение и сужение)
Переходим к ссылочным типам. Преобразование объектных типов лучше всего иллюстрируется с помощью дерева наследования. Рассмотрим небольшой пример наследования:
// Объявляем класс Parent class Parent { int x; }
// Объявляем класс Child и наследуем // его от класса Parent class Child extends Parent { int y; }
// Объявляем второго наследника // класса Parent - класс Child2 class Child2 extends Parent { int z; }
В каждом классе объявлено поле с уникальным именем. Будем рассматривать это поле как пример набора уникальных свойств, присущих некоторому объектному типу.
Три объявленных класса могут порождать три вида объектов. Объекты класса Parent обладают только одним полем x, а значит, только ссылки типа Parent могут ссылаться на такие объекты. Объекты класса Child обладают полем y и полем x, полученным по наследству от класса Parent. Стало быть, на такие объекты могут указывать ссылки типа Child или Parent. Второй случай уже иллюстрировался следующим примером:
Parent p = new Child();
Обратите внимание, что с помощью такой ссылки p можно обращаться лишь к полю x созданного объекта. Поле y недоступно, так как компилятор, проверяя корректность выражения p.y, не может предугадать, что ссылка p будет указывать на объект типа Child во время исполнения программы. Он анализирует лишь тип самой переменной, а она объявлена как Parent, но в этом классе нет поля y, что и вызовет ошибку компиляции.
Аналогично, объекты класса Child2 обладают полем z и полем x, полученным по наследству от класса Parent. Значит, на такие объекты могут указывать ссылки типа Child2 или Parent.
Таким образом, ссылки типа Parent могут указывать на объект любого из трех рассматриваемых типов, а ссылки типа Child и Child2 - только на объекты точно такого же типа. Теперь можно перейти к преобразованию ссылочных типов на основе такого дерева наследования.
Расширение означает переход от более конкретного типа к менее конкретному, т.е. переход от детей к родителям. В нашем примере преобразование от любого наследника (Child, Child2) к родителю (Parent) есть расширение, переход к более общему типу. Подобно случаю с примитивными типами, этот переход производится самой JVM при необходимости и незаметен для разработчика, то есть не требует никаких дополнительных усилий, так как он всегда проходит успешно: всегда можно обращаться к объекту, порожденному от наследника, по типу его родителя.
Переходим к ссылочным типам. Преобразование объектных типов лучше всего иллюстрируется с помощью дерева наследования. Рассмотрим небольшой пример наследования:
// Объявляем класс Parent class Parent { int x; }
// Объявляем класс Child и наследуем // его от класса Parent class Child extends Parent { int y; }
// Объявляем второго наследника // класса Parent - класс Child2 class Child2 extends Parent { int z; }
В каждом классе объявлено поле с уникальным именем. Будем рассматривать это поле как пример набора уникальных свойств, присущих некоторому объектному типу.
Три объявленных класса могут порождать три вида объектов. Объекты класса Parent обладают только одним полем x, а значит, только ссылки типа Parent могут ссылаться на такие объекты. Объекты класса Child обладают полем y и полем x, полученным по наследству от класса Parent. Стало быть, на такие объекты могут указывать ссылки типа Child или Parent. Второй случай уже иллюстрировался следующим примером:
Parent p = new Child();
Обратите внимание, что с помощью такой ссылки p можно обращаться лишь к полю x созданного объекта. Поле y недоступно, так как компилятор, проверяя корректность выражения p.y, не может предугадать, что ссылка p будет указывать на объект типа Child во время исполнения программы. Он анализирует лишь тип самой переменной, а она объявлена как Parent, но в этом классе нет поля y, что и вызовет ошибку компиляции.
Аналогично, объекты класса Child2 обладают полем z и полем x, полученным по наследству от класса Parent. Значит, на такие объекты могут указывать ссылки типа Child2 или Parent.
Таким образом, ссылки типа Parent могут указывать на объект любого из трех рассматриваемых типов, а ссылки типа Child и Child2 - только на объекты точно такого же типа. Теперь можно перейти к преобразованию ссылочных типов на основе такого дерева наследования.
Расширение означает переход от более конкретного типа к менее конкретному, т.е. переход от детей к родителям. В нашем примере преобразование от любого наследника (Child, Child2) к родителю (Parent) есть расширение, переход к более общему типу. Подобно случаю с примитивными типами, этот переход производится самой JVM при необходимости и незаметен для разработчика, то есть не требует никаких дополнительных усилий, так как он всегда проходит успешно: всегда можно обращаться к объекту, порожденному от наследника, по типу его родителя.
Parent p1=new Child(); Parent p2=new Child2();
В обеих строках переменным типа Parent присваивается значение другого типа, а значит, происходит преобразование. Поскольку это расширение, оно производится автоматически и всегда успешно.
Обратите внимание, что при подобном преобразовании с самим объектом ничего не происходит. Несмотря на то, что, например, поле y класса Child теперь недоступно, это не означает, что оно исчезло. Такое существенное изменение структуры объекта невозможно. Он был порожден от класса Child и сохраняет все его свойства. Изменился лишь тип ссылки, через которую идет обращение к объекту. Эту ситуацию можно условно сравнить с рассматриванием некоего предмета через подзорную трубу. Если перейти от трубы с большим увеличением к более слабой, то видимых деталей станет меньше, но сам предмет, конечно, никак от этого не изменится.
Следующие преобразования являются расширяющими:
от класса A к классу B, если A наследуется от B (важным частным случаем является преобразование от любого ссылочного типа к Object);от null-типа к любому объектному типу.
Второй случай иллюстрируется следующим примером:
Parent p=null;
Пустая ссылка null не обладает каким-либо конкретным ссылочным типом, поэтому иногда говорят о специальном null-типе. Однако на практике важно, что такое значение можно прозрачно преобразовать к любому объектному типу.
С изучением остальных ссылочных типов (интерфейсов и массивов) этот список будет расширяться.
Обратный переход, то есть движение по дереву наследования вниз, к наследникам, является сужением. Например, для рассматриваемого случая, переход от ссылки типа Parent, которая может ссылаться на объекты трех классов, к ссылке типа Child, которая может ссылаться на объекты лишь одного из трех классов, очевидно, является сужением. Такой переход может оказаться невозможным. Если ссылка типа Parent ссылается на объект типа Parent или Child2, то переход к Child невозможен, ведь в обоих случаях объект не обладает полем y, которое объявлено в классе Child. Поэтому при сужении разработчику необходимо явным образом указывать на то, что необходимо попытаться провести такое преобразование. JVM во время исполнения проверит корректность перехода. Если он возможен, преобразование будет проведено. Если же нет - возникнет ошибка.
Применение класса BorderLayout
Добавляя компоненты к контейнеру, вы должны использовать метод add с двумя параметрами, первый из которых указывает направление размещения, а второй - ссылку на добавляемый объект:
add("North", btn1); add("East", btn2); add("West", btn3); add("South", btn4); add("Center", btn5);
Назад Вперед
Контакты
О компании
Новости
Вакансии
Правовые аспекты
Условия использования
Торговые марки
Copyright 1994-2005 Sun Microsystems, Inc.
printmenus();
Программные продуктыРабочие станции и тонкие клиенты
Серверы
Системы хранения данных
Посмотреть все
»Java 2 Standard Edition
Developer Tools
Top Downloads
New Downloads
Патчи и обновления
Посмотреть все
»Истории успеха
The Sun Grid
Партнерские программы
Посмотреть все
»Программы SunSpectrum
Консалтинг
Услуги инсталляции
Поддержка ПО
Посмотреть все
»Сертификация
Авторизованные учебные центры
Посмотреть все
»События
Lab Downloads
Посмотреть все
»Применение приведений
Теперь, когда рассмотрены все виды преобразований, перейдем к ситуациям в коде, где могут встретиться или потребоваться приведения.
Такие ситуации могут быть сгруппированы следующим образом.
Присвоение значений переменным (assignment). Не все переходы допустимы при таком преобразовании - ограничения выбраны таким образом, чтобы не могла возникнуть ошибочная ситуация.Вызов метода. Это преобразование применяется к аргументам вызываемого метода или конструктора. Допускаются почти те же переходы, что и для присвоения значений. Такое приведение никогда не порождает ошибок. Так же приведение осуществляется при возвращении значения из метода.Явное приведение. В этом случае явно указывается, к какому типу требуется привести исходное значение. Допускаются все виды преобразований, кроме приведений к строке и запрещенных. Может возникать ошибка времени исполнения программы.Оператор конкатенации производит преобразование к строке своих аргументов.Числовое расширение (numeric promotion). Числовые операции могут потребовать изменения типа аргумента(ов). Это преобразование имеет особое название - расширение (promotion), так как выбор целевого типа может зависеть не только от исходного значения, но и от второго аргумента операции.
Рассмотрим все случаи более подробно.
Присвоение значений
Такие ситуации неоднократно применялись в этой лекции для иллюстрации видов преобразования. Приведение может потребоваться, если переменной одного типа присваивается значение другого типа. Возможны следующие комбинации.
Если сочетание этих двух типов образует запрещенное приведение, возникнет ошибка. Например, примитивные значения нельзя присваивать объектным переменным, включая следующие примеры:
// пример вызовет ошибку компиляции
// примитивное значение нельзя // присвоить объектной переменной Parent p = 3;
// приведение к классу-"обертке" // также запрещено Long a=5L;
// универсальное приведение к строке // возможно только для оператора + String s=true;
Далее, если сочетание этих двух типов образует расширение (примитивных или ссылочных типов), то оно будет осуществлено автоматически, неявным для разработчика образом:
int i=10; long a=i; Child c = new Child(); Parent p=c;
Если же сочетание оказывается сужением, то возникает ошибка компиляции, такой переход не может быть проведен неявно:
// пример вызовет ошибку компиляции int i=10; short s=i; // ошибка! сужение! Parent p = new Child(); Child c=p; // ошибка! сужение!
Как уже упоминалось, в подобных случаях необходимо выполнять преобразование явно:
int i=10; short s=(short)i; Parent p = new Child(); Child c=(Child)p;
Более подробно явное сужение рассматривается ниже.
Здесь может вызвать удивление следующая ситуация, которая не порождает ошибок компиляции:
byte b=1; short s=2+3; char c=(byte)5+'a';
В первой строке переменной типа byte присваивается значение целочисленного литерала типа int, что является сужением. Во второй строке переменной типа short присваивается результат сложения двух литералов типа int, а тип этой суммы также int. Наконец, в третьей строке переменной типа char присваивается результат сложения числа 5, приведенного к типу byte, и символьного литерала.
Однако все эти примеры корректны. Для удобства разработчика компилятор проводит дополнительный анализ при присвоении значений переменным типа byte, short и char. Если таким переменным присваивается величина типа byte, short, char или int, причем ее значение может быть получено уже на момент компиляции, и оказывается, что это значение укладывается в диапазон типа переменной, то явного приведения не требуется. Если бы такой возможности не было, пришлось бы писать так:
byte b=(byte)1; // преобразование необязательно short s=(short)(2+3); // преобразование необязательно char c=(char)((byte)5+'a'); // преобразование необязательно
// преобразование необходимо, так как // число 200 не укладывается в тип byte byte b2=(byte)200;
Работа с системой Layout Manager
Назад Вперед
В предыдущей статье мы рассказали вам о том, как создавать компоненты и размещать их в контейнере. Однако предложенный способ размещения компонент в окне контейнера едва ли можно назвать удобным, так как заранее трудно предугадать, на каком месте окажется тот или иной орган управления.
К счастью, имеются способы, позволяющие контролировать размещение отдельных компонент в окне контейнера. И хотя эти способы не позволяют задавать конкретные координаты и размеры органов управления, использовнные схемы размещения компонент будут правильно работать на любой аппаратной платформе (не забывайте, что Java создавалась как средство разработки приложений, способных выполняться на любой платформе).
В чем трудность создания пользовательского интерфейса для мультиплатформных систем?
В том, что разработчик приложения никогда не знает характеристики устройства отображения, установленные у пользователя. Он, в частности, не может заранее знать разрешение монитора, размер системного шрифта и другие характеристики, необходимые для компоновки диалоговых панелей в терминах абсолютных координат.
Средства пользовательского интерфейса AWT способны динамически измнять размеры компонент, подгоняя их "по месту" в системе пользователя. В результате значительно повышается вероятность того что внешний вид диалоговой панели, в каком она предстанет перед пользователем, будет похож на то, что ожидал разработчик.
Назад Вперед
Контакты
О компании
Новости
Вакансии
Правовые аспекты
Условия использования
Торговые марки
Copyright 1994-2005 Sun Microsystems, Inc.
printmenus();
Программные продуктыРабочие станции и тонкие клиенты
Серверы
Системы хранения данных
Посмотреть все
»Java 2 Standard Edition
Developer Tools
Top Downloads
New Downloads
Патчи и обновления
Посмотреть все
»Истории успеха
The Sun Grid
Партнерские программы
Посмотреть все
»Режим BorderLayout
Назад Вперед
При использовании режима BorderLayout окно контейнера разделяется на рамку и центральную часть. При размещении компонент указывается направление от центра окна, в котором слудует размещать компоненты.
Режим CardLayout
Назад Вперед
Режим CardLayout предназначен для создания набора диалоговых панелей, которые можно показывать по очереди в одном окне прямоугольной формы. Обычно для управления процессом перебора диалоговых панелей в режиме CardLayout используются отдельные органы управления, расположенные в другой панели или даже в другом аплете на той же самой странице сервера Web.
Класс CardLayout содержит два конструктора и несколько методов.
Режим FlowLayout
Назад Вперед
В этом режиме мы добавляли компоненты во всех примерах аплетов, приведенных ранее, так как по умолчанию для аплетов используется именно режим FlowLayout.
Режим GridBagLayout
Назад Вперед
Режим GridBagLayout намного сложнее только что описанного режима GridLayout. Он позволяет размещать компоненты разного размера в таблице, задавая при этом для отдельных компонент размеры отступов и количество занимаемых ячеек.
Сейчас мы не будем рассматривать этот режим, так как сходные результаты могут быть достигнуты другими, менее сложными способами. Например, вы можете создать в контейнере несколько панелей, использовав внутри каждой свой метод размещения компонент.
Если вы создаете аплеты для размещения в документах HTML, никто не заставляет вас ограничиваться только одним аплетом для одного документа HTML - вы можете разместить там произвольное количество аплетов, организовав взаимодействие с одной стороны, между отдельными аплетами, а с другой - между аплетами и расширениями сервера Web.
В интегрированной системе разработки приложений Java WorkShop версии 2.0 имеется встроенная система визуального проектирования пользовательского интерфейса, в результате работы которой создаются исходные тексты классов. Размещение органов управления при этом выполняется интерактивными средствами.
Назад Вперед
Контакты
О компании
Новости
Вакансии
Правовые аспекты
Условия использования
Торговые марки
Copyright 1994-2005 Sun Microsystems, Inc.
printmenus();
Программные продуктыРабочие станции и тонкие клиенты
Серверы
Системы хранения данных
Посмотреть все
»Java 2 Standard Edition
Developer Tools
Top Downloads
New Downloads
Патчи и обновления
Посмотреть все
»Истории успеха
The Sun Grid
Партнерские программы
Посмотреть все
»Программы SunSpectrum
Консалтинг
Услуги инсталляции
Поддержка ПО
Посмотреть все
»Сертификация
Авторизованные учебные центры
Посмотреть все
»События
Lab Downloads
Посмотреть все
»Режим GridLayout
Назад Вперед
В режиме GridLayout компоненты размещаются в ячейках таблицы, параметры которой можно задать с помощью конструкторов класса GridLayout.
При размещении компонент внутри ячеек таблицы все они получают одинаковые размеры. Если один из параметров, задающих размерность таблицы, равен нулю, это означает, что соответствующий столбец или строка может содержать любое количество элементов.
Заметим, что оба параметра rows и cols не могут быть равны нулю одновременно.
Приведем описание конструкторов класса GridLayout.
Режимы системы Layout Manager
Назад Вперед
Прежде чем мы рассмотрим различные режимы компоновки системы Layout Manager, вспомним, как происходит наследование класса Applet (рис. 1).
Рис. 1. Наследование класса Applet
Класс Applet наследуется от класса Panel, который, в свою очередь, наследуется от класса Container и Component. Класс Container пользуется интерфейсом LayoutManager, что позволяет выбирать для контейнеров один из нескольких режимов размещения компонент в окне контейнера.
Что же касается класса Panel, то для него по умолчанию выбирается режим размещения компонент с названием Flow Layout. Разумеется, вы можете выбрать другой режим размещения, указав его явным образом.
Ниже мы перечислили все возможные режимы системы Layout Manager:
Режим размещения компонент | Описание |
FlowLayout | Компоненты заполняют окно контейнера "потоком" по мере их добавления методом add. Они размещаются слева направо и сверху вниз |
GridLayout | Компоненты размещаются в виде таблицы по мере добавления слева направо и сверху вниз. Для этой таблицы можно указать количество столбцов и строк |
GridBagLayout | Аналогично предыдущему, однако при добавлении компонент в таблицу можно указать координаты ячейки, в которую помещается компонента |
BorderLayout | При размещении компоненты указывается одно из нескольких направлений: юг, север, запад, восток, центр. Направление определяется относительно центра окна контейнера |
CardLayout | Размещение компонент друг над другом в одном окне. Этот режим позволяет организовать набор диалоговых панелей в виде блокнота |
Каждому режиму соответсвует одноименный класс, методы и конструкторы которого позволяют выбирать различные компоновки.
Далее на примере конкретного приложения мы рассмотрим использование перечисленных выше режимов системы Layout Manager.
Назад Вперед
Контакты
О компании
Новости
Вакансии
Правовые аспекты
Условия использования
Торговые марки
Copyright 1994-2005 Sun Microsystems, Inc.
printmenus();
Программные продуктыТип переменной и тип ее значения
Теперь, когда были подробно рассмотрены все примеры преобразований, нужно вернуться к вопросу переменной и ее значений.
Как уже говорилось, переменная определяется тремя базовыми характеристиками: имя, тип, значение. Имя дается произвольным образом и никак не сказывается на свойствах переменной. А вот значение всегда имеет некоторый тип, не обязательно совпадающий с типом самой переменной. Поэтому необходимо рассмотреть все возможные типы переменных и выяснить, значения каких типов они могут иметь.
Начнем с переменных примитивных типов. Поскольку эти переменные действительно хранят само значение, то их тип всегда точно совпадает с типом значения.
Проиллюстрируем это правило на примере:
byte b=3; char c='A'+3; long m=b+c; double d=m-3F ;
Здесь переменная b будет хранить значение типа byte после сужения целочисленного литерала типа int. Переменная c будет хранить тип char после того, как компилятор осуществит сужающее преобразование результата суммирования, который будет иметь тип int. Для переменной m выполнится расширение результата суммирования типа от int к типу long. Наконец, переменная d будет хранить значение типа double, получившееся в результате расширения результата разности, который имеет тип float.
Переходим к ссылочным типам. Во-первых, значение любой переменной такого типа - ссылка, которая может указывать лишь на объекты, порожденные от тех или иных классов, и далее обсуждаются только свойства данных классов. (Также объекты могут порождаться от массивов, эта тема рассматривается в отдельной лекции.)
Кроме того, ссылочная переменная любого типа может иметь значение null. Большинство действий над такой переменной, например, обращение к полям или методам, приведет к ошибке.
Итак, какова связь между типом ссылочной переменной и ее значением? Здесь главное ограничение - проверка компилятора, который следит, чтобы все действия, выполняющиеся над объектом, были корректны. Компилятор не может предугадать, на объект какого класса будет реально ссылаться та или иная переменная. Все, чем он располагает, - тип самой переменной. Именно его и использует компилятор для проверок. А значит, все допустимые значения переменной должны гарантированно обладать свойствами, определенными в классе-типе этой переменной. Такую гарантию дает только наследование. Отсюда получаем правило: ссылочная переменная типа A может указывать на объекты, порожденные от самого типа A или его наследников.
Point p = new Point();
В этом примере переменная и ее значение одинакового типа, поэтому над объектом можно совершать все возможные для данного класса действия.
Parent p = new Child();
Такое присвоение корректно, так как класс Child порожден от Parent. Однако теперь допустимые действия над переменной p, а значит, над объектом, только что созданным на основе класса Child, ограничены возможностями класса Parent. Например, если в классе Child определен некий новый метод newChildMethod(), то попытка его вызвать p.newChildMethod() будет порождать ошибку компиляции. Необходимо подчеркнуть, что никаких изменений с самим объектом не происходит, ограничение порождается используемым способом доступа к этому объекту - переменной типа Parent.
Чтобы показать, что объект не потерял никаких свойств, произведем следующее обращение:
((Child)p).newChildMethod();
Здесь в начале проводится явное сужение к типу Child. Во время исполнения программы JVM проверит, совместим ли тип объекта, на который ссылается переменная p, с типом Child. В нашем случае это именно так. В результате получается ссылка типа Child, поэтому становится допустимым вызов метода newChildMethod(), который вызывается у объекта, созданного в предыдущей строке.
Обратим внимание на важный частный случай - переменная типа Object может ссылаться на объекты любого типа.
В дальнейшем, с изучением новых типов (абстрактных классов, интерфейсов, массивов) этот список будет продолжаться, а пока коротко обобщим то, что было рассмотрено в данном разделе.
Примитивный | В точности совпадает с типом переменной |
Ссылочный | nullсовпадающий с типом переменнойклассы-наследники от типа переменной |
Object | nullлюбой ссылочный |
Тождественное преобразование
Самым простым является тождественное преобразование. В Java преобразование выражения любого типа к точно такому же типу всегда допустимо и успешно выполняется.
Зачем нужно тождественное приведение? Есть две причины для того, чтобы выделить такое преобразование в особый вид.
Во-первых, с теоретической точки зрения теперь можно утверждать, что любой тип в Java может участвовать в преобразовании, хотя бы в тождественном. Например, примитивный тип boolean нельзя привести ни к какому другому типу, кроме него самого.
Во-вторых, иногда в Java могут встречаться такие выражения, как длинный последовательный вызов методов:
print(getCity().getStreet().getHouse().getFlat().getRoom());
При исполнении такого выражения сначала вызывается первый метод getCity(). Можно предположить, что возвращаемым значением будет объект класса City. У этого объекта далее будет вызван следующий метод getStreet(). Чтобы узнать, значение какого типа он вернет, необходимо посмотреть описание класса City. У этого значения будет вызван следующий метод (getHouse()), и так далее. Чтобы узнать результирующий тип всего выражения, необходимо просмотреть описание каждого метода и класса.
Компилятор без труда справится с такой задачей, однако разработчику будет нелегко проследить всю цепочку. В этом случае можно воспользоваться тождественным преобразованием, выполнив приведение к точно такому же типу. Это ничего не изменит в структуре программы, но значительно облегчит чтение кода:
print((MyFlatImpl)(getCity().getStreet().getHouse().getFlat()));
Унарное числовое расширение
Это преобразование расширяет примитивные типы byte, short или char до типов int по правилам расширения примитивных типов.
Унарное числовое расширение может выполняться при следующих операциях:
унарные операции + и -;битовое отрицание ~;операции битового сдвига <<, >>, >>>.
Операторы сдвига имеют два аргумента, но они расширяются независимо друг от друга, поэтому данное преобразование является унарным. Таким образом, результат выражения 5<<3L имеет тип int. Вообще, результат операторов сдвига всегда имеет тип int или long.
Примеры работы всех этих операторов с учетом расширения подробно рассматривались в предыдущих лекциях.
Виды приведений
В Java предусмотрено семь видов приведений:
тождественное (identity);расширение примитивного типа (widening primitive);сужение примитивного типа (narrowing primitive);расширение объектного типа (widening reference);сужение объектного типа (narrowing reference);преобразование к строке (String);запрещенные преобразования (forbidden).
Рассмотрим их по отдельности.
Как уже говорилось, Java является
Как уже говорилось, Java является строго типизированным языком, а это означает, что каждое выражение и каждая переменная имеет строго определенный тип уже на момент компиляции. Тип устанавливается на основе структуры применяемых выражений и типов литералов, переменных и методов, используемых в этих выражениях.
Например:
long a=3; a = 5+'A'+a; print("a="+Math.round(a/2F));
Рассмотрим, как в этом примере компилятор устанавливает тип каждого выражения и какие преобразования (conversion) типов необходимо осуществить при каждом действии.
В первой строке литерал 3 имеет тип по умолчанию, то есть int. При присвоении этого значения переменной типа long необходимо провести преобразование.Во второй строке сначала производится сложение значений типа int и char. Второй аргумент будет преобразован так, чтобы операция проводилась с точностью в 32 бита. Второй оператор сложения опять потребует преобразования, так как наличие переменной a увеличивает точность до 64 бит.В третьей строке сначала будет выполнена операция деления, для чего значение long надо будет привести к типу float, так как второй операнд - дробный литерал. Результат будет передан в метод Math.round, который произведет математическое округление и вернет целочисленный результат типа int. Это значение необходимо преобразовать в текст, чтобы осуществить дальнейшую конкатенацию строк. Как будет показано ниже, эта операция проводится в два этапа - сначала простой тип приводится к объектному классу-"обертке" (в данном случае int к Integer), а затем у полученного объекта вызывается метод toString(), что дает преобразование к строке.
Данный пример показывает, что даже простые строки могут содержать многочисленные преобразования, зачастую незаметные для разработчика. Часто бывают и такие случаи, когда программисту необходимо явно изменить тип некоторого выражения или переменной, например, чтобы воспользоваться подходящим методом или конструктором.
Вспомним уже рассмотренный пример:
byte b=1; byte c=(byte)-b; int i=c;
Здесь во второй строке необходимо провести явное преобразование, чтобы присвоить значение типа int переменной типа byte. В третьей же строке обратное приведение производится автоматически, неявным для разработчика образом.
Рассмотрим сначала, какие переходы между различными типами можно осуществить.
Вызов метода
Это приведение возникает в случае, когда вызывается метод с объявленными параметрами одних типов, а при вызове передаются аргументы других типов. Объявление методов рассматривается в следующих лекциях курса, однако такой простой пример вполне понятен:
// объявление метода с параметром типа long void calculate(long l) { ... }
void main() { calculate(5); }
Как видно, при вызове метода передается значение типа int, а не long, как определено в объявлении этого метода.
Здесь компилятор предпринимает те же шаги, что и при приведении в процессе присвоения значений переменным. Если типы образуют запрещенное преобразование, возникнет ошибка.
// пример вызовет ошибку компиляции
void calculate(long a) { ... }
void main() { calculate(new Long(5)); // здесь будет ошибка }
Если сужение, то компилятор не сможет осуществить приведение и потребуются явные указания.
void calculate(int a) { ... }
void main() { long a=5; // calculate(a); // сужение! так будет ошибка. calculate((int)a); // корректный вызов }
Наконец, в случае расширения, компилятор осуществит приведение сам, как и было показано в примере в начале этого раздела.
Надо отметить, что, в отличие от ситуации присвоения, при вызове методов компилятор не производит преобразований примитивных значений от byte, short, char или int к byte, short или char. Это привело бы к усложнению работы с перегруженными методами. Например:
// пример вызовет ошибку компиляции
// объявляем перегруженные методы // с аргументами (byte, int) и (short, short) int m(byte a, int b) { return a+b; } int m(short a, short b) { return a-b; }
void main() { print(m(12, 2)); // ошибка компиляции! }
В этом примере компилятор выдаст ошибку, так как при вызове аргументы имеют тип (int, int), а метода с такими параметрами нет. Если бы компилятор проводил преобразование для целых величин, подобно ситуации с присвоением значений, то пример стал бы корректным, но пришлось бы прилагать дополнительные усилия, чтобы указать, какой из двух возможных перегруженных методов хотелось бы вызвать.
Аналогичное преобразование потребуется при возвращении значения из метода, если тип результата и заявленный тип возвращаемого значения не совпадают.
long get() { return 5; }
Хотя в выражении return указан целочисленный литерал типа int, во всех местах, где будет вызван этот метод, будет получено значение типа long. Для такого преобразования действуют те же правила, что и для присвоения значения.
В заключение рассмотрим пример, включающий в себя все рассмотренные случаи преобразования:
short get(Parent p) { return 5+'A'; // приведение при возвращении значения }
void main() { long a = // приведение при присвоении значения get(new Child()); // приведение при вызове метода }
В этой лекции были рассмотрены
В этой лекции были рассмотрены правила работы с типами данных в строго типизированном языке Java. Поскольку компилятор строго отслеживает тип каждой переменной и каждого выражения, в случае изменения этого типа необходимо четко понимать, какие действия допустимы, а какие нет, с точки зрения компилятора и виртуальной машины.
Были рассмотрены все виды приведения типов в Java, то есть переход от одного типа к другому. Они разбиваются на 7 групп, начиная с тождественного и заканчивая запрещенными. Основные 4 вида определяются сужающими или расширяющими переходами между простыми или ссылочными типами. Важно помнить, что при явном сужении числовых типов старшие биты просто отбрасываются, что порой приводит к неожиданному результату. Что касается преобразования ссылочных значений, то здесь действует правило - преобразование никогда не порождает новых и не изменяет существующих объектов. Меняется лишь способ работы с ними.
Особенным в Java является преобразование к строке.
Затем были рассмотрены все ситуации в программе, где могут происходить преобразования типов. Прежде всего, это присвоение значений, когда преобразование зачастую происходит незаметно для программиста. Вызов метода во многом похож на инициализацию. Явное приведение позволяет осуществить желаемый переход в том случае, когда компилятор не позволяет сделать это неявно. Преобразование при выполнении числовых операций оказывает существенное влияние на результат.
В заключение была рассмотрена связь между типом переменной и типом ее значения.
Запрещенные преобразования
Не все переходы между произвольными типами допустимы. Например, к запрещенным преобразованиям относятся: переходы от любого ссылочного типа к примитивному, от примитивного - к ссылочному (кроме преобразований к строке). Уже упоминавшийся пример - тип boolean - нельзя привести ни к какому другому типу, кроме boolean (как обычно - за исключением приведения к строке). Затем, невозможно привести друг к другу типы, находящиеся не на одной, а на соседних ветвях дерева наследования. В примере, который рассматривался для иллюстрации преобразований ссылочных типов, переход от Child к Child2 запрещен. В самом деле, ссылка типа Child может указывать на объекты, порожденные только от класса Child или его наследников. Это исключает вероятность того, что объект будет совместим с типом Child2.
Этим список запрещенных преобразований не исчерпывается. Он довольно велик, и в то же время все варианты достаточно очевидны, поэтому подробно рассматриваться не будут. Желающие могут получить полную информацию из спецификации.
Разумеется, попытка осуществить запрещенное преобразование вызовет ошибку компиляции.
Аплет Options
Назад Вперед
Аплет Options демонстрирует методики работы с панелями, а также с различными режимами системы Layout Manager.
В окне аплета Options мы создали три панели (рис. 2).
Рис. 2. Окно аплета Options
В верхней панели отображается текстовая строка First panel. Цвет и шрифт этой строки, а также цвет фона можно задавать при помощи второй панели, расположенной в центре окна нашего аплета.
Вторая панель представляет собой блокнот, на страницах которого находятся списки цвета фона, текста и шрифтов. С помощью кнопок нижней панели вы можете перелистывать страницы этого блокнота. На рис. 3 и 4 мы показали страницы, предназначенные для выбора цвета фона и цвета текста, соответственно.
Рис. 3. Выбор цвета фона
Рис. 4. Выбор цвета текста
Нажимая кнопки Background Color, Foreground Color и Set Font, вы можете отображать нужные вам страницы блокнота. С помощью кнопок Next и Prev можно перебирать страницы блокнота в прямом или обратном направлении, соответственно.
Назад Вперед
Контакты
О компании
Новости
Вакансии
Правовые аспекты
Условия использования
Торговые марки
Copyright 1994-2005 Sun Microsystems, Inc.
printmenus();
Программные продуктыРабочие станции и тонкие клиенты
Серверы
Системы хранения данных
Посмотреть все
»Java 2 Standard Edition
Developer Tools
Top Downloads
New Downloads
Патчи и обновления
Посмотреть все
»Истории успеха
The Sun Grid
Партнерские программы
Посмотреть все
»Программы SunSpectrum
Консалтинг
Услуги инсталляции
Поддержка ПО
Посмотреть все
»Сертификация
Авторизованные учебные центры
Посмотреть все
»События
Lab Downloads
Посмотреть все
»Добавление компонент в панели
Назад Вперед
Для добавления компонент в панель вы должны указать, для какой панели вызывается метод add, например:
Botton btn1; Botton btn2; btn1 = new Button(); btn2 = new Button(); pBottomPanel.add(btn1); pBottomPanel.add(btn2);
Назад Вперед
Контакты
О компании
Новости
Вакансии
Правовые аспекты
Условия использования
Торговые марки
Copyright 1994-2005 Sun Microsystems, Inc.
printmenus();
Программные продуктыРабочие станции и тонкие клиенты
Серверы
Системы хранения данных
Посмотреть все
»Java 2 Standard Edition
Developer Tools
Top Downloads
New Downloads
Патчи и обновления
Посмотреть все
»Истории успеха
The Sun Grid
Партнерские программы
Посмотреть все
»Программы SunSpectrum
Консалтинг
Услуги инсталляции
Поддержка ПО
Посмотреть все
»Сертификация
Авторизованные учебные центры
Посмотреть все
»События
Lab Downloads
Посмотреть все
»Добавление панелей
Назад Вперед
Создав панели, вы можете добавить их в окно аплета, вызвав метод add, как это показано ниже:
add(pTopPanel); add(pBottomPanel);
Заметим, что вы можете добавлять панели в панели, указывая, для какой панели нужно вызывать метод add:
Panel pLeft; Panel pRight; pLeft = new Panel(); pRight = new Panel(); pTopPanel.setLayout(new GridLayout(1, 2)); pTopPanel.add(pLeft); pTopPanel.add(pRight);
Здесь мы создали две панели pLeft и pRight, которые по нашему замыслу должны разделить пространство панели pTopPanel на две части по вертикали. Для обеспечения вертикального размещения панелей pLeft и pRight в панели pTopPanel мы вызвали для панели pTopPanel метод setLayout. При этом мы указали, что компоненты, добавляемые в эту панель, должны размещаться в таблице, состоящей из односй строки и двух столбцов.
Затем панели pLeft и pRight были добавлены в панель pTopPanel методом add.
Назад Вперед
Контакты
О компании
Новости
Вакансии
Правовые аспекты
Условия использования
Торговые марки
Copyright 1994-2005 Sun Microsystems, Inc.
printmenus();
Программные продуктыРабочие станции и тонкие клиенты
Серверы
Системы хранения данных
Посмотреть все
»Java 2 Standard Edition
Developer Tools
Top Downloads
New Downloads
Патчи и обновления
Посмотреть все
»Истории успеха
The Sun Grid
Партнерские программы
Посмотреть все
»Программы SunSpectrum
Консалтинг
Услуги инсталляции
Поддержка ПО
Посмотреть все
»Сертификация
Авторизованные учебные центры
Посмотреть все
»События
Lab Downloads
Посмотреть все
»Главный класс аплета Options
В главном классе аплета Options мы определили три поля с именами pPanel1, pCard и pControl:
FirstPanel pPanel1; CardPanel pCard; ControlPanel pControl;
В них хранятся ссылки на три класса, созданных нами для трех панелей.
Интерфейсы
Концепция абстрактных методов позволяет предложить альтернативу множественному наследованию. В Java класс может иметь только одного родителя, поскольку при множественном наследовании могут возникать конфликты, которые запутывают объектную модель. Например, если у класса есть два родителя, которые имеют одинаковый метод с различной реализацией, то какой из них унаследует новый класс? И какая будет функциональность родительского класса, который лишился своего метода?
Все эти проблемы не возникают в том случае, если наследуются только абстрактные методы от нескольких родителей. Даже если унаследовано несколько одинаковых методов, все равно у них нет реализации и можно один раз описать тело метода, которое будет использоваться при вызове любого из этих методов.
Именно так устроены интерфейсы в Java. От них нельзя порождать объекты, но другие классы могут реализовывать их.
Исходный текст аплета Options
Назад Вперед
Исходный текст аплета Options представлен в листинге 1.
Листинг 1. Файл Options.java
import java.applet.*; import java.awt.*;
public class Options extends Applet { FirstPanel pPanel1; CardPanel pCard; ControlPanel pControl;
public String getAppletInfo() { return "Name: Options"; }
public void init() { setLayout(new GridLayout(3, 1));
pPanel1 = new FirstPanel(); add(pPanel1);
pCard = new CardPanel(pPanel1); add(pCard);
pControl = new ControlPanel(pCard); add(pControl);
pPanel1.setBackground(Color.yellow); pPanel1.setForeground(Color.black);
repaint(); } }
class FirstPanel extends Panel { String szFontName = "TimesRoman";
public void paint(Graphics g) { Dimension dimAppWndDimension = getSize();
g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1);
g.setFont(new Font(szFontName, Font.PLAIN, 24)); g.drawString("First panel", 10, 50);
super.paint(g); } }
class CardPanel extends Panel { Panel pBgColor; Panel pFgColor; Panel pFont;
Panel pControlled;
Choice chBgColor; Choice chFgColor; Choice chFont;
Label lbBgColor; Label lbFgColor; Label lbFont;
public CardPanel(Panel pControlledPanel) { pControlled = pControlledPanel;
setLayout(new CardLayout(5, 5));
pBgColor = new Panel(); pFgColor = new Panel(); pFont = new Panel();
add("BgColor", pBgColor); add("FgColor", pFgColor); add("Font", pFont);
chBgColor = new Choice(); chFgColor = new Choice(); chFont = new Choice();
chBgColor.add("Yellow"); chBgColor.add("Green"); chBgColor.add("White");
chFgColor.add("Black"); chFgColor.add("Red"); chFgColor.add("Green");
chFont.add("TimesRoman"); chFont.add("Helvetica"); chFont.add("Courier");
lbBgColor = new Label("Background color"); lbFgColor = new Label("Foreground color"); lbFont = new Label("Font");
pBgColor.add(lbBgColor); pBgColor.add(chBgColor);
pFgColor.add(lbFgColor); pFgColor.add(chFgColor);
pFont.add(lbFont); pFont.add(chFont); }
public void paint(Graphics g) { Dimension dimAppWndDimension = getSize(); g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1);
super.paint(g); }
public boolean action(Event evt, Object obj) { Choice ch;
if(evt.target instanceof Choice) { ch = (Choice)evt.target;
if(evt.target.equals(chBgColor)) { if(ch.getSelectedIndex() == 0) pControlled.setBackground( Color.yellow);
else if(ch.getSelectedIndex() == 1) pControlled.setBackground( Color.green);
else if(ch.getSelectedIndex() == 2) pControlled.setBackground( Color.white); } else if(evt.target.equals(chFgColor)) { if(ch.getSelectedIndex() == 0) pControlled.setForeground( Color.black);
else if(ch.getSelectedIndex() == 1) pControlled.setForeground( Color.red);
else if(ch.getSelectedIndex() == 2) pControlled.setForeground( Color.green); } else if(evt.target.equals(chFont)) { if(ch.getSelectedIndex() == 0) ((FirstPanel)pControlled).szFontName = "TimesRoman";
else if(ch.getSelectedIndex() == 1) ((FirstPanel)pControlled).szFontName = "Helvetica";
else if(ch.getSelectedIndex() == 2) ((FirstPanel)pControlled).szFontName = "Courier"; } else { return false; } pControlled.repaint();
return true; } return false; } }
class ControlPanel extends Panel { Button btNext; Button btPrev; Button btBgColor; Button btFgColor; Button btFont; Panel pCard;
public ControlPanel(Panel pCardPanel) { pCard = pCardPanel; setLayout(new GridLayout(2,3));
btBgColor = new Button("Background Color"); btFgColor = new Button("Foreground Color"); btFont = new Button("Set Font"); btNext = new Button("Next"); btPrev = new Button("Prev");
add(btBgColor); add(btFgColor); add(btFont); add(btNext); add(btPrev); }
public boolean action(Event evt, Object obj) { if(evt.target instanceof Button) { if(evt.target.equals(btBgColor)) { ((CardLayout)pCard.getLayout()).show( pCard, "BgColor"); } else if(evt.target.equals(btFgColor)) { ((CardLayout)pCard.getLayout()).show( pCard, "FgColor"); } else if(evt.target.equals(btFont)) { ((CardLayout)pCard.getLayout()).show( pCard, "Font"); } else if(evt.target.equals(btNext)) { ((CardLayout)pCard.getLayout()).next( pCard); } else if(evt.target.equals(btPrev)) { ((CardLayout)pCard.getLayout()). previous(pCard); } else { return false; } return true; } return false; } }
Класс CardPanel
С помощью класса CardPanel мы создали панель для блокнота, содержащего три страницы. Этот класс, так же как и предыдущий, создан на базе класса Panel.
Класс ControlPanel
Класс ControlPanel создан для нижней панели с управляющими кнопками.
Класс FirstPanel
Мы создали класс FirstPanel на базе класса Panel, определив в нем одно поле типа String и переопределив метод paint:
class FirstPanel extends Panel { . . . }
Текстовое поле szFontName хранит название шрифта, с использованием которого в окне верхней панели отображается текстовая строка:
String szFontName = "TimesRoman";
Метод paint определяет текущие размеры панели и рисует вокруг нее прямоугольную рамку:
Dimension dimAppWndDimension = getSize();
g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1);
Далее метод paint выбирает в контекст отображения, связанный с панелью, шрифт с названием szFontName и рисует текстовую строку:
g.setFont(new Font(szFontName, Font.PLAIN, 24)); g.drawString("First panel", 10, 50);
Заметим, что сразу после запуска аплета рамка и строка будут нарисованы с использованием черного цвета, выбранного в контекст отображения панели по умолчанию. В дальнейшем вы можете изменить этот цвет при помощи соответствующей страницы блокнота, реализованного во второй панели.
Последнее действие, которое выполняет метод paint первой панели - вызов метода paint из родительского класса:
super.paint(g);
Это приводит к перерисовке окна аплета.
Ключевое слово abstract
Следующее важное понятие, которое необходимо рассмотреть,– ключевое слово abstract.
Иногда имеет смысл описать только заголовок метода, без его тела, и таким образом объявить, что данный метод будет существовать в этом классе. Реализацию этого метода, то есть его тело, можно описать позже.
Рассмотрим пример. Предположим, необходимо создать набор графических элементов, неважно, каких именно. Например, они могут представлять собой геометрические фигуры – круг, квадрат, звезда и т.д.; или элементы пользовательского интерфейса – кнопки, поля ввода и т.д. Сейчас это не имеет решающего значения. Кроме того, существует специальный контейнер, который занимается их отрисовкой. Понятно, что внешний вид каждой компоненты уникален, а значит, соответствующий метод (назовем его paint()) будет реализован в разных элементах по-разному.
Но в то же время у компонент может быть много общего. Например, любая из них занимает некоторую прямоугольную область контейнера. Сложные контуры фигуры необходимо вписать в прямоугольник, чтобы можно было анализировать перекрытия, проверять, не вылезает ли компонент за границы контейнера, и т.д. Каждая фигура может иметь цвет, которым ее надо рисовать, может быть видимой, или невидимой и т.д. Очевидно, что полезно создать родительский класс для всех компонент и один раз объявить в нем все общие свойства, чтобы каждая компонента лишь наследовала их.
Но как поступить с методом отрисовки? Ведь родительский класс не представляет собой какую-либо фигуру, у него нет визуального представления. Можно объявить метод paint() в каждой компоненте независимо. Но тогда контейнер должен будет обладать сложной функциональностью, чтобы анализировать, какая именно компонента сейчас обрабатывается, выполнять приведение типа и только после этого вызывать нужный метод.
Именно здесь удобно объявить абстрактный метод в родительском классе. У него нет внешнего вида, но известно, что он есть у каждого наследника. Поэтому заголовок метода описывается в родительском классе, тело метода у каждого наследника свое, а контейнер может спокойно пользоваться только базовым типом, не делая никаких приведений.
Приведем упрощенный пример:
// Базовая арифметическая операция abstract class Operation { public abstract int calculate(int a, int b); } // Сложение class Addition extends Operation { public int calculate(int a, int b) { return a+b; } }
// Вычитание class Subtraction extends Operation { public int calculate(int a, int b) { return a-b; } }
class Test { public static void main(String s[]) { Operation o1 = new Addition(); Operation o2 = new Subtraction();
o1.calculate(2, 3); o2.calculate(3, 5); } }
Видно, что выполнения операций сложения и вычитания в методе main() записываются одинаково.
Обратите внимание – поскольку абстрактный метод не имеет тела, после описания его заголовка ставится точка с запятой. А раз у него нет тела, то к нему нельзя обращаться, пока его наследники не опишут реализацию. Это означает, что нельзя создавать экземпляры класса, у которого есть абстрактные методы. Такой класс сам объявляется абстрактным.
Класс может быть абстрактным и в том случае, если у него нет абстрактных методов, но должен быть абстрактным, если такие методы есть. Разработчик может указать ключевое слово abstract в списке модификаторов класса, если хочет запретить создание экземпляров этого класса. Классы-наследники должны реализовать (implements) все абстрактные методы (если они есть) своего абстрактного родителя, чтобы их можно было объявлять неабстрактными и порождать от них экземпляры.
Конечно, класс не может быть одновременно abstract и final. Это же верно и для методов. Кроме того, абстрактный метод не может быть private, native, static.
Сам класс может без ограничений пользоваться своими абстрактными методами.
abstract class Test { public abstract int getX(); public abstract int getY(); public double getLength() { return Math.sqrt(getX()*getX()+ getY()*getY()); } }
Это корректно, поскольку метод getLength() может быть вызван только у объекта. Объект может быть порожден только от неабстрактного класса, который является наследником от Test, и должен был реализовать все абстрактные методы.
По этой же причине можно объявлять переменные типа абстрактный класс. Они могут иметь значение null или ссылаться на объект, порожденный от неабстрактного наследника этого класса.
Ключевые слова this и super
Эти ключевые слова уже упоминались, рассматривались и некоторые случаи их применения. Здесь они будут описаны более подробно.
Если выполнение кода происходит в динамическом контексте, то должен быть объект, ассоциированный с ним. В этом случае ключевое слово this возвращает ссылку на данный объект:
class Test { public Object getThis() { return this; // Проверим, куда указывает эта ссылка } public static void main(String s[]) { Test t = new Test(); System.out.println(t.getThis()==t); // Сравнение } }
Результатом работы программы будет:
true
То есть внутри методов слово this возвращает ссылку на объект, у которого этот метод вызван. Оно необходимо, если нужно передать аргумент, равный ссылке на данный объект, в какой-нибудь метод.
class Human { public static void register(Human h) { System.out.println(h.name+ " is registered."); }
private String name; public Human (String s) { name = s; register(this); // саморегистрация }
public static void main(String s[]) { new Human("John"); } }
Результатом будет:
John is registered.
Другое применение this рассматривалось в случае "затемняющих" объявлений:
class Human { private String name;
public void setName(String name) { this.name=name; } }
Слово this можно использовать для обращения к полям, которые объявляются ниже:
class Test { // int b=a; нельзя обращаться к // необъявленному полю! int b=this.a; int a=5; { System.out.println("a="+a+", b="+b); } public static void main(String s[]) { new Test(); } }
Результатом работы программы будет:
a=5, b=0
Все происходит так же, как и для статических полей – b получает значение по умолчанию для a, т.е. ноль, а затем a инициализируется значением 5.
Наконец, слово this применяется в конструкторах для явного вызова в первой строке другого конструктора этого же класса. Там же может применяться и слово super, только уже для обращения к конструктору родительского класса.
Другие применения слова super также связаны с обращением к родительскому классу объекта. Например, оно может потребоваться в случае переопределения (overriding) родительского метода.
Переопределением называют объявление метода, сигнатура которого совпадает с одним из методов родительского класса.
class Parent { public int getValue() { return 5; } }
class Child extends Parent { // Переопределение метода public int getValue() { return 3; }
public static void main(String s[]) { Child c = new Child();
// пример вызова переопределенного метода System.out.println(c.getValue()); } }
Вызов переопределенного метода использует механизм полиморфизма, который подробно рассматривается в конце этой лекции. Однако ясно, что результатом выполнения примера будет значение 3. Невозможно, используя ссылку типа Child, получить из метода getValue() значение 5, родительский метод перекрыт и уже недоступен.
Иногда при переопределении бывает полезно воспользоваться результатом работы родительского метода. Предположим, он делал сложные вычисления, а переопределенный метод должен вернуть округленный результат этих вычислений. Понятно, что гораздо удобнее обратиться к родительскому методу, чем заново описывать весь алгоритм. Здесь применяется слово super. Из класса наследника с его помощью можно обращаться к переопределенным методам родителя:
class Parent { public int getValue() { return 5; } }
class Child extends Parent {
// переопределение метода public int getValue() { // обращение к методу родителя return super.getValue()+1; }
public static void main(String s[]) { Child c = new Child(); System.out.println(c.getValue()); } }
Результатом работы программы будет значение 6.
Обращаться с помощью ключевого слова super к переопределенному методу родителя, т.е. на два уровня наследования вверх, невозможно. Если родительский класс переопределил функциональность своего родителя, значит, она не будет доступна его наследникам.
Поскольку ключевые слова this и super требуют наличия ассоциированного объекта, т.е. динамического контекста, использование их в статическом контексте запрещено.
Конструктор класса CardPanel
При создании объекта класса CardPanel мы передаем конструктору ссылку на верхнюю панель, параметрами которой нужно управлять. Конструктор записывает эту ссылку в поле pControlled:
public CardPanel(Panel pControlledPanel) { pControlled = pControlledPanel; . . . }
Затем конструктор устанавливает режим размещения CardLayout, оставляя зазор по вертикали и горизонтали, равный пяти пикселам:
setLayout(new CardLayout(5, 5));
На следующем этапе мы создаем три панели для страниц блокнота и добавляем их в панель CardPanel, задавая имена:
pBgColor = new Panel(); pFgColor = new Panel(); pFont = new Panel();
add("BgColor", pBgColor); add("FgColor", pFgColor); add("Font", pFont);
Теперь нам нужно создать и заполнить три списка, предназначенный для выбора цвета и шрифта. Эти списки создаются как объекты класса Choice:
chBgColor = new Choice(); chFgColor = new Choice(); chFont = new Choice();
После создания списки наполняются текстовыми строками. В каждый список мы добавляем по три строки:
chBgColor.add("Yellow"); chBgColor.add("Green"); chBgColor.add("White");
chFgColor.add("Black"); chFgColor.add("Red"); chFgColor.add("Green");
chFont.add("TimesRoman"); chFont.add("Helvetica"); chFont.add("Courier");
Для того чтобы снабдить списки подписями, мы создаем три объекта класса Label:
lbBgColor = new Label("Background color"); lbFgColor = new Label("Foreground color"); lbFont = new Label("Font");
Эти объекты, а также списки добавляются на свои страницы блокнота (то есть в свои панели):
pBgColor.add(lbBgColor); pBgColor.add(chBgColor);
pFgColor.add(lbFgColor); pFgColor.add(chFgColor);
pFont.add(lbFont); pFont.add(chFont);
На этом работа метода init заканчивается.
Конструктор класса ControlPanel
В задачу конструктора класса ControlPanel входит запоминание ссылки на панель блокнота, установка режима размещения компонент GridLayout, а также создание и добавление в нижнюю панель управляющих кнопок:
public ControlPanel(Panel pCardPanel) { pCard = pCardPanel; setLayout(new GridLayout(2,3));
btBgColor = new Button("Background Color"); btFgColor = new Button("Foreground Color"); btFont = new Button("Set Font"); btNext = new Button("Next"); btPrev = new Button("Prev");
add(btBgColor); add(btFgColor); add(btFont); add(btNext); add(btPrev); }
Кнопки располагаются в ячейках таблицы, содержащей две строки и три столбца. В целом конструктор класса ControlPanel не имеет никаких интересных особенностей.
Метод action
Метод action обрабатывает события, возникающие в результате выбора новых значений из списков, расположенных на страницах блокнота. Схема обработки событий не имеет никаких особенностей.
Вначале метод action проверяет, что событие вызвано списком класса Choice:
if(evt.target instanceof Choice) { . . . return true; } return false; }
События, связанные с изменением цвета фона, обрабатываются следующим образом:
ch = (Choice)evt.target;
if(evt.target.equals(chBgColor)) { if(ch.getSelectedIndex() == 0) pControlled.setBackground( Color.yellow);
else if(ch.getSelectedIndex() == 1) pControlled.setBackground( Color.green);
else if(ch.getSelectedIndex() == 2) pControlled.setBackground( Color.white); }
Здесь метод setBackground вызывается для объекта, ссылка на который передана конструктору класса и записана в поле pControlled. Это ссылка на панель, размещенную в верхней части окна нашего аплета.
Аналогичным образом изменяется цвет текста и рамки для верхней панели:
else if(evt.target.equals(chFgColor)) { if(ch.getSelectedIndex() == 0) pControlled.setForeground( Color.black);
else if(ch.getSelectedIndex() == 1) pControlled.setForeground( Color.red);
else if(ch.getSelectedIndex() == 2) pControlled.setForeground( Color.green); }
Для изменения шрифта мы устанавливаем новое значение переменной поля szFontName, определенной в классе FirstPanel:
else if(evt.target.equals(chFont)) { if(ch.getSelectedIndex() == 0) ((FirstPanel)pControlled).szFontName = "TimesRoman";
else if(ch.getSelectedIndex() == 1) ((FirstPanel)pControlled).szFontName = "Helvetica";
else if(ch.getSelectedIndex() == 2) ((FirstPanel)pControlled).szFontName = "Courier"; }
Для того чтобы адресоваться к полю szFontName, нам пришлось выполнить явное преобразование типа ссылки pControlled.
Последнее действие, которое совершает метод action - это перерисовка окна верхней панели, которая выполняется с помощью метода repaint:
pControlled.repaint();
Метод init
Прежде всего метод init устанавливает для окна аплета режим размещения GridLayout:
setLayout(new GridLayout(3, 1));
Окно аплета делится на три горизнтальные области, в которых мы будем размещать панели.
Панели создаются с помощью оператора new как объекты соответствующих классов, определенных в нашем приложении:
pPanel1 = new FirstPanel(); add(pPanel1);
pCard = new CardPanel(pPanel1); add(pCard);
pControl = new ControlPanel(pCard); add(pControl);
Для добавления панелей в окно аплета мы использовали метод add.
Далее метод init устанавливает начальные значения для цвета фона и текста верхней панели:
pPanel1.setBackground(Color.yellow); pPanel1.setForeground(Color.black);
Обратите внимание, что мы вызываем методы setBackground и setForeground для объекта pPanel1.
После выполнения всех этих действий метод init перерисовывает окно аплета, вызывая метод repaint:
repaint();
Методы
Рассмотрим случай переопределения (overriding) методов:
class Parent { public int getValue() { return 0; } } class Child extends Parent { public int getValue() { return 1; } }
И строки, демонстрирующие работу с этими методами:
Child c = new Child(); System.out.println(c.getValue()); Parent p = c; System.out.println(p.getValue());
Результатом будет:
1 1
Можно видеть, что родительский метод полностью перекрыт, значение 0 никак нельзя получить через ссылку, указывающую на объект класса Child. В этом ключевая особенность полиморфизма – наследники могут изменять родительское поведение, даже если обращение к ним производится по ссылке родительского типа. Напомним, что, хотя старый метод снаружи уже недоступен, внутри класса-наследника к нему все же можно обратиться с помощью super.
Рассмотрим более сложный пример:
class Parent { public int getValue() { return 0; } public void print() { System.out.println(getValue()); } }
class Child extends Parent { public int getValue() { return 1; } }
Что появится на консоли после выполнения следующих строк?
Parent p = new Child(); p.print();
С помощью ссылки типа Parent вызывается метод print(), объявленный в классе Parent. Из этого метода делается обращение к getValue(), которое в классе Parent возвращает 0. Но компилятор уже не может предсказать, к динамическому методу какого класса произойдет обращение во время работы программы. Это определяет виртуальная машина на основе объекта, на который указывает ссылка. И раз этот объект порожден от Child, то существует лишь один метод getValue().
Результатом работы примера будет:
1
Данный пример демонстрирует, что переопределение методов должно производиться с осторожностью. Если слишком сильно изменить логику их работы, нарушить принятые соглашения (например, начать возвращать null в качестве значения ссылочного типа, если родительский метод такого не допускал), это может привести к сбоям в работе родительского класса, а значит, объекта наследника. Более того, существуют и некоторые обязательные ограничения.
Вспомним, что заголовок метода состоит из модификаторов, возвращаемого значения, сигнатуры и throws-выражения. Сигнатура (имя и набор аргументов) остается неизменной, если говорить о переопределении. Возвращаемое значение также не может меняться, иначе это приведет к появлению двух разных методов с одинаковыми сигнатурами.
Рассмотрим модификаторы доступа.
Рассмотрим случай переопределения (overriding) методов:
class Parent { public int getValue() { return 0; } } class Child extends Parent { public int getValue() { return 1; } }
И строки, демонстрирующие работу с этими методами:
Child c = new Child(); System.out.println(c.getValue()); Parent p = c; System.out.println(p.getValue());
Результатом будет:
1 1
Можно видеть, что родительский метод полностью перекрыт, значение 0 никак нельзя получить через ссылку, указывающую на объект класса Child. В этом ключевая особенность полиморфизма – наследники могут изменять родительское поведение, даже если обращение к ним производится по ссылке родительского типа. Напомним, что, хотя старый метод снаружи уже недоступен, внутри класса-наследника к нему все же можно обратиться с помощью super.
Рассмотрим более сложный пример:
class Parent { public int getValue() { return 0; } public void print() { System.out.println(getValue()); } }
class Child extends Parent { public int getValue() { return 1; } }
Что появится на консоли после выполнения следующих строк?
Parent p = new Child(); p.print();
С помощью ссылки типа Parent вызывается метод print(), объявленный в классе Parent. Из этого метода делается обращение к getValue(), которое в классе Parent возвращает 0. Но компилятор уже не может предсказать, к динамическому методу какого класса произойдет обращение во время работы программы. Это определяет виртуальная машина на основе объекта, на который указывает ссылка. И раз этот объект порожден от Child, то существует лишь один метод getValue().
Результатом работы примера будет:
1
Данный пример демонстрирует, что переопределение методов должно производиться с осторожностью. Если слишком сильно изменить логику их работы, нарушить принятые соглашения (например, начать возвращать null в качестве значения ссылочного типа, если родительский метод такого не допускал), это может привести к сбоям в работе родительского класса, а значит, объекта наследника. Более того, существуют и некоторые обязательные ограничения.
Вспомним, что заголовок метода состоит из модификаторов, возвращаемого значения, сигнатуры и throws-выражения. Сигнатура (имя и набор аргументов) остается неизменной, если говорить о переопределении. Возвращаемое значение также не может меняться, иначе это приведет к появлению двух разных методов с одинаковыми сигнатурами.
Рассмотрим модификаторы доступа.
Объявление интерфейсов
Объявление интерфейсов очень похоже на упрощенное объявление классов.
Оно начинается с заголовка. Сначала указываются модификаторы. Интерфейс может быть объявлен как public и тогда он будет доступен для общего использования, либо модификатор доступа может не указываться, в этом случае интерфейс доступен только для типов своего пакета. Модификатор abstract для интерфейса не требуется, поскольку все интерфейсы являются абстрактными. Его можно указать, но делать этого не рекомендуется, чтобы не загромождать код.
Далее записывается ключевое слово interface и имя интерфейса.
После этого может следовать ключевое слово extends и список интерфейсов, от которых будет наследоваться объявляемый интерфейс. Родительских типов может быть много, главное, чтобы не было повторений и чтобы отношение наследования не образовывало циклической зависимости.
Наследование интерфейсов действительно очень гибкое. Так, если есть два интерфейса, A и B, причем B наследуется от A, то новый интерфейс C может наследоваться от них обоих. Впрочем, понятно, что указание наследования от A является избыточным, все элементы этого интерфейса и так будут получены по наследству через интерфейс B.
Затем в фигурных скобках записывается тело интерфейса.
public interface Drawble extends Colorable, Resizable { }
Тело интерфейса состоит из объявления элементов, то есть полей-констант и абстрактных методов. Все поля интерфейса должны быть public final static, так что эти модификаторы указывать необязательно и даже нежелательно, чтобы не загромождать код. Поскольку поля объявляются финальными, необходимо их сразу инициализировать.
public interface Directions { int RIGHT=1; int LEFT=2; int UP=3; int DOWN=4; }
Все методы интерфейса являются public abstract и эти модификаторы также необязательны.
public interface Moveable { void moveRight(); void moveLeft(); void moveUp(); void moveDown(); }
Как мы видим, описание интерфейса гораздо проще, чем объявление класса.
Описание исходного текста аплета Options
Назад Вперед
Помимо основного класса Options в нашем аплете создается еще три класса для панелей с именами FirstPanel, CardPanel и ControlPanel.
Класс FirstPanel соответствует самой верхней панели, в которой отображается строка текста First panel. Классы CardPanel и ControlPanel испльзуются для создания панелей со списками и управляющими кнопками, соответственно. Мы будем рассматривать эти классы по отдельности.
Полиморфизм
Ранее были рассмотрены правила объявления классов с учетом их наследования. В этой лекции было введено понятие переопределенного метода. Однако полиморфизм требует более глубокого изучения. При объявлении одноименных полей или методов с совпадающими сигнатурами происходит перекрытие элементов из родительского и наследующего класса. Рассмотрим, как функционируют классы и объекты в таких ситуациях.
Полиморфизм и объекты
В заключение рассмотрим несколько особенностей, вытекающих из свойств полиморфизма.
Во-первых, теперь можно точно сформулировать, что является элементами ссылочного типа. Ссылочный тип обладает следующими элементами:
непосредственно объявленными в его теле;объявленными в его родительском классе и реализуемых интерфейсах, кроме:
private-элементов;"скрытых" элементов (полей и статических методов, скрытых одноименными элементами);переопределенных (динамических) методов.
Во-вторых, продолжим рассматривать взаимосвязь типа переменной и типов ее возможных значений. К случаям, описанным в предыдущей лекции, добавляются еще два. Переменная типа абстрактный класс может ссылаться на объекты, порожденные неабстрактным наследником этого класса. Переменная типа интерфейс может ссылаться на объекты, порожденные от класса, реализующего данный интерфейс.
Сведем эти данные в таблицу.
Абстрактный класс |
null неабстрактный наследник | |
Интерфейс |
null классы, реализующие интерфейс, а именно: реализующие напрямую (заголовок содержит implements); наследуемые от реализующих классов;реализующие наследников этого интерфейса;смешанный случай - наследование от класса, реализующего наследника интерфейса |
Таким образом, Java предоставляет гибкую и мощную модель объектов, позволяющую проектировать самые сложные системы. Необходимо хорошо разбираться в ее основных свойствах и механизмах – наследование, статические элементы, абстрактные элементы, интерфейсы, полиморфизм, разграничения доступа и другие. Все они позволяют избегать дублирующего кода, облегчают развитие системы, добавление новых возможностей и изменение старых, помогают обеспечивать минимальную связность между частями системы, то есть повышают модульность. Также удачные технические решения можно многократно использовать в различных системах, сокращая и упрощая процесс их создания.
Для достижения таких важных целей требуется не только знание Java, но и владение объектно-ориентированным подходом, основными способами проектирования систем и проверки качества архитектурных решений. Платформа Java является основой и весьма удобным инструментом для применения всех этих технологий.
Поля
Начнем с полей, которые могут быть статическими или динамическими. Рассмотрим пример:
class Parent { int a=2; } class Child extends Parent { int a=3; }
Прежде всего, нужно сказать, что такое объявление корректно. Наследники могут объявлять поля с любыми именами, даже совпадающими с родительскими. Затем, необходимо понять, как два одноименных поля будут сосуществовать. Действительно, объекты класса Child будут содержать сразу две переменных, а поскольку они могут отличаться не только значением, но и типом (ведь это два независимых поля), именно компилятор будет определять, какое из значений использовать. Компилятор может опираться только на тип ссылки, с помощью которой происходит обращение к полю:
Child c = new Child(); System.out.println(c.a); Parent p = c; System.out.println(p.a);
Обе ссылки указывают на один и тот же объект, порожденный от класса Child, но одна из них имеет такой же тип, а другая – Parent. Отсюда следуют и результаты:
3 2
Объявление поля в классе-наследнике "скрыло" родительское поле. Данное объявление так и называется – "скрывающим" (hiding). Это особый случай перекрытия областей видимости, отличный от "затеняющего" (shadowing) и "заслоняющего" (obscuring) объявлений. Тем не менее, родительское поле продолжает существовать. К нему можно обратиться и явно:
class Child extends Parent { int a=3; // скрывающее объявление int b=((Parent)this).a; // более громоздкое объявление int c=super.a; // более простое }
Переменные b и c получат значение, хранящееся в родительском поле a. Хотя выражение с super более простое, оно не позволит обратиться на два уровня вверх по дереву наследования. А ведь вполне возможно, что в родительском классе это поле также было скрывающим и в родителе родителя хранится еще одно значение. К нему можно обратиться явным приведением, как это делается для b.
Рассмотрим следующий пример:
class Parent { int x=0; public void printX() { System.out.println(x); } } class Child extends Parent { int x=-1; }
Каков будет результат следующих строк?
new Child().printX();
Значение какого поля будет распечатано? Метод вызывается с помощью ссылки типа Child, но это не сыграет никакой роли. Вызывается метод, определенный в классе Parent, и компилятор, конечно, расценил обращение к полю x в этом методе именно как к полю класса Parent. Поэтому результатом будет 0.
Перейдем к статическим полям. На самом деле, для них проблем и конфликтов, связанных с полиморфизмом, не существует.
Рассмотрим пример:
class Parent { static int a=2; } class Child extends Parent { static int a=3; }
Каков будет результат следующих строк?
Child c = new Child(); System.out.println(c.a); Parent p = c; System.out.println(p.a);
Нужно вспомнить, как компилятор обрабатывает обращения к статическим полям через ссылочные значения. Неважно, на какой объект указывает ссылка. Более того, она может быть даже равна null. Все определяется типом ссылки.
Поэтому рассматриваемый пример эквивалентен:
System.out.println(Child.a) System.out.println(Parent.a)
А его результат сомнений уже не вызывает:
3 2
Можно привести следующее пояснение. Статическое поле принадлежит классу, а не объекту. В результате появление классов-наследников со скрывающими (hiding) объявлениями никак не сказывается на работе с исходным полем. Компилятор всегда может определить, через ссылку какого типа происходит обращение к нему.
Обратите внимание на следующий пример:
class Parent { static int a; }
class Child extends Parent { }
Каков будет результат следующих строк?
Child.a=10; Parent.a=5; System.out.println(Child.a);
В этом примере поле a не было скрыто и передалось по наследству классу Child. Однако результат показывает, что это все же одно поле:
5
Несмотря на то, что к полю класса идут обращения через разные классы, переменная всего одна.
Итак, наследники могут объявлять поля с именами, совпадающими с родительскими полями. Такие объявления называют скрывающими. При этом объекты будут содержать оба значения, а компилятор будет каждый раз определять, с каким из них надо работать.
Поля класса CardPanel
В полях pBgColor, pFgColor и pFont хранятся ссылки на панели страниц блокнота, которые мы разместим внутри панели класса CardPanel:
Panel pBgColor; Panel pFgColor; Panel pFont;
Кроме того, в поле pControlled хранится ссылка на верхнюю панель с текстовой строкой First Panel.
Panel pControlled;
Это поле будет проинициализировано конструктором класса CardPanel.
В следующих трех полях мы храним ссылки на списки класса Choice, предназначенные, соответственно, для выбора цвета текста, цвета фона и шрифта:
Choice chBgColor; Choice chFgColor; Choice chFont;
Три поля класса Label содержат ссылки на подписи к указанным выше спискам:
Label lbBgColor; Label lbFgColor; Label lbFont;
Поля класса ControlPanel
Следующие пять полей хранят ссылки на кнопки, управляющие страницами блокнота:
Button btNext; Button btPrev; Button btBgColor; Button btFgColor; Button btFont;
Поле pCard хранит ссылку на панель блокнота:
Panel pCard;
Эта ссылка инициализируется конструктором класса.
Применение интерфейсов
До сих пор интерфейсы рассматривались с технической точки зрения – как их объявлять, какие конфликты могут возникать, как их разрешать. Однако важно понимать, как применяются интерфейсы с концептуальной точки зрения.
Распространенное мнение, что интерфейс – это полностью абстрактный класс, в целом верно, но оно не отражает всех преимуществ, которые дают интерфейсы объектной модели. Как уже отмечалось, множественное наследование порождает ряд конфликтов, но отказ от него, хоть и делает язык проще, но не устраняет ситуации, в которых требуются подобные подходы.
Возьмем в качестве примера дерева наследования классификацию живых организмов. Известно, что растения и животные принадлежат к разным царствам. Основным различием между ними является то, что растения поглощают неорганические элементы, а животные питаются органическими веществами. Животные делятся на две большие группы – птицы и млекопитающие. Предположим, что на основе этой классификации построено дерево наследования, в каждом классе определены элементы с учетом наследования от родительских классов.
Рассмотрим такое свойство живого организма, как способность питаться насекомыми. Очевидно, что это свойство нельзя приписать всей группе птиц, или млекопитающих, а тем более растений. Но существуют представители каждой из названных групп, которые этим свойством обладают, – для растений это росянка, для птиц, например, ласточки, а для млекопитающих – муравьеды. Причем, очевидно, "реализовано" это свойство у каждого вида совсем по-разному.
Можно было бы объявить соответствующий метод (скажем, consumeInsect(Insect)) у каждого представителя независимо. Но если задача состоит в моделировании, например, зоопарка, то однотипную процедуру – кормление насекомыми – пришлось бы описывать для каждого вида отдельно, что существенно осложнило бы код, причем без какой-либо пользы.
Java предлагает другое решение. Объявляется интерфейс InsectConsumer:
public interface InsectConsumer { void consumeInsect(Insect i); }
Его реализуют все подходящие животные и растения:
// росянка расширяет класс растение public class Sundew extends Plant implements InsectConsumer { public void consumeInsect(Insect i) { ... } }
// ласточка расширяет класс птица public class Swallow extends Bird implements InsectConsumer { public void consumeInsect(Insect i) { ... } } // муравьед расширяет класс млекопитающее public class AntEater extends Mammal implements InsectConsumer { public void consumeInsect(Insect i) { ... } }
В результате в классе, моделирующем служащего зоопарка, можно объявить соответствующий метод:
// служащий, отвечающий за кормление, // расширяет класс служащий class FeedWorker extends Worker {
// с помощью этого метода можно накормить // и росянку, и ласточку, и муравьеда public void feedOnInsects(InsectConsumer consumer) { ... consumer.consumeInsect(insect); ... } }
В результате удалось свести работу с одним свойством трех разнородных классов в одно место, сделать код более универсальным. Обратите внимание, что при добавлении еще одного насекомоядного такая модель зоопарка не потребует никаких изменений, чтобы обслуживать новый вид, в отличие от первоначального громоздкого решения. Благодаря введению интерфейса удалось отделить классы, реализующие его (живые организмы) и использующие его (служащий зоопарка). После любых изменений этих классов при условии сохранения интерфейса их взаимодействие не нарушится.
Данный пример иллюстрирует, как интерфейсы предоставляют альтернативный, более строгий и гибкий подход вместо множественного наследования.
Работа с панелями
Назад Вперед
Панели, создаваемые на базе класса Panel, являются мощным средством организации диалогового интерфейса. Так как класс Panel произошел от класса Container, панель может содержать компоненты и другие панели. Для каждой панели можно определить режим размещения компонент, что позволяет создавать достаточно сложный пользовательский интерфейс.
В окне аплета вы можете создать несколько панелей, разделяющих его на части. В свою очередь, пространство, занимаемое панелями, также может быть разделено с использованием одного из описанных выше режимов размещения (рис. 1).
Рис. 1. Размещение нескольких панелей в окне аплета
Отдельные панели могут содержать в себе такие компоненты, как кнопки, переключатели, списки, текстовые поля и так далее.
Назад Вперед
Контакты
О компании
Новости
Вакансии
Правовые аспекты
Условия использования
Торговые марки
Copyright 1994-2005 Sun Microsystems, Inc.
printmenus();
Программные продуктыРабочие станции и тонкие клиенты
Серверы
Системы хранения данных
Посмотреть все
»Java 2 Standard Edition
Developer Tools
Top Downloads
New Downloads
Патчи и обновления
Посмотреть все
»Истории успеха
The Sun Grid
Партнерские программы
Посмотреть все
»Программы SunSpectrum
Консалтинг
Услуги инсталляции
Поддержка ПО
Посмотреть все
»Сертификация
Авторизованные учебные центры
Посмотреть все
»События
Lab Downloads
Посмотреть все
»Реализация интерфейса
Каждый класс может реализовывать любые доступные интерфейсы. При этом в классе должны быть реализованы все абстрактные методы, появившиеся при наследовании от интерфейсов или родительского класса, чтобы новый класс мог быть объявлен неабстрактным.
Если из разных источников наследуются методы с одинаковой сигнатурой, то достаточно один раз описать реализацию и она будет применяться для всех этих методов. Однако если у них различное возвращаемое значение, то возникает конфликт:
interface A { int getValue(); }
interface B { double getValue(); }
Если попытаться объявить класс, реализующий оба эти интерфейса, то возникнет ошибка компиляции. В классе оказывается два разных метода с одинаковой сигнатурой, что является неразрешимым конфликтом. Это единственное ограничение на набор интерфейсов, которые может реализовывать класс.
Подобный конфликт с полями-константами не столь критичен:
interface A { int value=3; } interface B { double value=5.4; } class C implements A, B { public static void main(String s[]) { C c = new C(); // System.out.println(c.value); - ошибка! System.out.println(((A)c).value); System.out.println(((B)c).value); } }
Как видно из примера, обращаться к такому полю через сам класс нельзя, компилятор не сможет понять, какое из двух полей нужно использовать. Но можно с помощью явного приведения сослаться на одно из них.
Итак, если имя интерфейса указано после implements в объявлении класса, то класс реализует этот интерфейс. Наследники данного класса также реализуют интерфейс, поскольку им достаются по наследству его элементы.
Если интерфейс A наследуется от интерфейса B, а класс реализует A, то считается, что интерфейс B также реализуется этим классом по той же причине – все элементы передаются по наследству в два этапа – сначала интерфейсу A, затем классу.
Наконец, если класс C1 наследуется от класса C2, класс C2 реализует интерфейс A1, а интерфейс A1 наследуется от интерфейса A2, то класс C1 также реализует интерфейс A2.
Все это позволяет утверждать, что переменные типа интерфейс также допустимы. Они могут иметь значение null, или ссылаться на объекты, порожденные от классов, реализующих этот интерфейс. Поскольку объекты порождаются только от классов, а все они наследуются от Object, это означает, что значения типа интерфейс обладают всеми элементами класса Object.
Рисование в окне панели
Назад Вперед
Как вы знаете, для того чтобы что-нибудь нарисовать, необходимо вначале получить контекст отображения. Методу paint передается контекст отображения, связанный с окном аплета. Если в окне имеются панели, то для рисования внутри них необходимо получить контекст отображения окон панелей.
Проще всего это сделать с помощью метода getGraphics, вызвав его для объекта класса Panel:
Graphics gpDraw; gpDraw = pDraw.getGraphics();
Здесь в переменную gpDraw мы записали ссылку на контекст отображения для панели pDraw.
Получив контекст отображения, можно приступить к рисованию. Вот, например, как можно нарисовать вокруг панели тонкую рамку:
Dimension dimAppWndDimension = pDraw.size(); gpDraw.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1);
В этом фрагменте кода мы вначале определили размеры панели, вызвав для нее метод size, а затем при помощи метода drawRect, вызванного для контекста отображения gpDraw, нарисовали рамку.
Для установки шрифта и рисования текста в окне панели вы также должны указывать ссылку на контекст отображения вашей панели:
gpDraw.setFont(new Font("Courier", Font.PLAIN, 12)); gpDraw.drawString( "Текст внутри окна панели", 10, 50);
Другой способ основан на создании собственного класса на базе класса Panel и переопределения в этом классе метода paint.
Назад Вперед
Контакты
О компании
Новости
Вакансии
Правовые аспекты
Условия использования
Торговые марки
Copyright 1994-2005 Sun Microsystems, Inc.
printmenus();
Программные продуктыРабочие станции и тонкие клиенты
Серверы
Системы хранения данных
Посмотреть все
»Java 2 Standard Edition
Developer Tools
Top Downloads
New Downloads
Патчи и обновления
Посмотреть все
»Истории успеха
The Sun Grid
Партнерские программы
Посмотреть все
»Программы SunSpectrum
Консалтинг
Услуги инсталляции
Поддержка ПО
Создание нового класса на базе класса Panel
Назад Вперед
Если ваш аплет создает много панелей, техника рисования в окнах этих панелей, описанная выше, может привести к усложнению исходного текста приложения. Так как рисование в окнах панелей выполняется в методе paint класса аплета, вам придется получать контекст отображения для каждой панели.
Намного проще создать несколько дочерних классов от класса Panel, переопределив в каждом из них метод paint. В этом случае для каждой панели вы можете создать свой метода paint, которому будет автоматически передаваться контекст отображения, связанный с окном соответствующей панели.
В аплете Options, который мы рассмотрим ниже, использована именно такая методика работы с панелями.
Назад Вперед
Контакты
О компании
Новости
Вакансии
Правовые аспекты
Условия использования
Торговые марки
Copyright 1994-2005 Sun Microsystems, Inc.
printmenus();
Программные продуктыРабочие станции и тонкие клиенты
Серверы
Системы хранения данных
Посмотреть все
»Java 2 Standard Edition
Developer Tools
Top Downloads
New Downloads
Патчи и обновления
Посмотреть все
»Истории успеха
The Sun Grid
Партнерские программы
Посмотреть все
»Программы SunSpectrum
Консалтинг
Услуги инсталляции
Поддержка ПО
Посмотреть все
»Сертификация
Авторизованные учебные центры
Посмотреть все
»События
Lab Downloads
Посмотреть все
»Создание панелей
Назад Вперед
Панель создается очень просто. Прежде всего необходимо выбрать для окна аплета схему размещения компонент, соответствующую требуему расположению панелей. Например, для создания в окне аплета двух панелей, разделяющих его по горизонтали, следует выбрать режим GridLayout:
setLayout(new GridLayout(2, 1));
Панели будут размещаться в ячейках таблицы, состоящей из одного столбца и двух строк.
Далее нужно создать объекты класса Panel:
Panel pTopPanel; pTopPanel = new Panel(); Panel pBottomPanel; pBottomPanel = new Panel();
Ссылка на панель, которая будет располагаться сверху, записывается в переменную pTopPanel, а на ту, что будет располагаться снизу - в переменную pBottomPanel.
Назад Вперед
Контакты
О компании
Новости
Вакансии
Правовые аспекты
Условия использования
Торговые марки
Copyright 1994-2005 Sun Microsystems, Inc.
printmenus();
Программные продуктыРабочие станции и тонкие клиенты
Серверы
Системы хранения данных
Посмотреть все
»Java 2 Standard Edition
Developer Tools
Top Downloads
New Downloads
Патчи и обновления
Посмотреть все
»Истории успеха
The Sun Grid
Партнерские программы
Посмотреть все
»Программы SunSpectrum
Консалтинг
Услуги инсталляции
Поддержка ПО
Посмотреть все
»Сертификация
Авторизованные учебные центры
Посмотреть все
»События
Lab Downloads
Посмотреть все
»Статические элементы
До этого момента под полями объекта мы всегда понимали значения, которые имеют смысл только в контексте некоторого экземпляра класса. Например:
class Human { private String name; }
Прежде, чем обратиться к полю name, необходимо получить ссылку на экземпляр класса Human, невозможно узнать имя вообще, оно всегда принадлежит какому-то конкретному человеку.
Но бывают данные и иного характера. Предположим, необходимо хранить количество всех людей (экземпляров класса Human, существующих в системе). Понятно, что общее число людей не является характеристикой какого-то одного человека, оно относится ко всему типу в целом. Отсюда появляется название "поле класса", в отличие от "поля объекта". Объявляются такие поля с помощью модификатора static:
class Human { public static int totalCount; }
Чтобы обратиться к такому полю, ссылка на объект не требуется, вполне достаточно имени класса:
Human.totalCount++; // рождение еще одного человека
Для удобства разрешено обращаться к статическим полям и через ссылки:
Human h = new Human(); h.totalCount=100;
Однако такое обращение конвертируется компилятором. Он использует тип ссылки, в данном случае переменная h объявлена как Human, поэтому последняя строка будет неявно преобразована в:
Human.totalCount=100;
В этом можно убедиться на следующем примере:
Human h = null; h.totalCount+=10;
Значение ссылки равно null, но это не имеет значения в силу описанной конвертации. Данный код успешно скомпилируется и корректно исполнится. Таким образом, в следующем примере
Human h1 = new Human(), h2 = new Human(); Human.totalCount=5; h1.totalCount++; System.out.println(h2.totalCount);
все обращения к переменной totalCount приводят к одному единственному полю, и результатом работы такой программы будет 6. Это поле будет существовать в единственном экземпляре независимо от того, сколько объектов было порождено от данного класса, и был ли вообще создан хоть один объект.
Аналогично объявляются статические методы.
class Human { private static int totalCount;
public static int getTotalCount() { return totalCount; } }
Для вызова статического метода ссылки на объект не требуется.
Human.getTotalCount();
Хотя для удобства обращения через ссылку разрешены, но принимается во внимание только тип ссылки:
Human h=null; h.getTotalCount(); // два эквивалентных Human.getTotalCount(); // обращения к одному // и тому же методу
Хотя приведенный пример технически корректен, все же использование ссылки на объект для обращения к статическим полям и методам не рекомендуется, поскольку это усложняет код.
Обращение к статическому полю является корректным независимо от того, были ли порождены объекты от этого класса и в каком количестве. Например, стартовый метод main() запускается до того, как программа создаст хотя бы один объект.
Кроме полей и методов, статическими могут быть инициализаторы. Они также называются инициализаторами класса, в отличие от инициализаторов объекта, рассматривавшихся ранее. Их код выполняется один раз во время загрузки класса в память виртуальной машины. Их запись начинается с модификатора static:
class Human { static { System.out.println("Class loaded"); } }
Если объявление статического поля совмещается с его инициализацией, то поле инициализируется также однократно при загрузке класса. На объявление и применение статических полей накладываются те же ограничения, что и для динамических,– нельзя использовать поле в инициализаторах других полей или в инициализаторах класса до того, как это поле объявлено:
class Test { static int a; static { a=5; // b=7; // Нельзя использовать до // объявления! } static int b=a; }
Это правило распространяется только на обращения к полям по простому имени. Если использовать составное имя, то обращаться к полю можно будет раньше (выше в тексте программы), чем оно будет объявлено:
class Test { static int b=Test.a; static int a=3; static { System.out.println("a="+a+", b="+b); } }
Если класс будет загружен в систему, на консоли появится текст:
a=3, b=0
Видно, что поле b при инициализации получило значение по умолчанию поля a, т.е. 0. Затем полю a было присвоено значение 3.
Статические поля также могут быть объявлены как final, это означает, что они должны быть проинициализированы строго один раз и затем уже больше не менять своего значения. Аналогично, статические методы могут быть объявлены как final, а это означает, что их нельзя перекрывать в классах-наследниках.
Для инициализации статических полей можно пользоваться статическими методами и нельзя обращаться к динамическим. Вводят специальные понятия – статический и динамический контексты. К статическому контексту относят статические методы, статические инициализаторы, инициализаторы статических полей. Все остальные части кода имеют динамический контекст.
При выполнении кода в динамическом контексте всегда есть объект, с которым идет работа в данный момент. Например, для динамического метода это объект, у которого он был вызван, и так далее.
Напротив, со статическим контекстом ассоциированных объектов нет. Например, как уже указывалось, стартовый метод main() вызывается в тот момент, когда ни один объект еще не создан. При обращении к статическому методу, например, MyClass.staticMethod(), также может не быть ни одного экземпляра MyClass. Обращаться к статическим методам класса Math можно, а создавать его экземпляры нельзя.
А раз нет ассоциированных объектов, то и пользоваться динамическими конструкциями нельзя. Можно только ссылаться на статические поля и вызывать статические методы. Либо обращаться к объектам через ссылки на них, полученные в результате вызова конструктора или в качестве аргумента метода и т.п.
class Test { public void process() { } public static void main(String s[]) { // process(); - ошибка! // у какого объекта его вызывать?
Test test = new Test(); test.process(); // так правильно } }
В этой лекции были рассмотрены
В этой лекции были рассмотрены особенности объектной модели Java. Это, во-первых, статические элементы, позволяющие использовать интерфейс класса без создания объектов. Нужно помнить, что, хотя для обращения к статическим элементам можно задействовать ссылочную переменную, на самом деле ее значение не используется, компилятор основывается только на ее типе.
Для правильной работы со статическими элементами вводятся понятия статического и динамического контекста.
Далее рассматривалось использование ключевых слов this и super. Выражение this предоставляет ссылку, указывающую на объект, в контексте которого оно встречается. Эта конструкция помогает избегать конфликтов имен, а также применяется в конструкторах.
Слово super позволяет задействовать свойства родительского класса, что необходимо для реализации переопределенных методов, а также в конструкторах.
Затем было введено понятие абстрактного метода и класса. Абстрактный метод не имеет тела, он лишь указывает, что метод с такой сигнатурой должен быть реализован в классе-наследнике. Поскольку он не имеет собственной реализации, классы с абстрактными методами также должны быть объявлены с модификатором abstract, который указывает, что от них нельзя порождать объекты. Основная цель абстрактных методов – описать в родительском классе как можно больше общих свойств наследников, пусть даже и в виде заголовков методов без реализации.
Следующее важное понятие – особый тип в Java, интерфейс. Его еще называют полностью абстрактным классом, так как все его методы обязательно абстрактные, а поля final static. Соответственно, на основе интерфейсов невозможно создавать объекты.
Интерфейсы являются альтернативой множественному наследованию. Классы не могут иметь более одного родителя, но они могут реализовывать сколько угодно интерфейсов. Таким образом, интерфейсы описывают общие свойства классов, не находящихся на одной ветви дерева наследования.
Наконец, важным свойством объектной модели является полиморфизм. Было подробно изучено поведение полей и методов, как статических, так и динамических, при переопределении. Что позволило перейти к вопросу соответствия типов переменной и ее значения.