Преобразование XML в HTML на сервере
Java Q&A
Нужно, чтобы преобразование XML документа с использованием XSL происходило на сервере и чтобы клиент получал HTML документ вместо комбинации XML документа и описания стилей в формате XSL. Как лучше подойти к решению этой задачи?
Есть много способов преобразования из формата XML в HTML. Оптимально было бы оставить эту задачу клиенту, так как выполнение ее на сервере требует слишком много ресурсов. Заставляя клиента выполнять необходимые преобразования, можно снять часть нагрузки с сервера.
К сожалению, нельзя рассчитывать на клиента - не все броузеры поддерживают преобразование XML в HTML, и понадобится достаточно много времени, прежде чем все пользователи обновят свои броузеры. Приложение на сервере может определить тип броузера и, в случае если он поддерживает XSL преобразование, передавать клиенту XML и XSL. Но это не самый удачный подход, возникают значительные трудности при поддержке.
Как выполнить преобразование на сервере? Возможны два подхода: использование сервлетов и JSP. Для выполнения преобразования на основе XSL понадобится XSL парсер. К счастью, есть отличные свободно распространяемые парсеры. (Важно еще и то, что можно получить бесплатную поддержку, например, задавая вопросы в соответствующие списки рассылки. - Смирнов).(см. ).
Обзаведясь парсером, легко можно реализовать преобразование из XML в HTML в сервлете или на JSP странице. Механизм преобразования для этих случаев во многом одинаков. Нужно передать XML и XSL файлы парсеру, а полученный HTML документ отправить пользователю в качестве ответа на запрос.
Если вы не знакомы с сервлетами и JSP, рекомендую вам обратить внимание на проект Tomcat - "эталонную" реализацию для этих технологий. Дистрибутив содержит документацию и примеры.(см. )
В зависимости от сложности XML документа вы можете решить полностью отказаться от применения XSL преобразования. Рекомендую посмотреть статью , в ней обсуждаются вопросы преобразования XML в HTML с использованием JSP. Представлен другой подход к решению описанной проблемы без использования XSL. Так же в статье приводится обширный список ссылок.
Relevant articles
"Using XML and JSP Together," Alex Chaffee (JavaWorld March 31, 2000) furnishes a good all-around discussion of XML-to-HTML conversion:
"Java & XML: 1 + 1 > 2," Anthony B. Coates (Sydney Java Users' Group, March 31, 1999):
XSL parsers, XSL-aware servlets, and servlet engines
LotusXSL:
Resin:
Cocoon:
Tomcat:
Xalan:
XSL:P:
Reprinted with permission from the March 2000 edition of JavaWorld magazine.
Copyright © ITworld.com, Inc., an IDG Communications company.
View the original article at: http://www.javaworld.com/javaworld/javaqa/ 2000-04/03-qa-0428-xml.html
Можно создать многострочную кнопку!
Перевод на русский © , 2000
Java Q&A
Предположим, я хочу создать кнопку с меткой, как показано ниже:
"Ваше
Имя"
"Имя" должно быть во второй строке.
Я пытался написать:
new JButton("Ваше \\n Имя")
К сожалению, у меня ничего не получилось. У вас есть какие-нибудь советы?
Короткий ответ: вы не сможете это сделать без значительных усилий. JButton не содержит внутренних возможностей для переноса длинных строк или начала новой строки, если текст содержит \n.
Все компоненты JFC отображаются с помощью соответствующего ComponentUI. Вместо того, чтобы помещать весь код отрисовки в метод paint JButton, JButton передает фунции отображения в ButtonUI (расширение ComponentUI). ComponentUI позволяет нам менять look and feel для Java Swing GUI на лету просто меняя рендереры ComponentUI в нашем JComponents.
Одна из реализаций ButtonUI, которую может использовать JButton -- это BasicButtonUI. Если вы достаточно храбры для того, чтобы заглянуть в код BasicButtonUI, вы обнаружите метод:
protected void paintText(Graphics g, JComponent c, Rectangle textRect, String text)
paintText() выполняет всю грязную работу по отрисовке строки. Если мы копнем немного глубже, то обнаружим, что этот метод передает само отображение в метод drawString() в BasicGraphicsUtils: public static void drawString(Graphics g,String text,int underlinedChar,int x,int y)
drawString() выполняет небольшую обработку и далее просто вызывает метод drawString() из класса Graphics. Нам пришлось повозиться, но теперь достаточно очевидно, почему JButton не будет делать перенос строки.
Так что, для того чтобы сделать то, что вы хотите у вас есть несколько вариантов:
Вы можете создать свою собственную JButton, расширив JButton и перекрыв метод paint. В этом методе вы разобъете строку на основе \n, сделаете несколько вычислений размеров, отрисуете кнопку и затем выведете разбитый текст. Однако, если вы пойдете этим путем, ваша кнопка потеряет возможность подключать look and feel. Также вы можете утратить некоторые эффекты отображения в Swing. Вы можете создать новый ButtonUI, который будет правильно форматировать ваш текст. Вы можете использовать комбинацию из JLabels и JButton для получения того, что вам надо, вместо своей собственной кнопки. Попробуйте следующее:
JButton b = new JButton();
b.setLayout(new BorderLayout()); JLabel label1 = new JLabel("Ваше"); JLabel label2 = new JLabel("Имя");
b.add(BorderLayout.NORTH,label1); b.add(BorderLayout.SOUTH,label2);
Если вы обнаружите, что вам приходится часто использовать многострочную кнопку, вы можете вложить вышеприведенное решение в новый класс, скажем MultiLineButton. Этот класс будет расширять JButton, содержать две копии JLabel и методы void setLabel1Text(String text) и void setLabel2Text(String text).
Swing могут отображать HTML!
Похоже, что мой превоначальный ответ о многострочных кнопках был неполным. Когда я писал свой ответ, я использовал Java 1.1.7 и Swing 1.0.3, для которых ответ остается правильным. Многие разработчики (включая меня) по-прежнему используют эти старые версии. Однако новые версии Swing способны отображать HTML.
Вот два совета, написанные двумя активными читателями JavaWorld:
Довольно много компонентов в Swing могут отрисовывать свое содержимое используя HTML. В результате, вы можете создать многострочную кнопку с помощью следующего кода:
JButton b = new JButton("<html>Your<br>Name");
-- Sebastian Fernandez
И
Для решения проблемы с многострочной кнопкой просто напишите:
JButton myButton = new JButton ("<html>Hello<p>World</html>");
Заметьте, что не все компоненты в Swing подерживают HTML.
-- Tarek Hammoud
Массивы массивов
Перевод на русский © , 2000
Java Q&A
Когда и как следует использовать массивы массивов?
В Java все объекты обрабатываются с помощью ссылок. Объект может иметь несколько ссылок и ссылка может указывать на разные объекты в разные моменты времени. В этом смысле ссылки напоминают указатели C/C++ (правда, они не поддерживают операции с указателями, которые заменены в Java семантикой массивов). Давайте посмотрим на пример:
String s = new String( "Привет, мир" ); // s является ссылкой типа String, указывающей // на объект String со значением "Привет, мир" .
s= new String( "Guten Tag, Welt" ); // Та же ссылка типа String теперь указывает // на другой объект String; то есть, // одна и та же ссылка указывала на два // разных объекта (последовательно). // (Заметьте, что теперь у нас есть объект String // со значением "Привет, мир" на который нет ссылок; // теперь он подлежит удалению // сборщиком мусора)
String t; // t это ссылка типа String со значением null // (не указывает ни на один объект). // Если вы попробуете использовать t в этот момент, т.е. // напишете int len = t.length; вы получите // NullPointerException (лучше было бы сказать // NullReferenceException).
t = s; // Теперь ссылка типа String t указывает на тот же // объект, что и String ссылка s, // то есть объект со значением "Guten Tag, Welt". // Таким образом, мы получили две ссылки // на один объект (одновременно).
Массивы в Java это своеобразные объекты, хранят ли они примитивы (int, char, boolean, и т.д) или другие объекты. Это означает, что на массивы ссылаются, как на любые другие объекты с добавлением [] семантики агрегирование/разыменования. Например:
String [] sa; // sa это ссылка null // попытка доступа к sa.length приведет к NullPointerException.
sa = new String [2]; // sa уже не null, а указывает на определенный объект- // массив из двух ссылок null String. // sa.length теперь равна 2 // (sa[0] и sa[1] это две ссылки null String).
sa[0] = "Hello, World"; sa[1] = "Guten Tag, Welt"; // теперь sa указывает на массив из двух непустых ссылок типа String.
sa = new String[1]; // sa.length equals 1 // Та же ссылка sa тперь указывает на другой // (и более короткий) массив. // sa[0] это null ссылка типа String. // Попытка обратится к sa[1] вызовет // ArrayIndexOutOfBoundsException.
sa[0] = "Hello, World"; // sa[0] теперь непуста.
Вам также необходимо учитывать, что
String [] [] saa; saa [0] [0] = "Help";
приведет к NullPointerException, так как saa это пустая ссылка -- saa не ссылается ни на один объект. Для того, чтобы присвоить значение первому элементу первого массива saa должна ссылаться на массив с ненулевой длиной и saa[0] должна ссылаться на непустой массив строк также с ненулевой длиной. Теперь мы можем написать:
String [] [] saa; // saa это null ссылка на массив массивов, содержащих String // Попытка вызвать saa.length сразу же приведет к NullPointerException, // так же как и вызов saa[0].
saa = new String [1][]; // saa теперь ссылается на массив, содержащий 1 null ссылку на String[]. // saa.length равна 1. // saa[0] пуст.
saa[0] = new String[2]; // saa теперь ссылается на массив, содержащий одну непустую ссылку // на String[] с длиной 2. // saa.length по-прежнему равна 1. // saa[0].length равна 2 (но saa[0][0] и // saa[0][1] обе null).
saa[0][0] = "Hello, World"; saa[0][1] = "Guten Tag, Welt"; // Теперь saa[0][0] и saa[0][1] обе непусты.
Заметье, что вы не можете ссылаться на saa[0][0] пока saa[0] непуст, и вы не можете сделать saa[0] непустым пока вы не сделаете saa непустым также. В принципе вам прийдется создавать сво массив массивов в возрастающем порядке.
Вот упрощенный способ для инициализации ссылок в массивах:
String [][] saa = { { { "Hello, World }, { "Guten Tag, Welt"} } }; // создаем объект String[][], как и ранее // и присваиваем ему saa. // Пробел подчеркивает, что созданный объект // это массив из одного String[], // содержащий две строки.
Используя такой прием, перепишем наш пример:
String [][] saa = { { { "Help" } } };
Однако saa теперь указывает на последовательный массив строк. Заметьте, что приведенный синтаксис работает только при инициализации ссылки на массив (инициализация это специальный тип присваивания во время объявления). Более общий способ создания нового массива и присваивания ему ссылки на новый или существующий массив выглядит так (в этом случае для уже существующей ссылки):
saa = new String [][] { // заметьте пустые [][] -- компилятор сам // установит размер (пустые [][] должны быть обязательно). { { "Hello" }, { "World" } } // это saa[0] , // заметьте запятую, разделяющую // saa[0] и saa[1] { { "Guten Tag" }, { "Welt"} } // это is saa[1] }; // теперь saa.length = 2, и длина saa[0] и saa[1] также 2
Firewall tunneling
Перевод на русский © , 2000
Java Q&A
Как мне соединиться с моим Java сервером через HTTP когда клиент находится за proxy/firewall? Я пробовал такое с апплетом, но он выдает в числе прочих исключение "host unreachable".
Вы не можете соединится, потому что proxy/firewall клиента предотвращает большинство соединений через сокеты. Большинство файрволов перекрывают любой обмен информацией, кроме HTTP. Это делается для того, чтобы защитить внутренюю инфраструктуру; пользователи по-прежнему могут использовать Веб, но не могут соединится с другими сетевыми приложениями.
Однако, есть способы для соединения ваших Java приложений и серверов через HTTP. Иногда такой прием называют firewall tunneling. Для того, чтобы облегчить себе жизнь, напишите сервлеты для сервера и оберните все клиентские сообщения в HTTP запросы.
На стороне клиента ваш код должен использовать URLConnection для отправки данных на сервер:
//соединится URL url = new URL("http://www.myserver.com/ servlets/myservlet");
URLConnection conn = url.openConnection(); conn.setDoOutput(true); conn.setUseCaches(false); conn.setRequestProperty("Content-Type", "application/octet-stream");
//Открываем output stream и посылаем данные. OutputStream out = conn.getOutputStream(); //В данном случае ничего не выдается в поток, но вы можете это сделать.
out.flush(); out.close();
//Открываем input stream и загружаем данные. InputStream in = conn.getInputStream();
//Здесь вы будете загружать свои данные.
in.close()
На стороне сервера, когда клиент выдает openConnection(), вызывается метод сервлета doPost(). В этом методе вы можете читать данные и отсылать их клиенту.
Семафоры
Перевод на русский © , 2000
Java Q&A
Java использует ключевое слово synchronized и методы wait() и notify() класса java.lang.Object для синхронизации. Существуют ли другие способы, например семафоры, в Java?
Единственные механизмы синхронизации, встроенные в Java-- это мониторы (monitors) и наборы задержек (wait sets), о которых большинство программистов даже не слышало, так как они скрыты в synchronized, wait() и notify(). К счастью, Java позволяет вам реализовать все знакомые схемы синхронизации на основе мониторов и наборов задержек. В конце этой заметки мы узнаем, как написать наш собственный класс Semaphore.
Однако перед этим давайте посмотрим немного на теоретические основы. Каждый многопотоковый (multithreaded) язык программирования должен иметь механизм для синхронизации нитей. Возьмем, например, программу с нитью, которая создает объекты, и нитью, которая использует их, как в этом примере кода:
Object data = null;
public void push(Object d) { data = d; }
public Object pop() { Object d = data; data = null; return d; }
class Producer implements Runnable { public void run() { while (true) { Object o = createNewObject(); push(o); } } }
class Consumer implements Runnable { public void run() { while (true) { Object o = pop(); doSomethingWith(o); } } }
public static void main(String args[]) { (new Thread(new Producer())).start(); (new Thread(new Consumer())).start(); }
Проблема с этими примитивными классами Producer и Consumer заключается в том, что они не содержат никаких способов для взаимодействия друг с другом. Если одна нить работает быстрее, чем другая, она должна периодически ждать, пока медленная нить сделает свою работу. Мы можем изменить методы push() и pop() так, чтобы они работали поочередно и не обгоняли друг-друга.
public void push(Object d) { while (data != null) { /* Пустой цикл задержки. */ } data = d; }
public Object pop() { while (data == null) { /* Пусто. */ } Object d = data; data = null; /* Выход из цикла для push().*/ return d; }
Когда одна нить выполняет push(), а другая pop(), они взаимодействуют, используя значение поля data. push() ожидает окончания работы pop(), непрерывно проверяя поле data. Когда data становится null, push() может присвоить ему новый объект. pop() использует тот же прием, сидя в цикле и ожидая, пока data изменится. Как только data становится непустым, pop() выходит из цикла и устанавливает его опять в null.
Этот прием называется циклом задержки и содержит массу недостатков. Один из самых серьезных это то, что он может использовать все свободные ресурсы процессора. Если нить consume() имеет одинаковый или более высокий приоритет, чем produce(), цикл задержки в consume() может не дать produce() работать!
Java обходит необходимость в циклах задержки используя мониторы и наборы задержек. Монитор- это структура данных, которая может содержать одну-единственную нить. Если другая нить пытается занять монитор, содержащий нить, она будет заблокирована, пока монитор не освободится. У каждого объекта есть монитор: нить занимает его, вызывая метод synchronized для этого объекта, или входя в участок кода, который помечен как synchronized. Каждый объект Java содержит набор задержек -- набор нитей которые находятся в спящем режиме, пока другая нить не активирует одну из них. Нить запускает набор задержек объекта, вызывая его метод wait(). Она может покинуть набор задержек, когда другая нить вызывает метод notify() этого объекта.
Вот реализация push() и pop(), использующая мониторы и наборы задержек:
Object full = new Object(); Object empty = new Object();
public void push(Object d) { synchronized(full) { if (data != null) full.wait(); }
data = d;
synchronized(empty) { if (data != null) empty.notify(); } }
public Object pop() { synchronized(empty) { if (data == null) empty.wait(); }
Object o = data; data = null;
synchronized(full) { if (data == null) full.notify(); }
return o; }
Если data непуст, push() ждет, пока pop() сообщит ему, что data более не заполнен. Затем push() присваивает свой параметр data и сообщает pop(), что data заполнен. Такое решение гораздо менее нагружает процессор, чем цикл задержки. Оно также легко расширяется для поддержки нескольких производителей и потребителей объектов, вместо одного на каждый: достаточно только объявить push() и pop() как synchronized!
Мониторы Java и наборы задержек достаточно надежны для решения большинства проблем синхронизации и достаточно безопасны для того, чтобы большинство программистов не испытывали проблем. А для тех, кто любит рискованные приемы, мы приводим обещанную реализацию класса Semaphore:
/** * Semaphore- * это простая реализация хорошо известного * примитива синхронизации. * Его счетчик может быть установлен в любое * неотрицательное значение или 0 по-умолчанию. */ package com.randomwalk.library.sync;
public class Semaphore { private int counter;
public Semaphore() { this(0); }
public Semaphore(int i) { if (i < 0) throw new IllegalArgumentException(i + " < 0"); counter = i; }
/** * Увеличивает внутренний счетчик, * возможно активирует нить * wait()ing in acquire(). */ public synchronized void release() { if (counter == 0) { this.notify(); } counter++; }
/** * Уменьшает счетчик или блокирует, * если тот равен 0 * * @exception InterruptedException * передается из this.wait(). */ public synchronized void acquire() throws InterruptedException { while (counter == 0) { this.wait(); } counter--; } }
Небылицы?
Перевод на русский © , 2000
Java Q&A
Я видел веб-сайт, на котором курсор мыши превратился в рыбу с 5-ю слоями. Это вдохновило меня на то, чтобы превратить курсор на моей домашней страничке в музыкальную ноту с использованием 3-х слоев. Как мне это сделать?
В Java есть возможность создания курсоров мыши из любого изображения. Для этого нужно встроить Java апплет с измененным курсором в ваш HTML.
Создание курсора выполняется методом в java.awt.Toolkit из API:
public Cursor createCustomCursor (Image cursor, Point hotSpot, String name) throws IndexOutOfBoundsException
Toolkit-- это абстрактный класс, поэтому сначала нужно получить нативную реализацию:
Toolkit tk = panel.getToolkit();
Потом создать курсор:
Cursor cursor = tk.createCustomCursor (img, hotSpot, name);
И установить курсор для панели:
panel.setCursor(cursor);
(Полный код апплета показан ниже.)
К сожалению, создание курсоров возможно только в JDK 1.2 и выше. Это означает, что большинство веб-броузеров не будет их поддерживать, так как они используют только JDK 1.1. Один из выходов -- это использование Java Plug-in для JDK 1.2, который работает, как и любой другой plug-in броузера. Тут вы можете найти дополнительную информацию:
Ну а теперь, как и обещано, полный код апплета:
import java.awt.*; import java.applet.*;
public class CursorApplet extends Applet {
public void init() {
//загрузить изображение через Media Tracker MediaTracker tracker = new MediaTracker(this); Image cursor = getImage (getCodeBase(), "music_note.gif"); tracker.addImage(cursor, 0);
try { tracker.waitForID(0); } catch (InterruptedException ie) { ie.printStackTrace(); }
Cursor cr = null; //получить toolkit Toolkit tk = getToolkit();
try { //это x,y координаты изображения //которые действительно "щелкают" Point hotSpot = new Point(1, 1); //создаем наш курсор cr = tk.createCustomCursor( cursor, hotSpot, "music_note"); } catch (IndexOutOfBoundsException e) { e.printStackTrace(); } //устанавливаем курсор для апплета setCursor(cr); } }
Help tips в Swing
Перевод на русский © , 2000
Java Q&A
Возможно ли сделать поддержку help tips для меню в Swing?
В компьютерном мире достаточно распространены приложения, показывающие краткую справку (help tip) в строке статуса, когда пользователь выбирает один из элементов меню. Я был удивлен тем, что набор компонентов меню в Swing не поддерживает такую возможность. Есть ли способ создать такую функциональность, не создавая подкласс одного из огромных классов Swing? Мне удалось заставить работать таким образом неизмененный класс JMenuItems:
myMenu.add(new JMenuItem("Save As..."));
создавая подкласс JMenuItems и перекрывая метод menuSelectionChanged(). К сожалению, так нельзя поступить ни с элементами меню, созданными с помощью action (которая возвращает чистый JMenuItem), ни с JCheckBoxMenuItems или JRadioButtonMenuItems.
Я использую JDK 1.2.2 FCS.
Эксперты Java Q&A рекомендуют использовать делегирование к базовому adapter class вместо наследования, для того, чтобы получить требуемую функциональность.
Adapter class возьмет JMenuItem, строку (String) со справочной информацией и JLabel, как строку статуса для отбражения сообщения. В момент выбора элемента меню adapter class отобразит соответствующий текст в строке статуса.
Сложная часть этой задачи: как обнаружить, какое событие мы должны отслеживать? Оказывается, ChangeEvent создается, когда активируется или деактивируется элемент меню. В Java для этого используется термин arming элемента меню. Таким образом, мы можем написать следующий adapter class:
class MenuHelpTextAdapter implements ChangeListener { private JMenuItem menuItem; private String helpText; private JLabel statusBar;
public MenuHelpTextAdapter( JMenuItem menuItem, String helpText, JLabel statusBar) { this.menuItem = menuItem; this.helpText = helpText; this.statusBar = statusBar; menuItem.addChangeListener(this); }
public void stateChanged(ChangeEvent evt) { if (menuItem.isArmed()) statusBar.setText(helpText); else statusBar.setText(" "); } }
Код, который создает adapterы для элементов меню достаточно прост:
new MenuHelpTextAdapter( item1, " Help text for item 1", statusBar);
new MenuHelpTextAdapter( item2, "Item 2 info", statusBar);
Используя этот adapter, как внутренний класс в данном случае, мы можем создать полную программу:
import java.awt.event.*; import java.awt.*; import javax.swing.*; import javax.swing.event.*;
public class StatusBarTest { JLabel statusBar; JMenuItem item1; JMenuItem item2;
private void init() { JFrame frame = new JFrame("Status Bar Test");
JMenuBar menuBar = new JMenuBar(); frame.setJMenuBar(menuBar); JMenu fileMenu = new JMenu("File"); menuBar.add(fileMenu);
item1 = new JMenuItem("Item 1"); item2 = new JMenuItem("Item 2");
fileMenu.add(item1); fileMenu.add(item2);
Container contentPane = frame.getContentPane(); contentPane.setLayout(new BorderLayout()); contentPane.add( "Center", new JButton( "Main application area"));
statusBar = new JLabel(" ");
contentPane.add("South", statusBar);
new MenuHelpTextAdapter( item1, "Help text for item 1", statusBar); new MenuHelpTextAdapter( item2, "Item 2 info", statusBar);
frame.setSize(400,400); frame.setVisible(true); }
public static void main(String[] args) { StatusBarTest t = new StatusBarTest(); t.init(); }
class MenuHelpTextAdapter implements ChangeListener { private JMenuItem menuItem; private String helpText; private JLabel statusBar;
public MenuHelpTextAdapter( JMenuItem menuItem, String helpText, JLabel statusBar) { this.menuItem = menuItem; this.helpText = helpText; this.statusBar = statusBar; menuItem.addChangeListener(this); }
public void stateChanged (ChangeEvent evt) { if (menuItem.isArmed()) statusBar.setText(helpText); else statusBar.setText(" "); } } }
Использование нитей в Java
Перевод на русский © , 2000
Java Q&A
Может ли программист задать для своего кода использование green threads или native threads?
Нет. Только пользователь может решить, какой пакет поддержки нитей будет использоватся при запуске Java программы. Более того, когда программа запущена, нельзя переключится между green threads и native threads.
Следовательно, вы должны написать код таким образом, чтобы он мог работать как с green threads, так и с native threads. Native threads относительно просты, так как они используют систему управления нитями операционной системы, большинство из которых гарантирует что каждая нить получит возможность работы. Нити с высоким приоритетом запускаются чаще, чем нити с низким, но все они получают возможность работать.
Green threads, однако, не обеспечивают такой гарантии. Если работает нить с высоким приоритетом, цикл в коде, который никогда не выполняет sleep(), yield(), wait() или perform() некоторые блокирующие функции ввода-вывода (I/O) не даст возможности работать другим нитям. Можно только пообещать, что нити с высоким приоритетом будут преобладать над нитями с низким. Это значит, что вся работа по распределению времени лежит на плечах программиста. Следовательно, код, который сам отвечает за распределение времени между нитями будет нормально работать как с green threads, так и с native threads; и, дополнительно, будет хорошо работать с native threads в любой операционной системе.
Брать на себе ответственность за распределение времени в программе кажется трудным, но, на самом деле, для большинства программ нужно сделать не так уж много. Каждый раз, когда нить выполняет сетевую операцию или операцию ввода-вывода, она должна дать возможность работать другой нити.
Распределение времени намного более важно для программ с нитями, которые работают в цикле или тратят значительное время на математические расчеты без ввода-вывода. В этих случаях простой вызов Thread.yield() достаточен для того, чтобы дать возможность работать другим нитям с тем же приоритетом. Для разделения процессорного времени с низко-приоритетными нитями, нить может вызвать Thread.sleep(), или, более точно, wait() для объекта пока нить с низким приоритетом не даст сигнал окончания работы, вызвав notify() для объекта.
Минимизирование окна
Перевод на русский © , 2000
Java Q&A
Существует ли надежный способ для задания минимального размера окна? Я хочу, чтобы мои пользователи могли увеличить окно приложения, но при этом не могли сделать его меньше определенного минимального размера. Я просмотрел документацию API для иерархии JFrame и не нашел ничего подходящего.
Java не содержит отдельного метода для задания минимального размера окна. Вы, однако, можете заставить окно придерживатся определенного минимального размера используя обработчики событий (event listeners). Если получено событие изменения размера окна, проверьте размер, и, если он меньше необходимого, восстановите его.
Вот пример кода:
import javax.swing.*; import java.awt.event.*;
public class MinSizeFrame extends JFrame implements ComponentListener {
static final int WIDTH = 400; static final int HEIGHT = 400;
static final int MIN_WIDTH = 300; static final int MIN_HEIGHT = 300;
public MinSizeFrame() { setSize(WIDTH, HEIGHT); addComponentListener(this); }
public void componentResized (ComponentEvent e) {
int width = getWidth(); int height = getHeight();
//мы проверяем ширину и высоту
boolean resize = false;
if (width < MIN_WIDTH) { resize = true; width = MIN_WIDTH; } if (height < MIN_HEIGHT) { resize = true; height = MIN_HEIGHT; } if (resize) { setSize(width, height); } }
public void componentMoved(ComponentEvent e){ }
public void componentShown(ComponentEvent e){ }
public void componentHidden(ComponentEvent e){ }
public static void main(String args[]) { MinSizeFrame f = new MinSizeFrame(); f.setVisible(true); } }
Конструктор апплета
Перевод на русский © , 2000
Java Q&A
Я не понимаю, для чего нужен конструктор в апплетах. Могу ли я его перекрывать?
Для того, чтобы написать апплет, вы должны сначала создать подкласс класса Applet. Класс Applet точно такой-же, как и любой другой; следовательно, конструктор апплета -- это просто конструктор подкласса класса Applet.
Конструктор апплета, как и любой другой конструктор, не может быть перекрыт. Конструкторы выполняют все необходимые операции инициализации для нового объекта. Помните, что вы можете перекрывать только методы, а не конструкторы.
Класс Applet содержит пять методов, которые дают вам возможность создавать апплет: init(), start(), stop(), destroy() и paint(). Сейчас мы рассмотрим метод init().
Метод init(), работа которого чем-то похожа на конструктор, управляет инициализацией апплета. Браузер или applet viewer автоматически вызывает его для выполнения инициализации при каждой загрузке апплета.
В свете вышесказанного, мы рекомендуем не использовать конструкторы в апплетах, так как нет гарантий, что апплет получит все данные перед вызовом своего метода init(). Например, пускай мы хотим загрузить изображение внутри конструктора апплета. У нас просто ничего не получится, и такая операция должна выполнятся внутри метода init().
В заключение, конструктор апплета это всего лишь конструктор, который был унаследован от класса Applet. Более того, этот конструктор не может быть перекрыт. Поэтому, так как использование конструктора апплета неэффективно, мы советуем воспользоваться перекрытым методом init() для всех операций инициализации.
Как заменить кофейную чашку
Перевод на русский © , 2000
Java Q&A
Как мне заменить значок с кофейной чашкой, который находится в левом верхнем углу апплетов и окон?
Для того чтобы заменить значок для окна -- как для апплета, так и для приложения -- вы должны сначала создать объект Image. Для этого существует несколько путей, но здесь мы воспользуемся объектом ImageIcon, так как его конструктор просто берет имя файла.
ImageIcon image = new ImageIcon( "C:/images/your_image.gif");
Как только вы создали ImageIcon, вы можете вызвать его метод getImage() и передать его результат методу setIconImage() класса Frame.
Frame.setIconImage(image.getImage());
Стоит отметить, что поскольку класс JFrame в Swing наследует класс Frame из AWT, метод setIconImage() также доступен и в JFrame. Полный код примера для JFrame приведен ниже:
import java.awt.*; import java.awt.event.*; import javax.swing.*;
public class AppIconFrame extends JFrame { public AppIconFrame() { enableEvents(AWTEvent.WINDOW_EVENT_MASK); initFrame(); }
//Инициализация окна private void initFrame(){ this.setSize(new Dimension(400, 300)); this.setTitle("Custom Icon"); ImageIcon image = new ImageIcon("c:\yourpath\yourfile.gif"); this.setIconImage(image.getImage()); }
//Перекрыт для вызова System Close protected void processWindowEvent(WindowEvent e) { super.processWindowEvent(e); if(e.getID() == WindowEvent.WINDOW_CLOSING) { System.exit(0); } }
public static void main(String[] args){ AppIconFrame frame = new AppIconFrame(); frame.setVisible(true); } }
Горячие клавиши
Перевод на русский © , 2000
Java Q&A
Как мне установить горячие клавиши для меню и кнопок? Я использую JDK 1.1.4; отличаются ли соответствующие методы в JDK 1.2?
Пример ниже должен ответить на ваши вопросы о горячих клавишах в Java. Код показывает как установить быстрый доступ для меню, элемента меню, чек-бокса и кнопки. Вы можете выбрать горячую клавишу нажав Alt и код быстрого доступа.
Такой код будет работать и с JDK 1.1, и с JDK 1.2. Заметьте, что для JDK 1.1 вам нужна более новая версия Swing, чем 1.0.2.
import java.awt.*; import javax.swing.*; import java.awt.event.*;
public class ShortCutKeys extends JApplet { JButton button; JCheckBox checkBox;
JMenuBar menuBar; JMenu fileMenu; JMenuItem exitMenuItem; JPanel panel;
public void init() { Container container = this.getContentPane();
Handler eventHandler = new Handler();
checkBox = new JCheckBox("Hello Mom!"); checkBox.setMnemonic (java.awt.event.KeyEvent.VK_M); checkBox.addActionListener(eventHandler);
button = new JButton("Hello Dad!"); button.setMnemonic (java.awt.event.KeyEvent.VK_D); button.addActionListener(eventHandler);
panel = new JPanel(); panel.setLayout(new FlowLayout()); panel.add(checkBox); panel.add(button);
exitMenuItem = new JMenuItem("Exit"); exitMenuItem.setMnemonic('x'); exitMenuItem.addActionListener(eventHandler); fileMenu = new JMenu("File"); fileMenu.setMnemonic('f'); fileMenu.add(exitMenuItem); menuBar = new JMenuBar(); menuBar.add(fileMenu); container.add(menuBar, BorderLayout.NORTH);
container.add(panel, BorderLayout.CENTER); }
class Handler implements ActionListener { public void actionPerformed(ActionEvent ae) { if (ae.getSource() == checkBox) { System.err.println ("Action Performed on CHECKBOX"); } else if (ae.getSource() == button) { System.err.println ("Action Performed on BUTTON"); } else if (ae.getSource() == exitMenuItem) { System.exit(0); } } }
public static void main(String[] args) { JFrame frame = new JFrame("Short Cut Keys"); ShortCutKeys sck = new ShortCutKeys(); sck.init(); frame.getContentPane().add(sck); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { System.exit(0); } } ); frame.setSize(300, 100); frame.show(); } }
Статические классы
Перевод на русский © , 2000
Java Q&A
Можно ли объявить внутренний или обычный класс как статический (static)?
Для того, чтобы понять использование ключевого слова static в заголовке класса мы должны разобраться с заголовками классов вообще. Вы можете объявить два типа классов: обычные классы (верхнего уровня) и внутренние.
Обычные классы Вы объявляете обычный класс на уровне члена пакета (package). Каждый обычный класс соответствует своему собственному файлу java, использующему тоже имя, что и класс.
Обычный класс по-определению уже находится снаружи, поэтому нет смысла объявлять его статическим; это ошибка. Компилятор определит ее и сообщит вам.
Внутренние классы Вы определяете внутренний класс внутри обычного класса. В зависимости от того, как он определен, внутренний класс может быть одного из четырех типов:
1. Анонимный. Анонимный класс объявляется и создается внутри одного и того же выражения. У них нет имен и они могут быть созданы только один раз.
Вот пример анонимного класса:
okButton.addActionListener( new ActionListener(){ public void actionPerformed (ActionEvent e){ dispose(); } });
Так как анонимный класс не содержит нормального заголовка класса, где можно использовать static, он не может быть объявлен, как статический.
2. Локальный. Локальные классы точно такие-же, как и локальные переменные, в том смысле, что они создаются и используются внутри блока кода. Как только класс внутри блока объявлен, он может быть создан сколько угодно раз внутри этого блока. Как и локальные переменные, локальные классы не могут быть объявлены как public, protected, private или static.
Вот пример кода:
//some code block .......{ class ListListener implements ItemListener { List list; public ListListener(List l) { list = l; }
public void itemStateChanged(ItemEvent e) { String s = l.getItemSelected(); doSomething(s); } } List list1 = new List(); list list2 = new List(); list1.addItemListener( new ListListener(list1)); list2.addItemListener( new ListListener(list2)); }
3. Компонентный. Компонентный класс объявляется внутри тела класса. Вы можете использовать множество классов где-угодно внутри тела класса-контейнера. Вы должны объявить компонентный класс если вы хотите использовать переменные и методы класса-контейнера без необходимости делегирования.
Компонентный класс- единственный, который может быть объявлен статическим. Когда вы объявляете компонентный класс, вы можете создать его только внутри содержимого объекта внешнего класса, по отношению к которому объявлен компонетный класс. Если вы хотите обойти это ограничение, вам необходимо объявить комопонентный класс как статический.
Когда вы объявляете компонентный класс с модификатором static, он становится вложенным классом верхнего уровня и может быть использован как обычный класс, как объяснено выше.
4. Вложенный верхнего уровня. Вложенный класс верхнего уровня это компонентный класс с модификатором static. Вложенный класс верхнего уровня такой-же, как и любой другой класс верхнего уровня, но он объявлен внутри другого класса или интерфейса. Вложенные классы верхнего уровня обычно используются как удобный способ для объединия родственных классов без создания нового пакета.
Если ваш главный класс содержит несколько меньших вспомогательных классов, которые могут быть использованы вне класса и имеют смысл только внутри главного класса, стоит сделать их вложенными классами верхнего уровня. Для использования вложенных классов используйте: КлассВерхнегоУровня.ВложенныйКласс.
Посмотрите на следующий пример:
public class Filter { Vector criteria = new Vector(); public addCriterion(Criterion c) { criteria.addElement(c); } public boolean isTrue(Record rec) { for(Enumeration e=criteria.elements(); e.hasMoreElements();) { if(! ((Criterion)e.nextElement()).isTrue(rec)) return false; } return true; }
public static class Criterion { String colName, colValue; public Criterion(Stirng name, String val) { colName = name; colValue = val; } public boolean isTrue(Record rec) { String data = rec.getData(colName); if(data.equals(colValue)) return true; return false; } } }
И когда вы хотите его использовать:
Filter f = new Filter(); f.addCriterion(new Filter.Criterion("SYMBOL", "SUNW")); f.addCriterion(new Filter.Criterion("SIDE", "BUY")); ..... if(f.isTrue(someRec)) //do some thing .....
Одно важное замечание: ключевое слово static не делает с заголовком класса тоже, что с заголовком пременной или метода.
JFC: Почему именно так?
Java Q&A
Почему нужно добавлять компоненты приложения в контейнер content окна приложения JFrame? Почему нельзя добавлять их непосредственно в окно, как в библиотеке AWT?
Почему? - Потому, что так нужно.
В данном случае ответ действительно вполне исчерпывающий. Тем не менее, имеет смысл обсудить, как в библиотеке Swing реализованы контейнеры верхнего уровня, такие как JFrame. Все контейнеры верхнего уровня имеют единственную компоненту - объект класса JRootPane. Класс JRootPane, в свою очередь, содержит компоненту glassPane и класс JLayeredPane. Далее, класс JLayeredPane содержит меню и контейнер contentPane. Вложенность контейнеров предоставляет логическую структуру размещения компонент.
Требование добавлять компоненты в контейнер contentPane призывает придерживаться описанной структуры. Попытка поступить иначе будет противоречить принципам, заложенным разработчиками библиотека Swing.
Минуточку, но зачем используется такой дизайн?
Размещая компоненты приложения в одном месте (contentPane) вы значительно упрощаете себе жизнь. Такой подход позволяет, в частности, непосредственно использовать панель glassPane и располагать компоненты на разных слоях. Например, если бы вы разместили графическую компоненту над или рядом с glassPane, вы бы не смогли запретить передачу событий этой компоненте или нарисовать что-нибудь сверху так же просто, как с использованием glassPane. Кроме того, вы бы не смогли поместить слой (содержащий, например, выскакивающее меню) над всеми компонентами, так как часть из них может лежать вне контейнера layeredPane.
Следую общей идеологии, вы получаете все предусмотренные преимущества (частью которых вы, быть может, ни когда и не воспользуетесь). Если же вы захотите всех перехитрить, вам придется побеспокоиться о поддержке внешнего дизайна приложения (look and feel), самостоятельно реализовать возможность размещения компонент на разных слоях (уровнях), а так же, для полноты, реализовать функциональность glassPane. На этом пути вам придется переписать значительную часть библиотеки Swing.
Ресурсы
Graphic Java 2: Mastering the JFC, Volume 2: Swing, 3rd ed.,David M. Greary (Prentice Hall, 1999) provides a discussion on the design of Swing:
Reprinted with permission from the March 2000 edition of JavaWorld magazine.
Copyright © ITworld.com, Inc., an IDG Communications company.
View the original article at:
|