для ограниченных свойств применяются методы
addVetoableChangeListener(VetoableChangeListener v) и removeVetoableChangeListener(VetoableChangeListener v). Здесь VetoableChangeListener – интерфейс с одним методом
void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException().
По аналогии со вспомогательным классом PropertyChangeSupport, который используется при реализации связанных свойств, для ограниченных свойств в пакете java.beans есть вспомогательный класс VetoableChangeSupport. В нем реализованы алгоритмы, необходимые для поддержки событий ограниченных свойств.
В качестве примера вспомним класс SomeBean, рассмотренный ранее. Его свойство someProperty() реализовано как связанное. Переделаем пример и реализуем это свойство как ограниченное.
/* пример # 26: bean-класс с ограниченным свойством: SomeBean.java */
import java.beans.*;
public class SomeBean {
private String someProperty = null;
private VetoableChangeSupport vcs;
public SomeBean(){
vcs = new VetoableChangeSupport(this);
}
public void addVetoableChangeListener
(VetoableChangeListener pcl){
vcs.addVetoableChangeListener(pcl);
}
public void removeVetoableChangeListener
(VetoableChangeListener pcl){
pcs.removePropertyChangeListener(pcl);
}
public String getSomeProperty(){
return someProperty;
}
public void setSomeProperty(String value) throws
PropertyVetoException{
vcs.fireVetoableChange(“someProperty”, someProperty, value);
someProperty = value;
}
}
Как видно, принципиально ничего не изменилось. Только вместо PropertyChangeSupport использован VetoableChangeSupport и в описании set -метода добавлено throws PropertyVetoException. Теперь someProperty является ограниченным свойством, и зарегистрировавшийся слушатель может запретить его изменение.
Рассмотренные возможности организации связи бина с другими компонентами не являются единственно возможными. Бин, как и любой класс, может быть источником событий и/или слушателем. И эти события могут быть не связаны с изменением свойств бина.
В таких случаях обычно используют существующие события типа ActionEvent, хотя можно построить и свои события.
Задания к главе 13
Вариант А
1. Создать апплет. Поместить на него текстовое поле JTextField, кнопку JButton и метку JLabel. В метке отображать все введенные символы, разделяя их пробелами.
2. Поместить в апплет две панели JPanel и кнопку. Первая панель содержит поле ввода и метку “Поле ввода”; вторая – поле вывода и метку “Поле вывода”. Для размещения в окне двух панелей и кнопки “Скопировать” использовать менеджер размещения BorderLayout.
3. Изменить задачу 2 так, чтобы при нажатии на кнопку “Скопировать” текст из поля ввода переносился в поле вывода, а поле ввода очищалось.
4. Задача 2 модифицируется так, что при копировании поля ввода нужно, кроме собственно копирования, организовать занесение строки из поля ввода во внутренний список. При решении использовать коллекцию,
в частности ArrayList.
5. К условию задачи 2 добавляется еще одна кнопка с надписью “Печать”. При нажатии на данную кнопку весь сохраненный список должен быть выведен в консоль. При решении использовать коллекцию, в частности TreeSet.
6. Написать программу для построения таблицы значений функции Использовать метку JLabel, содержащую текст “Функция: ”; панель, включающую три текстовых поля JTextField, содержащих значения параметра, шага (например, 0.1) и количества точек. Начальное значение x=0. С каждым текстовым полем связана метка, содержащая его название. В приложении должно находиться текстовое поле со скроллингом, содержащее полученную таблицу.
7. Создать форму с набором кнопок так, чтобы надпись на первой кнопке при ее нажатии передавалась на следующую, и т.д.
8. Создать форму с выпадающим списком так, чтобы при выборе элемента списка на экране появлялись GIF-изображения, двигающиеся в случайно выбранном направлении по апплету.
9. В апплете изобразить прямоугольник (окружность, эллипс, линию). Направление движения объекта по экрану изменяется на противоположное щелчком по клавише мыши. При этом каждый второй щелчок меняет цвет фона.
10. Создать фрейм с изображением окружности. Длина дуги окружности изменяется нажатием клавиш от 1 до 9.
11. Создать фрейм с кнопками. Кнопки “вверх”, “вниз”, “вправо”, “влево” двигают в соответствующем направлении линию. При достижении границ фрейма линия появляется с противоположной стороны.
12. Создать фрейм и разместить на нем окружность (одну или несколько). Объект должен “убегать” от указателя мыши. При приближении на некоторое расстояние объект появляется в другом месте фрейма.
13. Создать фрейм/апплет с изображением графического объекта. Объект на экране движется к указателю мыши, когда последний находится
в границах фрейма/апплета.
14. Изменить задачу 12 так, чтобы количество объектов зависело от размеров апплета и изменялось при “перетягивании” границы в любом направлении.
15. Промоделировать в апплете вращение спутника вокруг планеты по эллиптической орбите. Когда спутник скрывается за планетой, то он не виден.
16. Промоделировать в апплете аналоговые часы (со стрелками) с кнопками для увеличения/уменьшения времени на час/минуту.
Вариант B
Для заданий варианта В главы 4 создать графический интерфейс для занесения информации при инициализации объекта класса, для выполнения действий, предусмотренных заданием, и для отправки сообщений другому пользователю системы.
Тестовые задания к главе 13
Вопрос 13.1.
Какой менеджер размещения использует таблицу с ячейками равного размера?
1) FlowLayout;
2) GridLayout;
3) BorderLayout;
4) CardLayout.
Вопрос 13.2.
Дан код:
import java.awt.*;
public class Quest2 extends Frame{
Quest2(){
Button yes = new Button("YES");
Button no = new Button("NO");
add(yes);
add(no);
setSize(100, 100);
setVisible(true);
}
public static void main(String[] args){
Quest2 q = new Quest2();
} }
В результате будет выведено:
1) две кнопки, занимающие весь фрейм, YES – слева и NO – справа;
2) одна кнопка YES, занимающая целый фрейм;
3) одна кнопка NO, занимающая целый фрейм;
4) две кнопки наверху фрейма – YES и NO.
Вопрос 13.3.
Какое выравнивание устанавливается по умолчанию для менеджера размещений FlowLayout?
1) FlowLayout.RIGHT;
2) FlowLayout.LEFT;
3) FlowLayout.CENTER;
4) FlowLayout.LEADING;
5) указывается явно.
Вопрос 13.4.
Сколько кнопок будет размещено в приведенном ниже апплете?
import java.awt.*; public class Quest4 extends java.applet.Applet{ Button b = new Button("YES"); public void init(){ add(b); add(b); add(new Button("NO")); add(new Button("NO")); }}1) одна кнопка с YES и одна кнопка NO;
2) одна кнопка с YES и две кнопки NO;
3) две кнопки с YES и одна кнопка NO;
4) две кнопки с YES и две кнопки NO.
Вопрос 13.5.
Объект JСheckBox объявлен следующим образом:
JCheckBox ob = new JCheckBox();
Какая из следующих команд зарегистрирует его в блоке прослушивания событий?
1) ob.addItemListener();
2) ob.addItemListener(this);
3) addItemListener(this);
4) addItemListener();
5) ни одна из приведенных.»
ПОТОКИ ВЫПОЛНЕНИЯ
Класс Thread и интерфейс Runnable
К большинству современных распределенных приложений (Rich Client)
и Web-приложений (Thin Client) выдвигаются требования одновременной поддержки многих пользователей, каждому из которых выделяется отдельный поток, а также разделения и параллельной обработки информационных ресурсов. Потоки – средство, которое помогает организовать одновременное выполнение нескольких задач, каждую в независимом потоке. Потоки представляют собой классы, каждый из которых запускается и функционирует самостоятельно, автономно (или относительно автономно) от главного потока выполнения программы. Существуют два способа создания и запуска потока: расширение класса Thread или реализация интерфейса Runnable.
// пример # 1: расширение класса Thread: Talk.java
package chapt14;
public class Talk extends Thread {
public void run() {
for (int i = 0; i < 8; i++) {
System. out. println("Talking");
try {
// остановка на 400 миллисекунд
Thread. sleep (400);
} catch (InterruptedException e) {
System. err. print(e);
}
}
}
}
При реализации интерфейса Runnable необходимо определить его единственный абстрактный метод run(). Например:
/* пример # 2: реализация интерфейса Runnable: Walk.java: WalkTalk.java */
package chapt14;
public class Walk implements Runnable {
public void run() {
for (int i = 0; i < 8; i++) {
System. out. println("Walking");
try {
Thread. sleep (400);
} catch (InterruptedException e) {
System. err. println(e);
}
}
}
}
package chapt14;
public class WalkTalk {
public static void main(String[] args) {
// новые объекты потоков
Talk talk = new Talk();
Thread walk = new Thread(new Walk());
// запуск потоков
talk.start();
walk.start();
//Walk w = new Walk(); // просто объект, не поток
// w.run(); //выполнится метод, но поток не запустится!
}
}
Использование двух потоков для объектов классов Talk и Walk приводит
к выводу строк: Talking Walking. Порядок вывода, как правило, различен при нескольких запусках приложения.
Жизненный цикл потока
Новый |
Работоспособный |
Неработоспособный |
Пассивный |
При выполнении программы объект класса Thread может быть в одном из четырех основных состояний: “новый”, “работоспособный”, “неработоспособный” и “пассивный”. При создании потока он получает состояние “новый” (NEW) и не выполняется. Для перевода потока из состояния “новый” в состояние “работоспособный” (RUNNABLE) следует выполнить метод start(), который вызывает метод run() – основной метод потока.
Рис. 14.1. Состояния потока
Поток может находиться в одном из состояний, соответствующих элементам статически вложенного перечисления Thread.State:
NEW – поток создан, но еще не запущен;
RUNNABLE – поток выполняется;
BLOCKED – поток блокирован;
WAITING – поток ждет окончания работы другого потока;
TIMED_WAITING – поток некоторое время ждет окончания другого потока;
TERMINATED — поток завершен.
Получить значение состояния потока можно вызовом метода getState().
Поток переходит в состояние “неработоспособный” (WAITING) вызовом методов wait(), suspend() (deprecated-метод)или методов ввода/вывода, которые предполагают задержку. Для задержки потока на некоторое время (в миллисекундах) можно перевести его в режим ожидания (TIMED_WAITING) с помощью методов sleep(long millis) и wait(long timeout), при выполнении которого может генерироваться прерывание InterruptedException. Вернуть потоку работоспособность после вызова метода suspend() можно методом resume() (deprecated-метод), а после вызова метода wait() – методами notify() или notifyAll(). Поток переходит в “пассивное” состояние (TERMINATED), если вызваны методы interrupt(), stop() (deprecated-метод)или метод run() завершил выполнение. После этого, чтобы запустить поток еще раз, необходимо создать новый объект потока. Метод interrupt() успешно завершает поток, если он находится в состоянии “работоспособность”. Если же поток неработоспособен, то метод генерирует исключительные ситуации разного типа в зависимости от способа остановки потока.
Интерфейс Runnable не имеет метода start(), а только единственный метод run(). Поэтому для запуска такого потока, как Walk, следует создать объект класса Thread и передать объект Walk его конструктору. Однако при прямом вызове метода run() поток не запустится, выполнится только тело самого метода.
Методы suspend(), resume() и stop() являются deprecated-методами и запрещены к использованию, так как они не являются в полной мере “потоко-
безопасными”.
Управление приоритетами и группы потоков
Потоку можно назначить приоритет от 1 (константа MIN_PRIORITY) до 10 (MAX_PRIORITY) с помощью метода setPriority(int prior). Получить значение приоритета можно с помощью метода getPriority().
// пример # 3: демонстрация приоритетов: PriorityRunner.java: PriorThread.java
package chapt14;
public class PriorThread extends Thread {
public PriorThread(String name){
super (name);
}
public void run(){
for (int i = 0; i < 71; i++){
System. out. println(getName() + " " + i);
try {
sleep (1); //попробовать sleep(0);
} catch (InterruptedException e) {
System. err. print("Error" + e);
}
}
}
}
package chapt14;
public class PriorityRunner {
public static void main(String[] args) {
PriorThread min = new PriorThread("Min"); //1
PriorThread max = new PriorThread("Max"); //10
PriorThread norm = new PriorThread("Norm"); //5
min.setPriority(Thread.MIN_PRIORITY);
max.setPriority(Thread.MAX_PRIORITY);
norm.setPriority(Thread.NORM_PRIORITY);
min.start();
norm.start();
max.start();
}
}
Поток с более высоким приоритетом в данном случае, как правило, монополизирует вывод на консоль.
Потоки объединяются в группы потоков. После создания потока нельзя изменить его принадлежность к группе.
ThreadGroup tg = new ThreadGroup("Группа потоков 1");
Thread t0 = new Thread(tg, "поток 0");
Все потоки, объединенные группой, имеют одинаковый приоритет. Чтобы определить, к какой группе относится поток, следует вызвать метод
getThreadGroup(). Если поток до включения в группу имел приоритет выше приоритета группы потоков, то после включения значение его приритета станет равным приоритету группы. Поток же со значением приоритета более низким, чем приоритет группы после включения в оную, значения своего приоритета не изменит.
Управление потоками
Приостановить (задержать) выполнение потока можно с помощью метода sleep( время задержки ) класса Thread. Менее надежный альтернативный способ состоит в вызове метода yield(), который может сделать некоторую паузу и позволяет другим потокам начать выполнение своей задачи. Метод join() блокирует работу потока, в котором он вызван, до тех пор, пока не будет закончено выполнение вызывающего метод потока.
// пример # 4: задержка потока: JoinRunner.java
package chapt14;
class Th extends Thread {
public Th(String str) {
super ();
setName(str);
}
public void run() {
String nameT = getName();
System. out. println("Старт потока " + nameT);
if ("First".equals(nameT)) {
try {
sleep (5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System. out. println("завершение потока "
+ nameT);
} else if ("Second".equals(nameT)) {
try {
sleep (1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System. out. println("завершение потока "
+ nameT);
}
}
}
public class JoinRunner {
public static void main(String[] args) {
Th tr1 = new Th("First");
Th tr2 = new Th("Second");
tr1.start();
tr2.start();
try {
tr1.join();
System. out. println("завершение main");
} catch (InterruptedException e){
e.printStackTrace();
}
/* join() не дает работать потоку main до окончания выполнения потока tr1 */
}
}
Возможно, будет выведено:
Старт потока First
Старт потока Second