Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология Образование Политология Производство Психология Стандартизация Технологии |
AdjustmentListener и AdjustmentEvent
Это событие генерируется компонентом ScrollBar. Слушатель имеет один метод adjustmentValueChanged, сигнализирующий об изменении состояния полосы прокрутки. WindowListener и WindowEvent Это событие сигнализирует об изменении состояния окна (класс Window и его наследники). Рассмотрим особо один из методов слушателя – windowClosing. Этот метод вызывается, когда пользователь предпринимает попытку закрыть окно, например, нажимая на соответствующую кнопку в заголовке окна. Мы видели из примеров ранее, что в Java окна при этом не закрываются. Дело в том, что AWT лишь посылает WindowEvent в ответ на такое действие, а инициировать закрытие окна должен программист: public class WindowClosingAdapter extends WindowAdapter { public void windowClosing(WindowEvent e) { ((Window)e.getSource()).dispose(); }}Объявленный адаптер в методе windowClosing получает ссылку на окно, от которого пришло событие. Обычно мы пользовались методом setVisible(false), чтобы сделать компонент невидимым. Но поскольку Window автоматически порождает окно операционной системы, существует специальный метод dispose, который освобождает все системные ресурсы, связанные с этим окном. Когда окно будет закрыто, у слушателя вызывается еще один метод – windowClosed. ComponentListener и ComponentEvent Это событие отражает изменение основных параметров компонента – положение, размер, свойство visible. ContainerListener и ContainerEvent Это событие позволяет отслеживать изменение списка содержащихся в этом контейнере компонент. С развитием Java в AWT появляются и другие события, например, позволяющие поддерживать колесико мыши. Однако все они работают по точно такой же схеме, а потому их можно легко освоить самостоятельно. Обработка событий с помощью внутренних классов Еще в лекции, посвященной объявлению классов, было указано, что в теле класса можно объявлять внутренние классы. До сих пор такая возможность не была востребована в наших примерах, однако обработка событий AWT – как раз удобный случай рассмотреть такие классы на примере анонимных классов. Предположим, в приложение добавляется кнопка, которой следует добавить слушателя. Зачастую бывает удобно описать логику действий в отдельном методе того же класса. Если вводить слушателя, как делалось раньше – в отдельном классе, то это сразу порождает ряд неудобств: появляется новый, малосодержательный класс, которому к тому же необходимо передать ссылку на исходный класс и так далее. Гораздо удобнее поступить следующим образом: Button b = new Button(); b.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { processButton(); }});Рассмотрим подробно, что происходит в этом примере. Сначала создается кнопка, у которой затем вызывается метод addActionListener. Обратим внимание на аргумент этого метода. Может сложится впечатление, что производится попытка создать экземпляр интерфейса ( new ActionListener() ), однако это невозможно. Дело меняет фигурная скобка, которая указывает, что порождается экземпляр нового класса, объявление которого последует за этой скобкой. Класс наследуется от Object и реализует интерфейс ActionListener. Ему необходимо реализовать метод actionPerformed, что и делается. Обратите внимание на еще одну важную деталь – в этом методе вызывается processButton. Это метод, который мы планировали разместить во внешнем классе. Таким образом, внутренний класс может напрямую обращаться к методам внешнего класса. Такой класс называется анонимным, он не имеет своего имени. Однако правило, согласно которому компилятор всегда создает.class -файл для каждого класса Java, действует и здесь. Если внешний класс называется Test, то после компиляции появится файл Test$1.class. Пример приложения, использующего модель событий В заключение темы, посвященной событиям, рассмотрим пример приложения, которое активно их использует. Попробуем написать примитивный графический редактор, который позволяет рисовать с помощью курсора – если перемещать его с нажатой кнопкой мыши, то будет появляться линия. Нажатие пробела очищает поле. import java.awt.*; import java.awt.event.*; public class DrawCanvas extends Canvas { private int lastX, lastY; private int ex, ey; private boolean clear=false; public DrawCanvas () { super(); addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { lastX = e.getX(); lastY = e.getY(); } }); addMouseMotionListener(new MouseMotionAdapter() { public void mouseDragged(MouseEvent e) { ex=e.getX(); ey=e.getY(); repaint(); } }); addKeyListener(new KeyAdapter() { public void keyTyped(KeyEvent e) { if (e.getKeyChar()==' ') { clear = true; repaint(); } } }); } public void update(Graphics g) { if (clear) { g.clearRect(0, 0, getWidth(), getHeight()); clear = false; } else { g.drawLine(lastX, lastY, ex, ey); lastX=ex; lastY=ey; } } public static void main(String s[]) { final Frame f = new Frame(" Draw" ); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { f.dispose(); } }); f.setSize(400, 300); final Canvas c = new DrawCanvas(); f.add(c); f.setVisible(true); }}Класс DrawCanvas и является тем полем, на котором можно рисовать. В его конструкторе инициализируются все необходимые слушатели. В случае прихода события инициализируется перерисовка (метод repaint ), логика которой описана в update. Запускаемый метод main инициализирует frame, не забывая про windowClosing. В результате можно что-нибудь нарисовать:
Апплеты Перейдем к рассмотрению апплетов ( applets ) – Java-приложений, которые исполняются в браузере как часть HTML-страницы. Это означает, что такие приложения всегда визуальные. Действительно, класс Applet является наследником AWT-компонента Panel. Сам класс находится в пакете java.applet. Жизненный цикл апплета Важным вопросом для понимания работы апплетов является их жизненный цикл. Он описывается четырьмя методами. Init Этот метод вызывается браузером при конструировании апплета. Зачастую все инициализирующие действия описываются здесь, а не в конструкторе. Это может быть, например, создание AWT-компонент, запуск потоков исполнения, установление сетевых соединений и т.д. Start Этот метод вызывается после инициализации апплета. Он нужен по следующей причине. Апплет может содержать какие-то динамические части, например, анимацию или бегущую строку. Если пользователь воспользуется какой-нибудь ссылкой и уйдет со страницы с апплетом, браузер не станет его уничтожать – ведь пользователь может вернуться (нажав в браузере кнопку Back ), и он будет ожидать, что апплет сохранит свое состояние. Значит, апплет может оказаться в неактивном состоянии, когда лучше приостановить динамические процессы для экономии системных ресурсов. Метод start сигнализирует о переходе в активное состояние. Stop Этот метод всегда вызывается после метода start и сигнализирует о переходе в пассивное состояние. Destroy По завершении работы апплет необходимо корректно удалить, чтобы он имел возможность освободить занимаемые ресурсы. Для этого браузер вызывает метод destroy. В остальном апплет является полноценным AWT-компонентом и в методе init может добавить другие компоненты для создания пользовательского интерфейса, или даже открыть новый фрейм. Единственное, но существенное ограничение – это условие безопасности. Ведь код апплета скачивается по сети, а значит, может содержать в себе опасные действия. Поэтому браузер запускает виртуальную машину с ограничениями – апплетам запрещено обращаться к файловой структуре, запрещено устанавливать сетевые соединения с кем-либо, кроме сервера, откуда они были загружены, все вновь открываемые окна помечаются предупреждением. Более того, пользователь может так настроить свой браузер, что вовсе запретит исполнение Java. Можно, напротив, позволить апплетам то же, что и локальным приложениям. Есть и еще одно ограничение – версия Java, поддерживаемая браузером. Как говорилось в первой лекции, самый популярный на данный момент браузер – MS Internet Explorer – остановился на поддержке лишь Java 1.1, и то не в полном объеме. В некоторых случаях можно воспользоваться дополнительным продуктом Sun – Java Plug-in, который позволяет установить на браузер JVM любой версии. Продолжим рассмотрение апплетов. HTML-тег Раз апплет является частью HTML -страницы, значит, необходимо каким-то образом указать, где именно он располагается. Для этого служит специальный тег < applet>. Синтаксис тега < APPLET> в настоящее время таков: < APPLET CODE = appletFile WIDTH = pixels HEIGHT = pixels [ARCHIVE = jarFiles] [CODEBASE = codebaseURL] [ALT = alternateText] [NAME = appletInstanceName] [ALIGN = alignment] [VSPACE = pixels] [HSPACE = pixels]> [HTML-текст, отображаемый при отсутствии поддержки Java]< /APPLET>
Следующие два задают ширину свободного пространства в пикселах сверху и снизу апплета ( VSPACE ), а также слева и справа от него ( HSPACE ). Приведем пример простейшего апплета: import java.applet.*; import java.awt.*; public class HelloApplet extends Applet { public void init() { add(new Label(" Hello" )); }}HTML-тег для него: < applet code=HelloApplet.class width=200 height =50> < /applet>Передача параметров Существует очень полезная возможность передавать из HTML параметры в апплет. Таким образом, можно настроить программу без необходимости менять ее исходный код. В HTML параметры указываются следующим образом: < applet code=HelloApplet.class width=200 height =50> < param name=" text" value=" Hello!!! " > < /applet>В апплете значение параметров считывается таким образом: import java.applet.*; import java.awt.*; public class HelloApplet extends Applet { public void init() { String text = getParameter(" text" ); add(new Label(text)); }}Теперь выводимый текст можно настраивать из HTML. Интерфейс AppletContext Доступ к этому интерфейсу из апплета предоставляется методом getAppletContext. С его помощью апплет может взаимодействовать со страницей, откуда он был загружен, и с браузером. Так, именно в этом интерфейсе определен метод getApplet, с помощью которого можно обратиться по имени к другому апплету, находящемуся на той же странице. Метод showStatus меняет текст поля статуса в окне браузера. Метод showDocument позволяет загрузить новую страницу в браузер. Менеджеры компоновки При размещении компонент в контейнере поначалу всегда кажется удобным задавать их размер и положение явно с помощью метода setBounds. Однако дальше становятся очевидны недостатки такого подхода. Например, удобно предоставить пользователю возможность изменять размер фрейма, а это означает необходимость перестраивать компоненты. Развитие приложения также может привести к добавлению или удалению компонента, после чего придется пересчитывать координаты оставшихся элементов. Есть проблемы и другого характера. Мы указывали, что JVM выбирает шрифты из имеющихся в системе. Поэтому под разными платформами они могут оказаться разного размера или наклона. В результате приложение, красиво смотрящееся на машине разработчика, может " поплыть" у клиента. Даже под одной платформой пользователь может сменить системные настройки, вследствие чего внешний вид приложения может измениться не в лучшую сторону. Все эти соображения наводят на мысль, что было бы полезно каким-то образом автоматизировать расположение компонентов. Именно для этой цели служат менеджеры компоновки. Их задача – вычислить и установить размер и местоположение компонентов в контейнере. Вообще говоря, они могут использовать следующий набор параметров для своих вычислений:
В AWT каждый контейнер обладает менеджером компоновки. Если он равен null, то используются явные параметры компонентов. Настоящие же классы менеджеров должны реализовывать интерфейс LayoutManager. Этот интерфейс принимает в качестве constraints строку ( String ). Со временем это было признано недостаточно гибким (фирмы стали разрабатывать и предлагать свои менеджеры, обладающие самой разной функциональностью). Поэтому был добавлен новый интерфейс – LayoutManager2, принимающий в качестве ограничителя constraints. Рассмотрим работу нескольких наиболее распространенных менеджеров компоновки. Но перед этим отметим общий для них всех факт. Дело в том, что не всегда вся область контейнера подходит для размещения в ней компонент. Например, фрейм имеет рамку и полосу заголовка. В результате его полезная площадь меньше. Поэтому все менеджеры компоновки начинают с обращения к методу getInsets класса Container. Этот метод возвращает значение типа Insets. Это класс, который имеет четыре открытых поля – top, right, bottom, left, значения которых описывают отступы со всех четырех сторон, которые необходимо сделать, чтобы получить область, доступную для расположения компонент. Класс FlowLayout Этот менеджер является стандартным для Panel. Он не меняет размер компонент, а только располагает их один за другим в линию, как буквы в строке. Когда заканчивается первая " строка", он переходит на следующую, и так далее, пока либо не закончится область контейнера, либо не будут расположены все компоненты. В качестве параметров конструктору можно передать значение выравнивания по горизонтали (определены константы LEFT, RIGHT, CENTER – значение по умолчанию), а также величину необходимых отступов между компонентами по вертикали ( vgap ) и горизонтали ( hgap ). Их значение по умолчанию – 5 пикселов. Рассмотрим пример: final Frame f = new Frame(" Flaw" ); f.setSize(400, 300); f.setLayout(new FlowLayout(FlowLayout.LEFT)); f.add(new Label(" Test" )); f.add(new Button(" Long string" )); f.add(new TextArea(2, 20)); f.add(new Button(" short" )); f.add(new TextArea(4, 20)); f.add(new Label(" Long-long text" )); f.setVisible(true);Если теперь менять размер этого фрейма, то можно видеть, как перераспределяются компоненты:
Класс BorderLayout Этот менеджер является стандартным для контейнера Window и его наследников Frame и Dialog. BorderLayout использует ограничитель. При добавлении компонента необходимо указать одну из 5 констант, определенных в этом классе: NORTH, SOUTH, EAST, WEST, CENTER (используется по умолчанию). Первыми располагаются северный и южный компонент. Их высота не изменяется, а ширина становится равной ширине контейнера. Северный компонент помещается на самый верх контейнера, южный – вниз. Затем располагаются восточный и западный компоненты. Их ширина не меняется, а высота становится равной высоте контейнера за вычетом места, которое заняли первые две компоненты. Наконец, все оставшееся место занимает центральная компонента. Рассмотрим пример: final Frame f = new Frame(" Border" ); f.setSize(200, 150); f.add(new Button(" North" ), BorderLayout.NORTH); f.add(new Button(" South" ), BorderLayout.SOUTH); f.add(new Button(" West" ), BorderLayout.WEST); f.add(new Button(" East" ), BorderLayout.EAST); f.add(new Button(" Center" ), BorderLayout.CENTER); f.setVisible(true);Вот как выглядит такой фрейм:
И в этом менеджере есть параметры hgap и vgap (по умолчанию их значение равно нулю). Класс GridLayout Этот менеджер поступает следующим образом – он разделяет весь контейнер на одинаковые прямоугольные сектора (отсюда и его название – решетка). Далее последовательно каждый компонент полностью занимает свой сектор (таким образом, они все становятся одинакового размера). В конструкторе указывается количество строк и столбцов для разбиения: final Frame f = new Frame(" Grid" ); f.setSize(200, 200); f.setLayout(new GridLayout(3, 3)); for (int i=0; i< 8; i++) { f.add(new Button(" -" +(i+1)+" -" )); }f.setVisible(true);Вот как выглядит такой фрейм:
И в этом менеджере есть параметры hgap и vgap (по умолчанию их значение равно нулю). Класс CardLayout Этот менеджер ведет себя подобно колоде карт. В один момент виден лишь один компонент, и он занимает всю область контейнера. Программист может управлять тем, какой именно компонент показывается пользователю. Заключение Библиотека AWT имеет множество классов и внутренних механизмов. Новые версии Java добавляют новые возможности и пересматривают старые. Тем не менее, основные концепции были подробно рассмотрены в этой лекции и на их основе можно построить полнофункциональный графический интерфейс пользователя ( GUI ). Стандартные компоненты AWT иерархически упорядочены в дерево наследования с классом Component в вершине. Важным его наследником является класс Container, который может хранить набор компонентов. Прямые наследники Component составляют набор управляющих элементов (" контролов", от англ. controls ), а наследники Container – набор контейнеров для группировки и расположения компонентов. Для упрощения размещения отдельных элементов пользовательского интерфейса применяются менеджеры компоновки ( Layout managers ). Особое место в AWT занимает процедура отрисовки компонентов, которая может инициироваться как операционной системой, так и программой. Специальные классы служат для задания таких атрибутов, как цвет, шрифт и т.д. Один из наследников Container – класс Window, который представляет собой самостоятельное окно в многооконной операционной системе. Два его наследника – Dialog и Frame. Для работы с файлами определен наследник Dialog – FileDialog. Наконец, излагаются принципы модели событий от пользователя, позволяющей обрабатывать все действия, которые производит клиент, работая с программой. 11 событий и соответствующих им интерфейсов предоставляют все необходимое для написания полноценной GUI -программы. Апплеты – небольшие программы, предназначенные для работы в браузерах как небольшие части HTML -страниц. Класс java.applet.Applet является наследником Panel, а потому обладает всеми свойствами AWT-компонент. Были представлены этапы жизненного цикла апплета, отличного от цикла обычного приложения, которое запускается методом main. Для размещения апплета на HTML -странице необходимо использовать специальный тег < applet>. Кроме этого, можно указывать специальные параметры, чтобы апплет настраивался без перекомпиляции кода. Лекция 12. Потоки выполнения. Синхронизация Введение До сих пор во всех рассматриваемых примерах подразумевалось, что в один момент времени исполняется лишь одно выражение или действие. Однако начиная с самых первых версий, виртуальные машины Java поддерживают многопоточность, т.е. поддержку нескольких потоков исполнения ( threads ) одновременно. В данной лекции сначала рассматриваются преимущества такого подхода, способы реализации и возможные недостатки. Затем описываются базовые классы Java, которые позволяют запускать потоки исполнения и управлять ими. При одновременном обращении нескольких потоков к одним и тем же данным может возникнуть ситуация, когда результат программы будет зависеть от случайных факторов, таких как временное чередование исполнения операций несколькими потоками. В такой ситуации становятся необходимыми механизмы синхронизации, обеспечивающие последовательный, или монопольный, доступ. В Java этой цели служит ключевое слово synchronized. Предварительно будет рассмотрен подход к организации хранения данных в виртуальной машине. В заключение рассматриваются методы wait(), notify(), notifyAll() класса Object. Многопоточная архитектура Не претендуя на полноту изложения, рассмотрим общее устройство многопоточной архитектуры, ее достоинства и недостатки. Реализацию многопоточной архитектуры проще всего представить себе для системы, в которой есть несколько центральных вычислительных процессоров. В этом случае для каждого из них можно выделить задачу, которую он будет выполнять. В результате несколько задач будут обслуживаться одновременно. Однако возникает вопрос – каким же тогда образом обеспечивается многопоточность в системах с одним центральным процессором, который, в принципе, выполняет лишь одно вычисление в один момент времени? В таких системах применяется процедура квантования времени ( time-slicing ). Время разделяется на небольшие интервалы. Перед началом каждого интервала принимается решение, какой именно поток выполнения будет отрабатываться на протяжении этого кванта времени. За счет частого переключения между задачами эмулируется многопоточная архитектура. На самом деле, как правило, и для многопроцессорных систем применяется процедура квантования времени. Дело в том, что даже в мощных серверах приложений процессоров не так много (редко бывает больше десяти), а потоков исполнения запускается, как правило, гораздо больше. Например, операционная система Windows без единого запущенного приложения инициализирует десятки, а то и сотни потоков. Квантование времени позволяет упростить управление выполнением задач на всех процессорах. Теперь перейдем к вопросу о преимуществах – зачем вообще может потребоваться более одного потока выполнения? Среди начинающих программистов бытует мнение, что многопоточные программы работают быстрее. Рассмотрев способ реализации многопоточности, можно утверждать, что такие программы работают на самом деле медленнее. Действительно, для переключения между задачами на каждом интервале требуется дополнительное время, а ведь они (переключения) происходят довольно часто. Если бы процессор, не отвлекаясь, выполнял задачи последовательно, одну за другой, он завершил бы их заметно быстрее. Стало быть, преимущества заключаются не в этом. Первый тип приложений, который выигрывает от поддержки многопоточности, предназначен для задач, где действительно требуется выполнять несколько действий одновременно. Например, будет вполне обоснованно ожидать, что сервер общего пользования станет обслуживать несколько клиентов одновременно. Можно легко представить себе пример из сферы обслуживания, когда имеется несколько потоков клиентов и желательно обслуживать их все одновременно. Другой пример – активные игры, или подобные приложения. Необходимо одновременно опрашивать клавиатуру и другие устройства ввода, чтобы реагировать на действия пользователя. В то же время необходимо рассчитывать и перерисовывать изменяющееся состояние игрового поля. Понятно, что в случае отсутствия поддержки многопоточности для реализации подобных приложений потребовалось бы реализовывать квантование времени вручную. Условно говоря, одну секунду проверять состояние клавиатуры, а следующую – пересчитывать и перерисовывать игровое поле. Если сравнить две реализации time-slicing, одну – на низком уровне, выполненную средствами, как правило, операционной системы, другую – выполняемую вручную, на языке высокого уровня, мало подходящего для таких задач, то становится понятным первое и, возможно, главное преимущество многопоточности. Она обеспечивает наиболее эффективную реализацию процедуры квантования времени, существенно облегчая и укорачивая процесс разработки приложения. Код переключения между задачами на Java выглядел бы куда более громоздко, чем независимое описание действий для каждого потока. Следующее преимущество проистекает из того, что компьютер состоит не только из одного или нескольких процессоров. Вычислительное устройство – лишь один из ресурсов, необходимых для выполнения задач. Всегда есть оперативная память, дисковая подсистема, сетевые подключения, периферия и т.д. Предположим, пользователю требуется распечатать большой документ и скачать большой файл из сети. Очевидно, что обе задачи требуют совсем незначительного участия процессора, а основные необходимые ресурсы, которые будут задействованы на пределе возможностей, у них разные – сетевое подключение и принтер. Значит, если выполнять задачи одновременно, то замедление от организации квантования времени будет незначительным, процессор легко справится с обслуживанием обеих задач. В то же время, если каждая задача по отдельности занимала, скажем, два часа, то вполне вероятно, что и при одновременном исполнении потребуется не более тех же двух часов, а сделано при этом будет гораздо больше. Если же задачи в основном загружают процессор (например, математические расчеты), то их одновременное исполнение займет в лучшем случае столько же времени, что и последовательное, а то и больше. Третье преимущество появляется из-за возможности более гибко управлять выполнением задач. Предположим, пользователь системы, не поддерживающей многопоточность, решил скачать большой файл из сети, или произвести сложное вычисление, что занимает, скажем, два часа. Запустив задачу на выполнение, он может внезапно обнаружить, что ему нужен не этот, а какой-нибудь другой файл (или вычисление с другими начальными параметрами). Однако если приложение занимается только работой с сетью (вычислениями) и не реагирует на действия пользователя (не обрабатываются данные с устройств ввода, таких как клавиатура или мышь), то он не сможет быстро исправить ошибку. Получается, что процессор выполняет большее количество вычислений, но при этом приносит гораздо меньше пользы. Процедура квантования времени поддерживает приоритеты (priority) задач. В Java приоритет представляется целым числом. Чем больше число, тем выше приоритет. Строгих правил работы с приоритетами нет, каждая реализация может вести себя по-разному на разных платформах. Однако есть общее правило – поток с более высоким приоритетом будет получать большее количество квантов времени на исполнение и таким образом сможет быстрее выполнять свои действия и реагировать на поступающие данные. В описанном примере представляется разумным запустить дополнительный поток, отвечающий за взаимодействие с пользователем. Ему можно поставить высокий приоритет, так как в случае бездействия пользователя этот поток практически не будет занимать ресурсы машины. В случае же активности пользователя необходимо как можно быстрее произвести необходимые действия, чтобы обеспечить максимальную эффективность работы пользователя. Рассмотрим здесь же еще одно свойство потоков. Раньше, когда рассматривались однопоточные приложения, завершение вычислений однозначно приводило к завершению выполнения программы. Теперь же приложение должно работать до тех пор, пока есть хоть один действующий поток исполнения. В то же время часто бывают нужны обслуживающие потоки, которые не имеют никакого смысла, если они остаются в системе одни. Например, автоматический сборщик мусора в Java запускается в виде фонового (низкоприоритетного) процесса. Его задача – отслеживать объекты, которые уже не используются другими потоками, и затем уничтожать их, освобождая оперативную память. Понятно, что работа одного потока garbage collector 'а не имеет никакого смысла. Такие обслуживающие потоки называют демонами ( daemon ), это свойство можно установить любому потоку. В итоге приложение выполняется до тех пор, пока есть хотя бы один поток не- демон. Рассмотрим, как потоки реализованы в Java. |
Последнее изменение этой страницы: 2016-03-26; Просмотров: 931; Нарушение авторского права страницы