Философия Java

         

Более изощренные компоненты (Bean)


Этот следующий пример немного более изощренный, хотя фривольный. Есть JPanel, которая рисует маленькую окружность вокруг мыши, куда бы мышь не переместилась. Когда вы нажимаете кнопку, появляется “Bang!” в середине экрана, и возбуждается слушатель события.

Свойства, которые вы можете менять, это размер окружности, точно так же как и цвет, размер и текст слова, которое отображается при нажатии кнопки. BangBean также имеет свой собственный addActionListener( ) и removeActionListener( ), так что вы можете присоединять свой собственный слушатель, который будет обрабатывать щелчки мыши в BangBean. Вы должны быть способны распознать свойства и поддержку событий:

//: bangbean:BangBean.java

// Графический Bean.

package bangbean; import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.io.*; import java.util.*; import com.bruceeckel.swing.*;

public class BangBean extends JPanel implements Serializable { protected int xm, ym; protected int cSize = 20; // Размер окружности

protected String text = "Bang!"; protected int fontSize = 48; protected Color tColor = Color.red; protected ActionListener actionListener; public BangBean() { addMouseListener(new ML()); addMouseMotionListener(new MML()); } public int getCircleSize() { return cSize; } public void setCircleSize(int newSize) { cSize = newSize; } public String getBangText() { return text; } public void setBangText(String newText) { text = newText; } public int getFontSize() { return fontSize; } public void setFontSize(int newSize) { fontSize = newSize; } public Color getTextColor() { return tColor; } public void setTextColor(Color newColor) { tColor = newColor; } public void paintComponent(Graphics g) { super.paintComponent(g); g.setColor(Color.black); g.drawOval(xm - cSize/2, ym - cSize/2, cSize, cSize); } // Это уникальный слушатель, который

// является упрощенной формой управления слушателем:

public void addActionListener ( ActionListener l) throws TooManyListenersException { if(actionListener != null) throw new TooManyListenersException(); actionListener = l; } public void removeActionListener( ActionListener l) { actionListener = null; } class ML extends MouseAdapter { public void mousePressed(MouseEvent e) { Graphics g = getGraphics(); g.setColor(tColor); g.setFont( new Font( "TimesRoman", Font.BOLD, fontSize)); int width = g.getFontMetrics().stringWidth(text); g.drawString(text, (getSize().width - width) /2, getSize().height/2); g.dispose(); // Вызов метода слушателя:


if(actionListener != null) actionListener.actionPerformed( new ActionEvent(BangBean.this, ActionEvent.ACTION_PERFORMED, null)); } } class MML extends MouseMotionAdapter { public void mouseMoved(MouseEvent e) { xm = e.getX(); ym = e.getY(); repaint(); } } public Dimension getPreferredSize() { return new Dimension(200, 200); } } ///:~

Первое, что вы заметите, это то, что BangBean реализует интерфейс Serializable. Это значит, что построитель приложения может “законсервировать” всю информацию для BangBean, используя сериализацию, после того, как дизайнер установит значения свойств. Когда компонент (Bean) создастся как часть работающего приложения, эти “законсервированные” свойства восстанавливаются, так что вы получаете точно то, что разрабатывали.

Вы можете видеть, что все поля являются private, это то, что вы обычно делаете с компонентами, позволяя получить доступ только через методы, обычно, используя схему “property”.

Когда вы взглянете на сигнатуру addActionListener( ), вы увидите, что он может выбрасывать TooManyListenersException. Это означает, что здесь индивидуальная (unicast) обработка, которая означает, что извещается только один слушатель о возникновении события. Обычно вы будете использовать групповые (multicast) события, так что много слушателей могут быть извещены о событии. Однако это вводит нас в область, которую вы не готовы воспринять до следующей главы, так что мы вернемся к этому (под заголовком “возврат к JavaBeans”). Индивидуальная обработка обходит проблему.

Когда вы щелкаете мышью, текст помещается в центр BangBean, а если поле actionListener не null, вызывается actionPerformed( ), в процессе создается новый объект ActionEvent. Куда бы мышь не переместилась, захватываются новые координаты, и происходит перерисовка канвы (стирание любого текста, расположенного на канве, как вы увидите).

Здесь приведен класс BangBeanTest, позволяющий вам проверить компонент либо как апплет, либо как приложение:

//: c13:BangBeanTest.java

// <applet code=BangBeanTest



// width=400 height=500></applet>

import bangbean.*; import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.util.*; import com.bruceeckel.swing.*;

public class BangBeanTest extends JApplet { JTextField txt = new JTextField(20); // Во время тестирования отчет о действиях:



class BBL implements ActionListener { int count = 0; public void actionPerformed(ActionEvent e){ txt.setText("BangBean action "+ count++); } } public void init() { BangBean bb = new BangBean(); try { bb.addActionListener(new BBL()); } catch(TooManyListenersException e) { txt.setText("Too many listeners"); } Container cp = getContentPane(); cp.add(bb); cp.add(BorderLayout.SOUTH, txt); } public static void main(String[] args) { Console.run(new BangBeanTest(), 400, 500); } } ///:~

Когда компонент (Bean) находится в среде разработки, этот класс не будет использоваться, но полезно обеспечить тестирование метода для каждого вашего компонента (Bean). BangBeanTest помещает BangBean в апплет, присоединяет простой ActionListener к BangBean для печати счетчика событий в JTextField, когда бы не возникло ActionEvent. Конечно, обычно построитель приложения создает большинство кода, который использует этот компонент (Bean).

Когда вы запустите BangBean через BeanDumper, или поместите BangBean внутрь среды разработки, поддерживающей компоненты, вы заметите, что есть гораздо больше свойств и действий, чем это видно в приведенном выше коде. Это происходит потому, что BangBean наследуется от JPanel, а JPanel - это тоже компонент (Bean), так что вы также видите его свойства и события.


Более изощренный пример


Более интересный пример [73] задействует множественную базу данных, расположенную на сервере. Здесь в качестве базы данных используется хранилище для ведения журнала обращений, чтобы позволить людям регестрировать события, поэтому она называется Community Interests Database (CID). Этот пример будет обеспечивать только просмотр базы данных и ее реализации, и не предназначен быть полным руководством по разработке баз данных. Есть множество книг, семинаров и пакетов программ, которые помогут вам при проектировании и разработке базы данных.

Кроме того, этот пример предполагает, что проведена предварительная установка SQL базы данных на сервере (хотя это может быть запущено и на локальной машине), а так же опрос и обнаружение подходящего JDBC драйвера для базы данных. Существуют несколько бесплатных SQL баз данных, и некоторые из них автоматически устанавливаются с различными версиями Linux. Вы сами отвечаете за выбор базы данных и поиск JDBC драйвера. Приведенный здесь пример основывается на SQL базе данных системы, называемой “Cloudscape”.

Чтобы упростить изменения в информации о соединении, драйвер базы данных, URL базы данных, имя пользователя и пароль помещены в отдельный класс:

//: c15:jdbc:CIDConnect.java

// Информация для подключения к базе данных

// community interests database (CID).

public class CIDConnect { // Вся информация спецефична для CloudScape:

public static String dbDriver = "COM.cloudscape.core.JDBCDriver"; public static String dbURL = "jdbc:cloudscape:d:/docs/_work/JSapienDB"; public static String user = ""; public static String password = ""; } ///:~

В этом примере нет пароля защиты базы данных, поэтому имя пользователя и пароль представлены пустыми строками.

База данных состоит из множества таблиц, структура которых показана ниже:


“Members” содержит информацию о пользователе, “Events” и “Locations” содержат информацию о подключении и откуда оно было сделано, а “Evtmems” объединяет события и пользователей, которые хотят знать о событиях. Можно видеть, что данные в одной таблице являются ключами в другой таблице.


Следующий класс содержит SQL строки, которые создают эти таблицы базы данных (обратитесь к руководству по SQL, чтобы узнать объяснения этого кода):

//: c15:jdbc:CIDSQL.java

// SQL строки для создания таблиц CID.

public class CIDSQL { public static String[] sql = { // Создание таблицы MEMBERS:

"drop table MEMBERS", "create table MEMBERS " + "(MEM_ID INTEGER primary key, " + "MEM_UNAME VARCHAR(12) not null unique, "+ "MEM_LNAME VARCHAR(40), " + "MEM_FNAME VARCHAR(20), " + "ADDRESS VARCHAR(40), " + "CITY VARCHAR(20), " + "STATE CHAR(4), " + "ZIP CHAR(5), " + "PHONE CHAR(12), " + "EMAIL VARCHAR(30))", "create unique index " + "LNAME_IDX on MEMBERS(MEM_LNAME)", // Создание таблицы EVENTS

"drop table EVENTS", "create table EVENTS " + "(EVT_ID INTEGER primary key, " + "EVT_TITLE VARCHAR(30) not null, " + "EVT_TYPE VARCHAR(20), " + "LOC_ID INTEGER, " + "PRICE DECIMAL, " + "DATETIME TIMESTAMP)", "create unique index " + "TITLE_IDX on EVENTS(EVT_TITLE)", // Создание таблицы EVTMEMS

"drop table EVTMEMS", "create table EVTMEMS " + "(MEM_ID INTEGER not null, " + "EVT_ID INTEGER not null, " + "MEM_ORD INTEGER)", "create unique index " + "EVTMEM_IDX on EVTMEMS(MEM_ID, EVT_ID)", // Создание таблицы LOCATIONS

"drop table LOCATIONS", "create table LOCATIONS " + "(LOC_ID INTEGER primary key, " + "LOC_NAME VARCHAR(30) not null, " + "CONTACT VARCHAR(50), " + "ADDRESS VARCHAR(40), " + "CITY VARCHAR(20), " + "STATE VARCHAR(4), " + "ZIP VARCHAR(5), " + "PHONE CHAR(12), " + "DIRECTIONS VARCHAR(4096))", "create unique index " + "NAME_IDX on LOCATIONS(LOC_NAME)", }; } ///:~



Следующая программа использует информацию CIDConnect и CIDSQL для загрузки JDBC драйвера, создания соединения с базой данных и создания таблиц, структура которых показана на диаграмме. Для соединения с базой данных вы вызываете статический (static) метод DriverManager.getConnection( ), передавая в него URL базы данных, имя пользователя и пароль для доступа к базе данных. Назад вы получаете объект Connection, который вы можете использовать для опроса и манипуляций с базой данных. Как только соединение создано, вы можете просто поместить SQL в базу данных, в этом случае проходя по массиву CIDSQL. Однако при первом запуске этой программы команда “drop table” завершиться неудачей, что станет причиной исключения, которое будет поймано, объявлено и проигнорировано. Необходимость команды “drop table” легко понять из экспериментов: вы можете измнить SQL, который определяет таблицы, а затем вернуться в программу. При этом возникнет необходимость заменить старые таблицы новыми.

В этом примере есть смысл позволить исключениям отображаться на консоли:

//: c15:jdbc:CIDCreateTables.java

// Создание таблиц базы данных для

// community interests database.

import java.sql.*;

public class CIDCreateTables { public static void main(String[] args) throws SQLException, ClassNotFoundException, IllegalAccessException { // Загрузка драйвера (саморегистрация)

Class.forName(CIDConnect.dbDriver); Connection c = DriverManager.getConnection( CIDConnect.dbURL, CIDConnect.user, CIDConnect.password); Statement s = c.createStatement(); for(int i = 0; i < CIDSQL.sql.length; i++) { System.out.println(CIDSQL.sql[i]); try { s.executeUpdate(CIDSQL.sql[i]); } catch(SQLException sqlEx) { System.err.println( "Probably a 'drop table' failed"); } } s.close(); c.close(); } } ///:~

Обратите внимание, что все изменения в базе данных могут управляться путем изменения строк (String) в таблице CIDSQL, при этом CIDCreateTables не меняется.

executeUpdate( ) обычно возвращает число строк, которые были получены при воздействии SQL инструкции. executeUpdate( ) чаще всего используется для выполнения таких инструкций, как INSERT, UPDATE или DELETE, чтобы изменить одну или более строк. Для таких инструкций, как CREATE TABLE, DROP TABLE и CREATE INDEX, executeUpdate( ) всегда возвращает ноль.



Для проверки базы данных она загружается некоторыми простыми данными. Это требует нескольких INSERT'ов, за которыми следует SELECT для получения результирующего множества. Чтобы облегчить проверку добавления и изменения данных, тестовые данные представлены в виде двумерного массива типа Object, а метод executeInsert( ) затем может использовать информацию из одной строки для создания соответствующей SQL команды.

//: c15:jdbc:LoadDB.java

// Loads and tests the database.

import java.sql.*;

class TestSet { Object[][] data = { { "MEMBERS", new Integer(1), "dbartlett", "Bartlett", "David", "123 Mockingbird Lane", "Gettysburg", "PA", "19312", "123.456.7890", "bart@you.net" }, { "MEMBERS", new Integer(2), "beckel", "Eckel", "Bruce", "123 Over Rainbow Lane", "Crested Butte", "CO", "81224", "123.456.7890", "beckel@you.net" }, { "MEMBERS", new Integer(3), "rcastaneda", "Castaneda", "Robert", "123 Downunder Lane", "Sydney", "NSW", "12345", "123.456.7890", "rcastaneda@you.net" }, { "LOCATIONS", new Integer(1), "Center for Arts", "Betty Wright", "123 Elk Ave.", "Crested Butte", "CO", "81224", "123.456.7890", "Go this way then that." }, { "LOCATIONS", new Integer(2), "Witts End Conference Center", "John Wittig", "123 Music Drive", "Zoneville", "PA", "19123", "123.456.7890", "Go that way then this." }, { "EVENTS", new Integer(1), "Project Management Myths", "Software Development", new Integer(1), new Float(2.50), "2000-07-17 19:30:00" }, { "EVENTS", new Integer(2), "Life of the Crested Dog", "Archeology", new Integer(2), new Float(0.00), "2000-07-19 19:00:00" }, // Сопоставление людей и событий



{ "EVTMEMS", new Integer(1), // Dave is going to

new Integer(1), // the Software event.

new Integer(0) }, { "EVTMEMS", new Integer(2), // Bruce is going to

new Integer(2), // the Archeology event.

new Integer(0) }, { "EVTMEMS", new Integer(3), // Robert is going to

new Integer(1), // the Software event.

new Integer(1) }, { "EVTMEMS", new Integer(3), // ... and

new Integer(2), // the Archeology event.

new Integer(1) }, }; // Use the default data set:

public TestSet() {} // Use a different data set:

public TestSet(Object[][] dat) { data = dat; } }

public class LoadDB { Statement statement; Connection connection; TestSet tset; public LoadDB(TestSet t) throws SQLException { tset = t; try { // Load the driver (registers itself)

Class.forName(CIDConnect.dbDriver); } catch(java.lang.ClassNotFoundException e) { e.printStackTrace(System.err); } connection = DriverManager.getConnection( CIDConnect.dbURL, CIDConnect.user, CIDConnect.password); statement = connection.createStatement(); } public void cleanup() throws SQLException { statement.close(); connection.close(); } public void executeInsert(Object[] data) { String sql = "insert into " + data[0] + " values("; for(int i = 1; i < data.length; i++) { if(data[i] instanceof String) sql += "'" + data[i] + "'"; else

sql += data[i]; if(i < data.length - 1) sql += ", "; } sql += ')'; System.out.println(sql); try { statement.executeUpdate(sql); } catch(SQLException sqlEx) { System.err.println("Insert failed."); while (sqlEx != null) { System.err.println(sqlEx.toString()); sqlEx = sqlEx.getNextException(); } } } public void load() { for(int i = 0; i< tset.data.length; i++) executeInsert(tset.data[i]); } // Выбрасываем исключение на консоль:

public static void main(String[] args) throws SQLException { LoadDB db = new LoadDB(new TestSet()); db.load(); try { // Получаем ResultSet из загруженной базы данных:

ResultSet rs = db.statement.executeQuery( "select " + "e.EVT_TITLE, m.MEM_LNAME, m.MEM_FNAME "+ "from EVENTS e, MEMBERS m, EVTMEMS em " + "where em.EVT_ID = 2 " + "and e.EVT_ID = em.EVT_ID " + "and m.MEM_ID = em.MEM_ID"); while (rs.next()) System.out.println( rs.getString(1) + " " + rs.getString(2) + ", " + rs.getString(3)); } finally { db.cleanup(); } } } ///:~



Класс TestSet содержит множество данных по умолчанию, которое производится, если вы используете конструктор по умолчанию. Однако вы можете создать объект TestSet, используя альтернативный набор данных со вторым конструкторомo. Набор данных хранится в двумерном массиве типа Object, поскольку он может быть любого типа, включая String или числовые типы. Метод executeInsert( ) использует RTTI того, чтобы различать данные типа String (которые должны быть в кавычках) и данные не типа String, так как SQL команда строится из данных. После печати этой команды на консоль используется executeUpdate( ) для отсылки ее в базу данных.

Конструктор для LoadDB создает соединение и пошагово с помощью load( ) проходит по данным и вызывает executeInsert( ) для каждой записи. cleanup( ) закрывает инструкцию и соединение. Чтобы гарантировать этот вызов, он помещен в предложение finally.

Как только база данных будет загружена, инструкция executeQuery( ) производит простое результирующее множество. Так как запрос комбинирует несколько таблиц, он является примером объединения.

Более подробно о JDBC можно узнать в электронной документации, которая распространяется как часть пакета Java от Sun. Кроме того, вы можете найти дополнительную информацию в книге JDBC Database Access with Java (Hamilton, Cattel, and Fisher, Addison-Wesley, 1997). Другие книги, посвященные JDBC, появляются регулярно.


Более сложная поддержка компонент (Bean)


Вы можете видеть, как удивительно просто создать компонент (Bean). Но вы не ограничены тем, что вы видели здесь. Архитектура JavaBeans обеспечивает простой способ входа, но вы можете также распространить ее на более сложные ситуации. Эти ситуации выходят за пределы тем, рассматриваемых этой книгой, но они будут коротко обозначены здесь. Вы можете найти более подробный материал на java.sun.com/beans.

Одно из мест, где вы можете добавить изощренность - это свойства (properties). Приведенный выше пример показывает только единичные свойства, но также возможно представить различные свойства в массиве. Это называется индексированным свойством (indexed property). Вы просто обеспечиваете соответствующие методы (опять таки, следую соглашению об именах методов), а Introspector определяет их как индексированные свойства, так что ваш построитель приложения может отобразить их соответственно.

Свойства могут быть граничными, что означает, что они будут уведомлять другие объекты через PropertyChangeEvent. Другие объекты могут затем выбрать изменения себя, основываясь на изменении компонента (Bean).

Свойства могут быть ограничены, это значит, что другие объекты могут запрещать изменения этого свойства, если это недопустимо. Другие объекты уведомляются при помощи PropertyChangeEvent, и они могут выбросить исключение PropertyVetoException для предотвращения изменений и для восстановления старого значения.

Вы также можете изменить способ представления вашего компонента (Bean) в режиме дизайна:

Вы можете обеспечить специальную страницу свойств для вашего определенного компонента (Bean). Обычная страница свойств будет использоваться для всех остальных компонентов (Bean), а ваша будет вызываться автоматически, когда будет выбран ваш компонент (Bean).

Вы можете создать специальный редактор для определенного свойства, так что будет использоваться обычная страница свойств, но когда вы начнете редактировать особое свойство, ваш редактор вызовется автоматически.

Вы можете обеспечить специальный класс BeanInfo для вашего компонента (Bean), который будет производить информацию, отличную от информации по умолчанию, создаваемой Introspector.

Также возможно включать и выключать режим “эксперта” во всех FeatureDescriptor для различия между основными особенностями и более сложными особенностями.



Больше о компонентах (Beans)


Есть другой подход, который не будет указан здесь. Где бы вы не создавали компонент (Bean), вы должны ожидать, что он будет работать в многопоточной среде. Это означает, что вы должны понимать способности потоков, которые будут введены в Главе 14. Вы найдете раздел, называемый “возврат к JavaBeans”, в которой мы рассмотрим проблему и ее решение.

Есть несколько книг, посвященных JavaBeans; например, JavaBeans by Elliotte Rusty Harold (IDG, 1998).



BorderLayout


Апплет по умолчанию использует схему компоновки по умолчанию: BorderLayout (несколько предыдущих примеров меняли менеджер компоновки на FlowLayout). Без каких-то дополнительных инструкций он принимает все, что вы добавляете (add( )) и помещает это в центр, растягивая объект во все стороны до края.

Однако BorderLayout может больше. Этот менеджер компоновки имеет концепцию четырех граничных областей и центральной области. Когда вы добавляете что-то в панель, которая использует BorderLayout, вы можете использовать перегруженный метод add( ), принимающий константу в качестве своего первого аргумента. Это значение может быть любым из следующих:

BorderLayout.NORTH (верх)

BorderLayout.SOUTH (низ)

BorderLayout.EAST (справа)

BorderLayout.WEST (слева)

BorderLayout.CENTER (заполнить середину до других компонент или до краев)

Если вы не указываете область для помещения объекта, по умолчанию выбирается CENTER.

Вот пример. Используется компоновка по умолчанию, так как для JApplet по умолчанию используется BorderLayout:

//: c13:BorderLayout1.java

// Демонстрация BorderLayout.

// <applet code=BorderLayout1

// width=300 height=250> </applet>

import javax.swing.*; import java.awt.*; import com.bruceeckel.swing.*;

public class BorderLayout1 extends JApplet { public void init() { Container cp = getContentPane(); cp.add(BorderLayout.NORTH, new JButton("North")); cp.add(BorderLayout.SOUTH, new JButton("South")); cp.add(BorderLayout.EAST, new JButton("East")); cp.add(BorderLayout.WEST, new JButton("West")); cp.add(BorderLayout.CENTER, new JButton("Center")); } public static void main(String[] args) { Console.run(new BorderLayout1(), 300, 250); } } ///:~

Для всех место, кроме CENTER, элемент, который вы добавляете, сжимается, чтобы занимать наименьшее пространство по одному измерению, а по другому измерению он растягивается. Однако для CENTER, подстройка идет в обоих направлениях, чтобы занять середину.



Бордюры


JComponent содержит метод, называемый setBorder( ), который позволяет вам поместить разные интересные бордюры на любой видимый компонент. Следующий пример демонстрирует несколько различных поддерживаемых бордюров, используя метод, называемый showBorder( ), который создает JPanel и помещает бордюр в каждом случае. Также он использует RTTI для нахождения имени бордюра, который вы используете (отсекая информацию о пути), затем помещает это имя в JLabel, находящуюся в середине панели:

//: c13:Borders.java

// Различные бордюры Swing.

// <applet code=Borders

// width=500 height=300></applet>

import javax.swing.*; import java.awt.*; import java.awt.event.*; import javax.swing.border.*; import com.bruceeckel.swing.*;

public class Borders extends JApplet { static JPanel showBorder(Border b) { JPanel jp = new JPanel(); jp.setLayout(new BorderLayout()); String nm = b.getClass().toString(); nm = nm.substring(nm.lastIndexOf('.') + 1); jp.add(new JLabel(nm, JLabel.CENTER), BorderLayout.CENTER); jp.setBorder(b); return jp; } public void init() { Container cp = getContentPane(); cp.setLayout(new FlowLayout()); cp.setLayout(new GridLayout(2,4)); cp.add(showBorder(new TitledBorder("Title"))); cp.add(showBorder(new EtchedBorder())); cp.add(showBorder(new LineBorder(Color.blue))); cp.add(showBorder( new MatteBorder(5,5,30,30,Color.green))); cp.add(showBorder( new BevelBorder(BevelBorder.RAISED))); cp.add(showBorder( new SoftBevelBorder(BevelBorder.LOWERED))); cp.add(showBorder(new CompoundBorder( new EtchedBorder(), new LineBorder(Color.red)))); } public static void main(String[] args) { Console.run(new Borders(), 500, 300); } } ///:~

Вы также можете создать свой собственный бордюр и поместить его внутри кнопок, меток и т.п. — всего, что унаследовано от JComponent.



BoxLayout


Потому, что люди имеют много трудностей при работе с GridBagLayout, Swing также включает BoxLayout, который предоставляет вам много полезного, что умеет GridBagLayout без той сложности, так что вы можете часто использовать его, когда вам нужно выполнить ручное кодирование (опят таки, если ваш дизайн станет через чур сложным, используйте построитель GUI, который генерирует для вас GridBagLayout). BoxLayout позволяет вам управлять вам размещением компонент либо вертикально, либо горизонтально, и управлять пространством между компонентами, используя что-то, называемое “подпорки и склейки”. Сначала, позвольте показать, как использовать BoxLayout непосредственно, тем же способом, как были продемонстрированы другие менеджеры компоновки:

//: c13:BoxLayout1.java

// Вертикальный и горизонтальный BoxLayouts.

// <applet code=BoxLayout1

// width=450 height=200> </applet>

import javax.swing.*; import java.awt.*; import com.bruceeckel.swing.*;

public class BoxLayout1 extends JApplet { public void init() { JPanel jpv = new JPanel(); jpv.setLayout( new BoxLayout(jpv, BoxLayout.Y_AXIS)); for(int i = 0; i < 5; i++) jpv.add(new JButton("" + i)); JPanel jph = new JPanel(); jph.setLayout( new BoxLayout(jph, BoxLayout.X_AXIS)); for(int i = 0; i < 5; i++) jph.add(new JButton("" + i)); Container cp = getContentPane(); cp.add(BorderLayout.EAST, jpv); cp.add(BorderLayout.SOUTH, jph); } public static void main(String[] args) { Console.run(new BoxLayout1(), 450, 200); } } ///:~

Конструктор для BoxLayout немного отличается от других менеджеров компоновки — вы обеспечиваете Container, который будет управляться BoxLayout, в качестве первого аргумента, и направление компоновки в качестве второго аргумента.

Для упрощения дела, есть специальный контейнер, называемый Box, который использует BoxLayout, как свой родной менеджер. Следующий пример располагает компоненты горизонтально и вертикально, используя Box, который имеет два статических метода для создания боксов с вертикальным и горизонтальным выравниванием:

//: c13:Box1.java


// Вертикальный и горизонтальный BoxLayouts.

// <applet code=Box1

// width=450 height=200> </applet>

import javax.swing.*; import java.awt.*; import com.bruceeckel.swing.*;

public class Box1 extends JApplet { public void init() { Box bv = Box.createVerticalBox(); for(int i = 0; i < 5; i++) bv.add(new JButton("" + i)); Box bh = Box.createHorizontalBox(); for(int i = 0; i < 5; i++) bh.add(new JButton("" + i)); Container cp = getContentPane(); cp.add(BorderLayout.EAST, bv); cp.add(BorderLayout.SOUTH, bh); } public static void main(String[] args) { Console.run(new Box1(), 450, 200); } } ///:~

Как только вы получаете Box, вы передаете его в качестве второго аргумента при добавлении компонента в панель содержания.

Распорки между компонентами измеряется в пикселях. Для использования распорок, вы просто добавляете их между вставкой компонент:

//: c13:Box2.java

// Добавление разделителей.

// <applet code=Box2

// width=450 height=300> </applet>

import javax.swing.*; import java.awt.*; import com.bruceeckel.swing.*;

public class Box2 extends JApplet { public void init() { Box bv = Box.createVerticalBox(); for(int i = 0; i < 5; i++) { bv.add(new JButton("" + i)); bv.add(Box.createVerticalStrut(i*10)); } Box bh = Box.createHorizontalBox(); for(int i = 0; i < 5; i++) { bh.add(new JButton("" + i)); bh.add(Box.createHorizontalStrut(i*10)); } Container cp = getContentPane(); cp.add(BorderLayout.EAST, bv); cp.add(BorderLayout.SOUTH, bh); } public static void main(String[] args) { Console.run(new Box2(), 450, 300); } } ///:~

Распорки разделяют компоненты на фиксированную величину, а склейки наоборот: они разделят компоненты настолько, насколько это возможно. Так что это, скорее “пружина”, чем “клей” (а дизайн, на котором это базируется должен называться “пружины и распорки”, так что выбор терминов немного непонятен).

//: c13:Box3.java

// Использование Glue (клея).

// <applet code=Box3

// width=450 height=300> </applet>



import javax.swing.*; import java.awt.*; import com.bruceeckel.swing.*;

public class Box3 extends JApplet { public void init() { Box bv = Box.createVerticalBox(); bv.add(new JLabel("Hello")); bv.add(Box.createVerticalGlue()); bv.add(new JLabel("Applet")); bv.add(Box.createVerticalGlue()); bv.add(new JLabel("World")); Box bh = Box.createHorizontalBox(); bh.add(new JLabel("Hello")); bh.add(Box.createHorizontalGlue()); bh.add(new JLabel("Applet")); bh.add(Box.createHorizontalGlue()); bh.add(new JLabel("World")); bv.add(Box.createVerticalGlue()); bv.add(bh); bv.add(Box.createVerticalGlue()); getContentPane().add(bv); } public static void main(String[] args) { Console.run(new Box3(), 450, 300); } } ///:~

Распорки работают в одном направлении, но закрепленное место фиксирует пространство между компонентами в обоих направлениях:

//: c13:Box4.java

// Закрепленное Место(Rigid Areas) - это как пара распорок.

// <applet code=Box4

// width=450 height=300> </applet>

import javax.swing.*; import java.awt.*; import com.bruceeckel.swing.*;

public class Box4 extends JApplet { public void init() { Box bv = Box.createVerticalBox(); bv.add(new JButton("Top")); bv.add(Box.createRigidArea( new Dimension(120, 90))); bv.add(new JButton("Bottom")); Box bh = Box.createHorizontalBox(); bh.add(new JButton("Left")); bh.add(Box.createRigidArea( new Dimension(160, 80))); bh.add(new JButton("Right")); bv.add(bh); getContentPane().add(bv); } public static void main(String[] args) { Console.run(new Box4(), 450, 300); } } ///:~

Вы должны знать, что закрепленные области несколько спорны. Так как они использую абсолютное значение, некоторые люди чувствуют, что они причиняют больше неприятностей, чем они стоят.


Break и continue


Внутри тела любой инструкции итераций вы также можете использовать управление течением цикла, используя break и continue. break прерывает цикл без выполнения оставшихся инструкций в цикле. continue останавливает выполнение текущей итерации и возвращается к началу цикла, начиная следующую итерацию.

Эта программа показывает пример для break и continue внутри циклов for и while:

//: c03:BreakAndContinue.java

// Демонстрирует break и continue.

public class BreakAndContinue { public static void main(String[] args) { for(int i = 0; i < 100; i++) { if(i == 74) break; // вызод из цикла for

if(i % 9 != 0) continue; // Следующая итерация

System.out.println(i); } int i = 0; // "Бесонечный цикл":

while(true) { i++; int j = i * 27; if(j == 1269) break; // Выход из цикла

if(i % 10 != 0) continue; // В начало цикла

System.out.println(i); } } } ///:~

В цикле for значение i никогда не дойдет до 100, потому, что инструкция break прервет выполнение цикла, когда i будет равно 74. Обычно, вы будете использовать break как здесь, если вы не будете знать когда возникнет прерывающее условие. Инструкция continue влечет за собой возврат к началу цикла (при этом инкрементируя i) в любом случае, когда i не делится на 9 без остатка. Если это так, значение печатается.

Второй раздел показывает “бесконечный цикл”, который, теоретически, никогда не закончится. Однако, внутри цикла есть инструкция break, которая оборвет цикл. Дополнительно, вы увидите, что continue возвращает назад к началу цикла не завершив оставшегося. (Таким образом печать происходит во втором цикле только когда значение i делится на 10.) Вот результаты:

0 9 18 27 36 45 54 63 72 10 20 30 40

Значение 0 печатается, потому что 0 % 9 равно 0.

Вторая форма бесконечного цикла: for(;;). Компилятор трактует и while(true) и for(;;) одинаково, что бы вы не использовали - это вопрос стиля программирования.



Буфер обмена


JFC поддерживает ограниченное число операций с системным буфером обмена (в пакете java.awt.datatransfer). Вы можете скопировать объект String в буфер обмена как текст, и вы можете вставить текст из буфера обмена в объект String. Конечно, буфер обмена предназначен для хранения данных любого типа, но как представить эти данные в буфере обмена, если программа выполняет вырезание и вставку. Java API для буфера обмена обеспечивает расширяющуюся концепцию “особенностей”. Когда данные приходят из буфера обмена, они имеют множество особенностей, которыми они могут быть представлены (например, графика может быть представлена как строка чисел или как изображение) и вы можете видеть, поддерживает ли определенный буфер интересующие вас особенности.

Следующая программа просто демонстрирует вырезание, копирование и вставку данных String в JTextArea. Одно вы должны заметить, это то, что последовательность кнопок, которую вы обычно используете для вырезания, копирования и вставки так же работает. Но если вы посмотрите на JTextField или JTextArea в любой другой программе, вы обнаружите, что они тоже автоматически поддерживают последовательность клавиш для буфера обмена. Этот пример просто добавляет программное управление буфером обмена, и вы можете использовать эту технику, если хотите захватывать текст из буфера обмена и вставлять во что-то другое, чем JTextComponent.

//: c13:CutAndPaste.java

// Использование буфера обмена.

import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.awt.datatransfer.*; import com.bruceeckel.swing.*;

public class CutAndPaste extends JFrame { JMenuBar mb = new JMenuBar(); JMenu edit = new JMenu("Edit"); JMenuItem cut = new JMenuItem("Cut"), copy = new JMenuItem("Copy"), paste = new JMenuItem("Paste"); JTextArea text = new JTextArea(20, 20); Clipboard clipbd = getToolkit().getSystemClipboard(); public CutAndPaste() { cut.addActionListener(new CutL()); copy.addActionListener(new CopyL()); paste.addActionListener(new PasteL()); edit.add(cut); edit.add(copy); edit.add(paste); mb.add(edit); setJMenuBar(mb); getContentPane().add(text); } class CopyL implements ActionListener { public void actionPerformed(ActionEvent e) { String selection = text.getSelectedText(); if (selection == null) return; StringSelection clipString = new StringSelection(selection); clipbd.setContents(clipString,clipString); } } class CutL implements ActionListener { public void actionPerformed(ActionEvent e) { String selection = text.getSelectedText(); if (selection == null) return; StringSelection clipString = new StringSelection(selection); clipbd.setContents(clipString, clipString); text.replaceRange("", text.getSelectionStart(), text.getSelectionEnd()); } } class PasteL implements ActionListener { public void actionPerformed(ActionEvent e) { Transferable clipData = clipbd.getContents(CutAndPaste.this); try { String clipString = (String)clipData. getTransferData( DataFlavor.stringFlavor); text.replaceRange(clipString, text.getSelectionStart(), text.getSelectionEnd()); } catch(Exception ex) { System.err.println("Not String flavor"); } } } public static void main(String[] args) { Console.run(new CutAndPaste(), 300, 200); } } ///:~


Создание и добавление меню и JTextArea должно теперь выглядеть прозаическим действием. Что отличается, так это создание поля Clipboard clipbd, которое выполняется через Toolkit.

Все действия происходят в слушателях. Слушатели CopyL и CutL одинаковы, за исключением того, что последняя строка в CutL стирает скопированную строку. Специальные две строки создания объекта StringSelection из String вызывают setContents( ) с этим StringSelection. Это все, что нужно для помещения String в буфер обмена.

В PasteL данные вытягиваются из буфера обмена, используя getContents( ). Что приходит - это совершенно анонимный объект Transferable, который возвращает массив объектов DataFlavor, указывает, какие особенности поддерживаются определенным объектом. Вы можете так же спросить его напрямую с помощью isDataFlavorSupported( ), передав особенность, которая вас интересует. Однако здесь выбран самонадеянный подход: вызывается getTransferData( ) в надежде, что содержимое поддерживает особенности String, а если это не так, проблема выявляется в обработчике исключения.

В будущем вы можете ожидать поддержку большего числа особенностей.


C: Руководящие принципы программирования на Java


Это приложение содержит предложения призванные помочь вам в осуществлении низкоуровневой проектировки программных продуктов и последующего написания кода.

В действительности же, это только принципы, но не правила. Идея заключается в использовании заложенных в них идей и при этом следует не забывать об исключениях из правил, когда нужно отбросить или изменить какое-то правило.



Cборщики и итераторы


Если вы не знаете, сколько объектов вам будет необходимо для решения проблемы, или сколько долго они будут существовать, то вы также не знаете, как хранить эти объекты. Откуда вы можете знать, сколько пространства необходимо для этих объектов? Вы не можете, так как эта информация не известна, пока не настанет время выполнения.

Решения большинства проблем в объектно-ориентированном дизайне выглядит дерзко: вы создаете типы других объектов. Новый тип объектов, который решает эту обычную проблему хранения ссылок на другие объекты. Конечно, вы можете сделать то же самое с помощью массива, который поддерживают многие языки. Но это гораздо большее. Этот новый объект, обычно называемый контейнер (также называется коллекцией, но библиотека Java использует этот термин в другом смысле, так что эта книга будет использовать “контейнер”), будет расширять себя при необходимости, чтобы аккомодировать все, что вы поместите внутрь него. Так что вы не должны знать, сколько объектов вы положили храниться в контейнер. Просто создайте объект контейнера и дайте ему заботится о деталях.

К счастью, хорошие языки ООП пришли с набором контейнеров, как часть пакета. В C++ это часть Стандартной Библиотеки C++, часто называемой Библиотекой Стандартных Шаблонов [Standard Template Library (STL)]. Object Pascal имеет контейнеры в Библиотеке Визуальных Компонент [Visual Component Library (VCL)]. Smalltalk имеет более полный набор контейнеров. Java также имеет контейнеры в своей стандартной библиотеке. В некоторых библиотеках общие контейнеры достаточно хороши для всех надобностей, а в других (например, в Java) библиотека имеет различные типы контейнеров для разных надобностей: вектор (называемый ArrayList в Java) для последовательного доступа ко всем элементам, и связанный список для последовательной вставки в любое место, например, вы можете выбрать обычный тип, который удовлетворяет вашим требованиям. Библиотеки контейнеров могут также включать наборы, очереди, хэш-таблицы, деревья, стеки и т.п.


Все контейнеры имеют определенный способ для помещения вещей внутрь и получения вещей назад. Обычно это функции для добавления элемента в контейнер, а другая - для извлечения этого элемента. Но получение элемента может стать более пробламетично, поскольку функция простого выбора ограничена. Что если вы захотите манипулировать или сравнивать набор элементов в контейнере не по одному?

Решением этого является итератор, который является объектом, чья работа - это выбор элементов из контейнера и представление их пользователю итератора. Как класс, он также обеспечивает уровень абстракции. Эта абстракция может быть использована для разделения деталей контейнера от кода, который имеет доступ к контейнеру. Контейнер через итератор абстрагируется до простой последовательности. Итератор позволяет вам обработать эту последовательность, не заботясь о лежащей в основе структуре — является ли она ArrayList, LinkedList, Stack или чем-то еще. Это дает вам гибкость для легкой смены лежащей в основе структуры данных без переделки кода вашей программы. Java началась (в версиях 1.0 и 1.1) со стандартного итератора, называемого Enumeration, для всех контейнерных классов. В Java 2 добавлено много более сложных библиотек контейнеров, которые содержат итераторы, называемые Iterator, который делает много больше, чем старый Enumeration.

С точки зрения разработки, все что вам нужно - это последовательность, которой можно манипулировать для решения вашей проблемы. Если последовательность простых типов удовлетворяет всем вашим требованиям, то нет смысла иметь другие типы. Есть два довода, почему вам необходимы контейнеры. Во-первых, контейнеры обеспечивают различные типы интерфейсов и внешних воздействий. Стек имеет различные интерфейсы и поведение, в отличие от очереди, которая отличается от набора или списка. Один из них может обеспечивать более гибкое решение вашей проблемы, чем другие. Во-вторых, различные контейнеры имеют различную эффективность для определенных операций. Лучший пример - это ArrayList и LinkedList. Оба контейнера - это простая последовательность, которые могут иметь одинаковые интерфейсы и внешнее поведение. Но определенные операции могут иметь радикальное отличие в стоимости. Случайный доступ к элементам в ArrayList - это операция постоянная по времени. Она занимает примерно одно и то же время не зависимо от выбираемого вами элемента. Однако в LinkedList очень дорого перемещаться по списку элементов в случайном порядке, и занимает больше времени при нахождении элемента, чем при переборе их вниз по списку. С другой стороны, если вы хотите вставить элемент в середину последовательности, это дешевле в LinkedList, чем в ArrayList. Есть и другие операции, имеющие различия в эффективности, в зависимости от лежащей в основе структуры последовательности. В фазе разработки вы можете начать с LinkedList и, когда будете настраивать производительность, сменить на ArrayList. Поскольку есть абстракция через итераторы, вы можете сменить один на другой с минимальной переработкой вашего кода.

В завершение, помните, что контейнеры только хранилище для помещения объектов. Если такое хранилище решает все ваши проблемы, не имеет значение как оно реализовано (основная концепция для большинства типов объектов). Если вы работаете со средой программирования, имеющей встроенную возможность, обусловленную другими факторами, то стоимость различается для ArrayList и LinkedList может не иметь значения. Вам может понадобиться только один из типов последовательности. Вы можете вообразить “совершенный” абстрактный контейнер, который может автоматически изменять лежащую в основе реализацию в зависимости от способа его использования.


Цели


Так же как и предыдущая книга Думай на С++, эта книга структурирована вокруг процесса преподавания языка. В частности, моей мотивацией было создание чего-либо, что обеспечивало бы мне способ обучать языку на семинарах. Когда я думал о главах этой книги, я думал в терминах того, что позволило бы сделать лучший урок семинара. Моей целью было предоставить небольшой объем материала, который можно понять за приемлемый промежуток времени, выполняя примеры как и на лекциях. Поэтому цели  этой книги:

Представить определенный объем материала так, чтобы вы могли легко усвоить его прежде чем перейдете к следующему.

Использовать как можно короткие и простые примеры. Иногда это не позволяет разобрать "реальные" задачи, но я нашел, что начинающие обычно больше рады тому, что они могут понять каждую строку кода, вместо того чтобы поражаться обилием задач, которые данный кусок кода решает. Также существует строгое ограничение на объем кода, который может быть усвоен за одну лекцию. Возможно из-за этого кто-то будет критиковать меня в использовании "игрушечных" примеров, но я не отношусь к этому предвзято, так как я делаю что-то более полезное с педагогической точки зрения.

Внимательно следовать описанию, чтобы вам потом не пришлось столкнуться с тем, что вы что-то пропустили. Это конечно не всегда возможно, и в таких случаях дается краткое введение.

Давать вам только то, что я считаю нужным для понимания языка, а не все подряд что знаю я. Уверен, что существует определенная иерархия в важности информации, и некоторые факты, которые 95 процентам программистов просто не нужны, вводят большинство в заблуждение, приводя к мнению о сложности языка. Возьмем в качестве примера С, если вы запомнили таблицу последовательности выполнения операторов (я ее не помню), вы можете написать замечательный код. Но если необходимо постоянно думать об этом то это может запутать того кто читает или разрабатывает этот код. Поэтому лучше использовать скобки если что-то не понятно, чем помнить таблицу.

Давать каждую главу достаточно сжато с тем, чтобы время лекции, и время между выполнением примеров, было меньше. Это не только позволяет аудитории быть более активной и увлеченной во время практических работ, но также дает читателю чувство завершенности.

Дать вам фундамент, чтобы вы могли достаточно хорошо понять текущую главу и перейти к следующей, более сложной.



CheckBox-элементы


CheckBox-элемент обеспечивает способ создания единственного выбора включения/выключения; он состоит из небольшого прямоугольника и метки. Прямоугольник обычно содержит небольшой “x” (или какой-то другой индикатор того, что он установлен) или остается пустым, в зависимости от того, был ли он выбран.

Обычно вы будете создавать JCheckBox, используя конструктор, который получает метку в качестве аргумента. Вы можете установить и получить состояние и установить метку, если хотите прочесть или изменить ее после создания JCheckBox.

Независимо от того, где JCheckBox установлен или создан, происходят события, которые вы можете собирать тем же способом, что и для кнопки, используя ActionListener. Следующий пример использует JTextArea, чтобы убедится, что на всех checkBox-элементах произведен щелчок мышкой:

//: c13:CheckBoxes.java

// Использование JCheckBoxes.

// <applet code=CheckBoxes width=200 height=200>

// </applet>

import javax.swing.*; import java.awt.event.*; import java.awt.*; import com.bruceeckel.swing.*;

public class CheckBoxes extends JApplet { JTextArea t = new JTextArea(6, 15); JCheckBox cb1 = new JCheckBox("Check Box 1"), cb2 = new JCheckBox("Check Box 2"), cb3 = new JCheckBox("Check Box 3"); public void init() { cb1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e){ trace("1", cb1); } }); cb2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e){ trace("2", cb2); } }); cb3.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e){ trace("3", cb3); } }); Container cp = getContentPane(); cp.setLayout(new FlowLayout()); cp.add(new JScrollPane(t)); cp.add(cb1); cp.add(cb2); cp.add(cb3); } void trace(String b, JCheckBox cb) { if(cb.isSelected()) t.append("Box " + b + " Set\n"); else

t.append("Box " + b + " Cleared\n"); } public static void main(String[] args) { Console.run(new CheckBoxes(), 200, 200); } } ///:~

Метод trace( ) посылает имя выделенного JCheckBox и его текущего состояния в JTextArea, используя append( ), так что вы увидите совокупный список checkbox-элементов и их состояния.



Числа высокой точности


Java включает два класса для работы с высокоточной арифметикой: BigInteger и BigDecimal. Хотя они приблизительно попадают в ту же категорию, что и классы “оболочки”, ни один из них не имеет примитивного аналога.

Оба класса имеют методы, обеспечивающие аналогичные операции, которые вы выполняете для примитивных типов. Так что с классами BigInteger или BigDecimal вы можете делать все, что вы можете делать с int или float, только вы должны использовать вызов методов вместо операторов. Также, так как это более закручено, операции выполняются медленнее. Вы меняете скорость на точность.

BigInteger поддерживают целые числа произвольной точности. Это означает, что вы можете точно представить значение целого числа любого размера без потерь любой информации во время операций.

BigDecimal для чисел с фиксированной точкой произвольной точности; вы можете использовать это, например, для точных денежных расчетов.

Обратитесь к вашей онлайн документации, чтобы узнать более детально относительно конструкторов и методов, которые вы можете вызывать для этих двух классов.



Чистое наследование против расширения


Во время изучения наследования, всегда преподается наиболее понятный, чистый путь для создания диаграммы наследования, т.н. "чистое" наследование. При этом существуют только методы в интерфейсе базового класса, которые переопределяются в дочерних классах, как и показано на диаграмме:


Это так называемая чистая "is-a" связь, поскольку интерфейс класса определяет, что же это есть на самом деле. Наследование гарантирует, что любой дочерний класс будет иметь тот же интерфейс (т.е. не меньше его) как и у базового класса и ничего более. Если Вы последуете представленной диаграмме, то можете увидеть, что дочерние классы так же имеют интерфейс не больший, чем у базового.

Это разновидность так называемой чистой замены, поскольку объекты дочернего класса могут быть чудным образом заменены для базового класса и вам не нужно знать никакой дополнительной информации о подклассах, когда Вы их будете использовать:


Так и получается, базовый класс может принимать любые сообщения, которые Вы можете посылать дочернему, поскольку они оба имеют тот же самый интерфейс. Все, что вам нужно сделать это привести к базовому типу от дочернего класса и больше не смотреть, с каким именно типом объекта Вы имеете дело. Все обрабатывается средствами полиморфизма.

Когда Вы видите такой путь, то это означает, что используются чистые связи "is-a", при этом такой подход является единственным и любой другой дизайн сигнализирует о запутанном обдумывании и по определению кривому восприятию кода. Вот и попались Вы в ловушку. Как только Вы начали думать в этом направлении, развернитесь и откройте для себя расширение интерфейса (которое к несчастью подстрекается ключевым словом extends) являющегося лучшим решением частной проблемы. Такой подход называется "is-like-a" (это похоже на то) связью, поскольку дочерний класс похож на базовый класс, из-за того, что они имеют один и тот же фундаментальный интерфейс, но они имеют различные особенности, которые требуют дополнительных методов для своей реализации:


У такого удобного и разумного подхода (в зависимости от ситуации) имеется и небольшой недостаток. Расширенная часть интерфейса дочернего класса не доступна из базового класса, так что при приведении к базовому типу Вы не можете получить доступ к этим новым методам:


Если же Вы не приводите к базовому классу, об этом не стоит беспокоится, но зачастую вам необходимо получить точный тип объекта, так что Вы в последствии можете расширить методы этого типа. Следующая секция показывает, как это можно провернуть.



Читающие и пишущие


В Java 1.1 сделаны некоторые значительные модификации в фундаментальной библиотеке потоков ввода/вывода (однако Java 2 не внесла фундаментальных модификаций). Когда вы видите классы Reader и Writer, вы сначала можете подумать (как и я), что они предназначены для замены классов InputStream и OutputStream. Но не в этом случае. Хотя некоторые аспекты начальной библиотеки потоков устарели и были заменены (если вы используете их, вы должны получать предупреждение компилятора), классы InputStream и OutputStream все еще обеспечивают ценную функциональность в форме байт-ориентированных систем ввода/вывода, в то время как классы Reader и Writer обеспечивают Unicode-совместимый, символьно ориентированный ввод/вывод. Кроме того:

Java 1.1 добавил новые классы в иерархию InputStream и OutputStream, так что, очевидно, что эти классы не заменены. Иногда возникают ситуации, когда вы должны использовать классы из “byte” иерархии в комбинации с классами в “символьной” иерархии. Чтобы выполнить это, существуют классы - “мосты”: InputStreamReader преобразует InputStream к Reader, и OutputStreamWriter преобразует OutputStream к Writer.

Наиболее важная причина во введении иерархии Reader и Writer состоит в интернационализации. Старая иерархия потоков ввода/вывода поддерживает только 8-битные байтовые потоки и не обрабатывает 16 битные Unicode символы. Так как Unicode используется для интернационализации (и родной тип char в Java - это 16-bit Unicode), иерархия Reader и Writer были добавлены для поддержки Unicode и всех операций ввода/вывода. Кроме того, новые библиотеки были разработаны для ускорения операций по сравнению со старыми.

Как практикуется в этой книге, я попробую выполнить обзор классов, но вы должны принять во внимание, что вы будете использовать онлайн документацию для определения всех деталей, таких как исчерпывающий список методов.



Чтение файла с сервера


Вариация приведенной выше программы читает файл, расположенный на сервере. В этом случае, файл определяется клиентом:

//: c15:Fetcher.java

// <applet code=Fetcher width=500 height=300>

// </applet>

import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.net.*; import java.io.*; import com.bruceeckel.swing.*;

public class Fetcher extends JApplet { JButton fetchIt= new JButton("Fetch the Data"); JTextField f = new JTextField("Fetcher.java", 20); JTextArea t = new JTextArea(10,40); public void init() { Container cp = getContentPane(); cp.setLayout(new FlowLayout()); fetchIt.addActionListener(new FetchL()); cp.add(new JScrollPane(t)); cp.add(f); cp.add(fetchIt); } public class FetchL implements ActionListener { public void actionPerformed(ActionEvent e) { try { URL url = new URL(getDocumentBase(), f.getText()); t.setText(url + "\n"); InputStream is = url.openStream(); BufferedReader in = new BufferedReader( new InputStreamReader(is)); String line; while ((line = in.readLine()) != null) t.append(line + "\n"); } catch(Exception ex) { t.append(ex.toString()); } } } public static void main(String[] args) { Console.run(new Fetcher(), 500, 300); } } ///:~

Создание объекта URL сходно с пердыдущим примером —getDocumentBase( ) - стартовая позиция как и раньше, но в это т раз имя файла читается из поля JTextField. Как только объект URL создан, его строковая версия отображается в JTextArea так что мы видим, как она выглядит. Затем создается InputStream из URL, который в этом случае легко создает поток символов в файле. После конвертирования в Reader и буферизации, читается каждая строка и добавляется в JTextArea. Обратите внимание, что JTextArea было помещено внутрь JScrollPane так что прокрутка происходит автоматически.



Чтение и установка приоритетов


Можно определить приоритет процесса с помощью getPriority( ) и изменить его 

setPriority( ). Форму предыдущих примеров счетчиков "counter" можно использовать для демонстрации эффекта изменния приоритетов. В данном апплете можно видеть как счетчик замедляется по мере того, как его процесс получает низший приоритет:

//: c14:Counter5.java

// Adjusting the priorities of threads.

// <applet code=Counter5 width=450 height=600>

// </applet>

import javax.swing.*; import java.awt.*; import java.awt.event.*; import com.bruceeckel.swing.*;

class Ticker2 extends Thread { private JButton b = new JButton("Toggle"), incPriority = new JButton("up"), decPriority = new JButton("down"); private JTextField t = new JTextField(10), pr = new JTextField(3); // Display priority

private int count = 0; private boolean runFlag = true; public Ticker2(Container c) { b.addActionListener(new ToggleL()); incPriority.addActionListener(new UpL()); decPriority.addActionListener(new DownL()); JPanel p = new JPanel(); p.add(t); p.add(pr); p.add(b); p.add(incPriority); p.add(decPriority); c.add(p); } class ToggleL implements ActionListener { public void actionPerformed(ActionEvent e) { runFlag = !runFlag; } } class UpL implements ActionListener { public void actionPerformed(ActionEvent e) { int newPriority = getPriority() + 1; if(newPriority > Thread.MAX_PRIORITY) newPriority = Thread.MAX_PRIORITY; setPriority(newPriority); } } class DownL implements ActionListener { public void actionPerformed(ActionEvent e) { int newPriority = getPriority() - 1; if(newPriority < Thread.MIN_PRIORITY) newPriority = Thread.MIN_PRIORITY; setPriority(newPriority); } } public void run() { while (true) { if(runFlag) { t.setText(Integer.toString(count++)); pr.setText( Integer.toString(getPriority())); } yield(); } } }

public class Counter5 extends JApplet { private JButton start = new JButton("Start"), upMax = new JButton("Inc Max Priority"), downMax = new JButton("Dec Max Priority"); private boolean started = false; private static final int SIZE = 10; private Ticker2[] s = new Ticker2[SIZE]; private JTextField mp = new JTextField(3); public void init() { Container cp = getContentPane(); cp.setLayout(new FlowLayout()); for(int i = 0; i < s.length; i++) s[i] = new Ticker2(cp); cp.add(new JLabel( "MAX_PRIORITY = " + Thread.MAX_PRIORITY)); cp.add(new JLabel("MIN_PRIORITY = "


+ Thread.MIN_PRIORITY)); cp.add(new JLabel("Group Max Priority = ")); cp.add(mp); cp.add(start); cp.add(upMax); cp.add(downMax); start.addActionListener(new StartL()); upMax.addActionListener(new UpMaxL()); downMax.addActionListener(new DownMaxL()); showMaxPriority(); // Recursively display parent thread groups:

ThreadGroup parent = s[0].getThreadGroup().getParent(); while(parent != null) { cp.add(new Label( "Parent threadgroup max priority = "

+ parent.getMaxPriority())); parent = parent.getParent(); } } public void showMaxPriority() { mp.setText(Integer.toString( s[0].getThreadGroup().getMaxPriority())); } class StartL implements ActionListener { public void actionPerformed(ActionEvent e) { if(!started) { started = true; for(int i = 0; i < s.length; i++) s[i].start(); } } } class UpMaxL implements ActionListener { public void actionPerformed(ActionEvent e) { int maxp = s[0].getThreadGroup().getMaxPriority(); if(++maxp > Thread.MAX_PRIORITY) maxp = Thread.MAX_PRIORITY; s[0].getThreadGroup().setMaxPriority(maxp); showMaxPriority(); } } class DownMaxL implements ActionListener { public void actionPerformed(ActionEvent e) { int maxp = s[0].getThreadGroup().getMaxPriority(); if(--maxp < Thread.MIN_PRIORITY) maxp = Thread.MIN_PRIORITY; s[0].getThreadGroup().setMaxPriority(maxp); showMaxPriority(); } } public static void main(String[] args) { Console.run(new Counter5(), 450, 600); } } ///:~

Ticker2 следует установленной раннее в данной главе форме, но здесь есть дополнительное JTextField для отображения приоритета процесса и две дополнительные кнопки для увеличения и уменьшения значения приоритета.

Также обратите внимание на использование yield() добровольно отдающему блок управления планировщику. Без этого механизм с множеством процессов также будет работать, но вы заметите, что все будет выполняться медленнее (удалите вызов yield( ) чтобы убедиться в этом). Можно также вызывать sleep( ), но тогда значение счетчика будет определяться продолжительностью задержки заданной при вызове sleep( ), а не приоритетом процесса.



Метод init( ) в Counter5 создает массив из десяти Ticker2, их кнопки и поля ввода размещаются на форме конструктором Ticker2. Counter5 добавляет кнопки для общего запуска, а также кнопки для увеличения и уменьшения максимального значения приоритета для группы процессов. Добавочно существуют строки (label), для отображения возможных максимальных и минимальных значений приоритетов для процесса и JTextField, для отображения максимального приоритета для группы (мы рассмотрим группу процессов в следующем разделе). В заключении всего, приоритеты групп процессов потомков также отображаются как строки (labels).

Когда нажимается кнопка "up" или "down", то выбирается приоритет этого Ticker2 и он, соответственно, увеличивается или уменьшается.

Когда вы запустите эту программу, обратите внимание на несколько вещей. Во-первых, приоритет по умолчанию для группы  равен пяти. Даже если вы уменьшите максимальное значение приоритета ниже пяти перед запуском процессов (или перед созданием процессов, что потребует изменение кода), каждый процесс будет иметь приоритет по умолчанию равный пяти.

Простая проверка - взять один из счетчиков и уменьшать значение его приоритета на единицу, и наблюдать как он подсчитывается более медленнее.  А теперь попробуйте увеличит его снова. Вы можете его вернуть до уровня приорета группы процессов, но не более. Теперь уменьшим приоритет группы на некоторое время. Приоритет процессов не изменился, но если попытаться опять увеличить или уменьшить его то видно, что они автоматически принимают значение приоритета группы. Также новым процессам будет установлено значение приоритета по умолчанию, даже если оно больше значение приоритета для группы. (Таким образом приоритет для группы не мешает новым процессам получить большее значение приоритета по сравнению с существующим).

В конце, попробуйте увеличить максимальный приоритет для группы. Это не возможно сделать. Можно только уменьшить максимальное значение приоритета для группы, но не увеличить его.


Чтение из InputStream с помощью FilterInputStream


Классы FilterInputStream совершают две значительные вещи. DataInputStream позволяет вам читать различные типы примитивных данных, наряду с объектами типа String. (Все методы начинаются со слова “read”, например: readByte( ), readFloat( ), и т.п.) Таким образом, наряду со своим компаньоном DataOutputStream, это позволяет вам перемещать примитивные данные из одного места в другое через поток. Эти “места” определяются классами в таблице 11-1.

Оставшиеся классы изменяют способ внутреннего поведения InputStream: будет ли он буферизированный или нет, будет ли он хранить историю прочитанных строк (позволяя вам спрашивать номер строки или множества номеров строк), и сможете ли вы поместить назад единичный символ. Последние два класса выглядят так, как будто они предназначены для поддержки работы компилятора (то есть, они были добавлены для поддержки конструкций Java компилятора), так что вы, вероятно, не захотите использовать их в обычном программировании.

Вероятно, вам необходимо будет буферизировать ваш ввод почти каждый раз, в зависимости от устройства ввода/вывода, к которому вы подсоединяетесь, так что имеет больше смысла для библиотеки ввода/вывода сделать особый случай (или простой вызов метода) для не буферизированного ввода, в отличие от буферизированного ввода.

Таблица 11-3. Типы FilterInputStream

Класс

Функция

Аргументы конструктора

Как его использовать

Data-InputStream Используется в согласии с DataOutputStream, так что вы можете читать примитивные типы (int, char, long, и т.п.) из потока портативным способом. InputStream
Содержит полный интерфейс, чтобы позволить вам читать примитивные типы.
Buffered-InputStream Используйте это для предотвращения физического чтения каждый раз, когда вам необходимы дополнительные данные. Вы говорить “Использовать буфер”. InputStream с необязательным размером буфера.
Сам по себе не обеспечивает интерфейс, просто требует, чтобы использовался буфер. Присоединяет объект интерфейса.
LineNumber-InputStream Сохраняет историю номеров строк входного потока; вы можете вызвать getLineNumber( ) и setLineNumber(

int).

InputStream
Это просто добавляет нумерацию строк, так что вы, вероятно, присоедините объект интерфейса.
Pushback-InputStream Имеет буфер для возврата одного символа, так что вы можете поместить обратно один прочитанный символ. InputStream
Обычно используется в сканерах для компилятора и, вероятно, включено потому, что Java компилятор нуждается в нем. Вы, вероятно, не захотите использовать его.



Чтение из стандартного ввода


Стандартная модель ввода/вывода в Java имеет System.in, System.out и System.err. На протяжении всей этой книге вы видели, как писать в стандартный вывод, используя System.out, который представляет собой объект PrintStream. System.err аналогичен PrintStream, а System.in является производной InputStream без каких-либо включений. Это означает, что в то время, когда вы можете использовать System.out и System.err как они есть, System.in должен куда-то включаться (быть обернут), прежде, чем вы сможете прочесть из него.

Обычно вы захотите читать ввод построчно, используя readLine( ), так что вы захотите поместить System.in в BufferedReader. Чтобы сделать это, вы можете конвертировать System.in в Reader, используя InputStreamReader. Вот пример, который просто повторяет каждую строку, которую вы печатаете:

//: c11:Echo.java

// Как читать стандартный ввод.

import java.io.*;

public class Echo { public static void main(String[] args) throws IOException { BufferedReader in = new BufferedReader( new InputStreamReader(System.in)); String s; while((s = in.readLine()).length() != 0) System.out.println(s); // Пустая строка прерывает выполнение программы

} } ///:~

Причина указания исключения в том, что readLine( ) может выбросить IOException. Обратите внимание, что System.in обычно должен быть буферизирован, как и большинство потоков.



Что такое Jini?


Jini - это набор API и сетевых протоколов, которые могут помочь вам построить и развернуть распределенную систему, организованную, как федерация сервисов. Сервисы могут быть всем, что сидит в сети и готово выполнить полезную функцию. Аппаратные устройства, программы, каналы связи — даже сами пользователи - люди — могут быть сервисами. Например Jini-совместимые дисководы могут предлагать сервис “хранения”. Jini-совместимые принтеры могут прелагать сервис “распечатки”. Таким образом, федерация служб является набором сервисов, доступных в сети в данный момент, которыми могут воспользываться клиенты (под клиентами подразумевается программы, службы или пользователи) для достижения некоторой цели.

Для выполнения задачи клиенты привлекают на помощь сервисы. Например, клиентские программы могут загружать кртинки из хранилища изображений цифровой камеры, передавать картинки службе постоянного хранения, предаставляемой дисководом, и посылать страницу эскизов фотографий различных размеров для распечатки на цветном принтере. В этом примере клиентская программа построена, как распределенная система, содержащая в себе сервис хранения изображений, сервис постоянного хранения и сервис цветной печати. Клиенты и сервисы этой распределеной системы работают совместно для выполнения задач: выгрузки хранимых изображений из цифровой камеры и распечатка страниц эскизов.

Идея, стоящая за миром федерации, состоит в том, что Jini просматривает сеть и не вовлекает центр управления. Поскольку не один из сервисов не загружается, набор всех сервисов доступен в сети из федерации — группы, состоящей из равных элементов. Вместо централной власти, инфраструктура Jini времени выполнения просто обеспечивает способ для клиентов и сервисов находить друг друга (через службу поиска, которая хранит справочник поддерживаемых в настоящее время сервисов). После того, как услуги найдут друг друга, они становятся собственными. Клиент и привлеченные им сервисы выполняют свою задачу независимо от инфраструктуры Jini времени выполнения. Если служба поиска Jini потерпит крушение, любая распределенная система, собранная через сервис поиска до его падения, продолжит свою работу. Jini даже включает сетевой протокол, который могут использовать клиенты для нахождения служб при отсутствии службы поиска.



Что такое компонент (Bean)?


После того, как осядет пыль, компонент представляет собой блок кода, обычно, заключенного в класс. Ключевая способность построителя приложения состоит в обнаружении свойств и событий компонента. Для создания VB компонент программист должен написать довольно сложный кусок кода, следуя определенным соглашениям для выделения свойств и событий. Delphi был визуальным инструментом программирования второго поколения, и дизайн языка был построен исходя из простоты построения и использования визуальных компонент. Однако Java стал использовать создание визуальных компонент наиболее продвинутым способом с помощью JavaBeans, потому что компонент (Bean) - это просто класс. Вам не нужно писать дополнительный код или использовать специальное расширение языка для создания какого-нибудь компонента. Фактически, вам необходимо сделать только одну вещь: слегка модифицировать способ, которым вы создаете названия методов. То есть использовать имя метода, которое скажет построителю приложения, является ли он свойством, событием или просто обычным методом.

В документации по Java это соглашение об именах ошибочно называется “шаблон разработки (design pattern)”. Это не удачно, так как шаблоны разработки (смотрите Thinking in Patterns with Java, доступной на www.BruceEckel.com) и так оспариваются и без этой путаницы. Это не шаблоны разработки, это просто соглашение об именах и оно достаточно простое:

Для свойства с именем xxx вы обычно создаете два метода: getXxx( ) и setXxx( ). Обратите внимание, что первая буква после “get” или “set” автоматически преобразуется к нижнему регистру для получения имени свойства. Тип, производимый методом “get” должен быть тем же самым, что и тип аргумента в методе “set”. Имя свойcтва и тип для “get” и “set” не связаны. Для свойства типа boolean вы можете использовать подход для “get” и “set”, описанный выше, но вы также можете использовать “is” вместо “get”.

Обычные методы компонента (Bean) не удовлетворяют соглашению об именах, но они публичные (public).


Для событий вы используете подход “слушателей” из Swing. Он точно такой же, как вы видели: addFooBarListener(FooBarListener) и removeFooBarListener(FooBarListener) для обработки FooBarEvent. Чаше всего для ваших нужд подойдут встроенные события и слушатели, но вы можете также создать свои собственные события и интерфейсы слушателей.

Пункт 1 отвечает на вопрос о том, что вы могли заметить, когда просматривали код в старом стиле и код в новом стиле: число имен методов стало меньше, и иметь явно осмысленный характер. Теперь вы видите, что большинство из этих изменений были сделаны для адаптации к соглашению об именах в отношении “get” и “set”, чтобы встроить определенный компонент в Bean.

Мы может использовать это руководство для создания простого компонента (Bean):

//: frogbean:Frog.java

// Тривиальный JavaBean.

package frogbean; import java.awt.*; import java.awt.event.*;

class Spots {}

public class Frog { private int jumps; private Color color; private Spots spots; private boolean jmpr; public int getJumps() { return jumps; } public void setJumps(int newJumps) { jumps = newJumps; } public Color getColor() { return color; } public void setColor(Color newColor) { color = newColor; } public Spots getSpots() { return spots; } public void setSpots(Spots newSpots) { spots = newSpots; } public boolean isJumper() { return jmpr; } public void setJumper(boolean j) { jmpr = j; } public void addActionListener( ActionListener l) { //...

} public void removeActionListener( ActionListener l) { // ...

} public void addKeyListener(KeyListener l) { // ...

} public void removeKeyListener(KeyListener l) { // ...

} // "Обычный" публичный метод:

public void croak() { System.out.println("Ribbet!"); } } ///:~

Прежде всего, вы можете видеть, что это просто класс. Обычно все ваши поля будут private, и доступны только через методы. Следуя соглашению об именах, получим свойства jumps, color, spots и jumper (обратите внимание на регистр первой буквы имени свойства). Хотя имя внутреннего идентификатора такое же, как и имя свойства в первых трех случаях, в jumper вы можете видеть, что имя свойства не ограничивает вас в использовании определенного идентификатора для внутренней переменной (или, на самом деле, даже иметь любые внутренние переменные для этого свойства).

События, обрабатываемые этим компонентом, это ActionEvent и KeyEvent, основываются на наименовании методов “add” и “remove” для ассоциированного слушателя. И, наконец, вы можете видеть, что обычный метод croak( ) все еще является частью компонента, потому что это public метод, а не потому, что он удовлетворяет какой-то схеме названий.


Что такое Web?


Сначала Web может показаться немного мистическим, со всеми этими разговорами о “серфинге”, “присутствии” и “домашних страницах”. Была даже нарастающая реакция против “Internet-омании”, подвергающая сомнению экономическую ценность и результативность такого широкого движения. Полезно вернуться назад и посмотреть, что есть реально, но чтобы сделать это вы должны понимать системы клиент/сервер и другие вычислительные аспекты, которые полны запутанных проблем.



CORBA


В огромных распределенных приложениях вы можете быть неудовлетворены изложенными выше подходами. Например, вам может понадобиться интерфейс с унаследованным хранилищем данных, или вам может понадобиться услуга от сервера объектов не зависимо от его физического расположения. Такие ситуации требуют некоторого рода Процедуры Удаленного Вызова (RPC), и, возможно, независимости от языка. Здесь может помочь CORBA.

CORBA - это не особенность языка, это технология интеграции. Это спецификация, которой могут следовать производители для реализации CORBA-совместимых интегрированных продуктов. CORBA - это одна из попыток OMG определить рабочее пространство для распределенных, независящих от языка способностей объекта.

CORBA предлагает возможность создания процедуры удаленного вызова Java объектов и не Java объектов для взаимодействия с системой наследования независящим от расположения способом. Java добавляет поддержку сети и великолепный объектно-оиентированный язык для построения графических и не графических приложений. Модельные карты объектов Java и OMG дополняют друг друга, например, и Java, и CORBA реализуют концепцию интерфейсов и модель ссылок на объекты.



CORBA и RMI/IIOP


Спецификация EJB определяет взаимодействие с CORBA через совместимость с CORBA протоколами. Это достигнуто путем совмещения EJB служб, таких как JTS и JNDI, с соотвествующими службами CORBA и реализацией RMI поверх IIOP протокола CORBA.

Использование CORBA и RMI/IIOP в Enterprise JavaBeans реализовано в EJB Контейнере и за это отвечает поставщик EJB Котейнера. Использование CORBA и RMI/IIOP в EJB Контейнере спрятано от самого EJB Контейнера. Это означает, что Поставщик Enterprise Bean может написать свой EJB Компонент и развернуть его в любом EJB Контейнере не заботясь о том, какие коммуникационные прооколы он использует.



CORBA против RMI


Вы видели, что одной из главных особенностей CORBA являтся поддержка RPC, которая позволяет вашим локальным объектам вызывать методы удаленного объекта. Конечно, есть родное свойство Java, которое делает то же самое: RMI (смотрите Главу 15). При использовании RMI возможным RPC между объектами Java, CORBA делает возможным RPC между объектами, реализованными на любом языке. В этом огромное различие.

Однако RMI может быть использовано для вызова сервисов удаленного не Java кода. Все, что вам нужно - это некоторый Java объект-оболочка, включающий в себя не Java код на стороне сервера. Объект-оболочка присоединяется внешним образом к Java клиенту по RMI, и внутренним образом соединяется с не Java кодом, используя одну из технологий, таких как JNI или J/Direct.

Такой подход требует от вас написания некоторого рода интеграционного уровня, который явно делает то, что CORBA делает за вас, но в этом случае у вас нет неоходимости использовать ORB сторонних разработчиков.



Дальнейшее сетевое программирование


На самом деле существует еще множество вещей, которые могли бы аходиться в этом разделе. Сетевая поддержка в Java также широкую поддержку URLs, включая управление протоколами для различных типов содержания. Все это может быть найдено на интернет сайте. Вы можете найти полное и подробное описание сетевого программирования на Java в книге Java Network Programming by Elliotte Rusty Harold (O’Reilly, 1997).



Данные final


Многие языки программирования имеют пути сообщить компилятору, что данный кусочек данных является неизменным, константой. Константа наиболее удобна для применения в следующих двух случаях:

Она должна быть константой во время компиляции и не может быть изменена никогда. Она может быть инициализирована во время инициализации и не должна быть изменена после.

В случае константы во время компиляции компилятор свертывает константу до значения в любых вычислениях, где она используется; при этом, нагрузка при вычислениях во время работы программы может быть значительно снижена. В Java константы такого рода должны быть примитивного типа и объявлены с использованием final. Значение должно быть определено во время определения переменной, как и любой константы.

Поля имеющие модификаторы static и final вообще являются ячейкой для хранения и не могут быть изменены.

При использовании final с объектами, а не с примитивными типами получается несколько не тот эффект. С примитивами, final создает константу значения, а с объектами - ссылку, final создает ссылку - константу. Как только ссылка инициализируется на какой-то объект, она уже не может быть в последствии перенаправлена на другой объект. Однако сам объект может быть модифицирован; Java не предоставляет способа создать объект - константу. (Однако, Вы можете написать свой собственный класс с эффектом константы.) Эти же ограничения накладываются и на массивы, поскольку они тоже объекты.

Ниже представлен пример, демонстрирующий использование полей с модификатором final:

//: c06:FinalData.java

// Эффект полей final.

class Value { int i = 1; }

public class FinalData { // Может быть константой во время компиляции

final int i1 = 9; static final int VAL_TWO = 99; // Обычная public константы:

public static final int VAL_THREE = 39; // Не может быть константой во время компиляции:

final int i4 = (int)(Math.random()*20); static final int i5 = (int)(Math.random()*20);

Value v1 = new Value(); final Value v2 = new Value(); static final Value v3 = new Value(); // Массивы:


final int[] a = { 1, 2, 3, 4, 5, 6 };

public void print(String id) { System.out.println( id + ": " + "i4 = " + i4 + ", i5 = " + i5); } public static void main(String[] args) { FinalData fd1 = new FinalData(); //! fd1.i1++; // Ошибка: значение не может быть изменено

fd1.v2.i++; // Объект не константа!

fd1.v1 = new Value(); // OK -- не final

for(int i = 0; i < fd1.a.length; i++) fd1.a[i]++; // Объект не константа!

//! fd1.v2 = new Value(); // Ошибка: Нельзя

//! fd1.v3 = new Value(); // изменить ссылку

//! fd1.a = new int[3];

fd1.print("fd1"); System.out.println("Creating new FinalData"); FinalData fd2 = new FinalData(); fd1.print("fd1"); fd2.print("fd2"); } } ///:~

Поскольку i1 и VAL_TWO являются final примитивами со значениями во время компиляции, то они могут быть использованы в обоих случаях, как константы времени компиляции и не имеют при этом отличий. VAL_THREE определена более типичным путем и Вы можете видеть, как определяются константы: public так, что она может быть использована вне пакета, static т.е. может существовать только одна и final объявляет, что она и есть константа. Заметьте, что примитивы final static с начальными неизменяемыми значениями (константы времени компилирования) называются большими буквами и слова разделены подчеркиванием (такие наименование похожи на константы в C.) Так же заметьте, что i5 не может быть известна во время компиляции, поэтому она названа маленькими буквами.

Просто, если, что-то определено, как final это еще не значит, что его значение известно на стадии компиляции. Это утверждение демонстрируется инициализацией i4 и i5 во время выполнения с использованием случайно генерируемых чисел. Та порция примера так же показывает различие между созданием final с модификатором static и без него. Это различие заметно, только во время инициализации во время выполнения, это происходит из-за того, что значения времени компиляции обращаются в те же самые самим компилятором. (И по видимому, существенно оптимизированными.) Это различие показано в выводе программы после одного запуска:

fd1: i4 = 15, i5 = 9 Creating new FinalData fd1: i4 = 15, i5 = 9 fd2: i4 = 10, i5 = 9



Заметьте, что значения i4 для fd1 и для fd2 уникальны, но значение для i5 не изменилось после создания второго объекта FinalData. Такое произошло потому, что i5 static и инициализировалась только один раз при загрузке, а не каждый раз, когда создавался новый объект.

Переменные v1 и v4 демонстрируют значение final ссылки. Как Вы можете видеть в методе main( ), только потому, что v2 является final вовсе не означает, что Вы не можете изменить ее значение. Хотя, Вы не можете перенаправить v2 на новый объект, и это потому, что она final. Вот такой смысл вкладывается в понятие final ссылок. Вы так же можете увидеть точно такое же действие на примере массивов, поскольку они являются так же разновидностью ссылок. (Я например не знаю способа, как сделать ссылки массивов самих на себя final.) Таким образом, создание ссылок с типом final менее удобна в использовании, чем создание примитивов с модификатором final.


Дейтаграммы


Примеры, которые Вы увидели используют протоколTransmission Control Protocol (TCP, также известный каксокеты основанные на потоках), который создан для исключительной надежности и гарантирует, что данные будут доставлены туда, куда необходимо. Он позволяет организовать повторную передачу потерянных данных, он предоставляет возможность отсылки отдельных частей через разные маршрутизаторы, в случает если один из них выйдет из строя, и байты будут приняты именно в том порядке, в ктором они были посланы. Весь этот контроль и надежность имеет цену: в TCP высокие накладные расходы.

Существует второй протокол, называемый User Datagram Protocol (UDP), который не гарантирует, что пакеты будут доставлены и не гарантирует доставки в том порядке, в котором они были посланы. Он называется “ненадежным протоколом” (TCP это “надежный протокол”), и это звучит не очень хорошо, однако он намного быстрее и потому может быть полезным. Существуют некоторые приложения, такие как аудио сигналы, в которых потеря нескольких пакетов не очень не имеет большого значения, но скорость очень важна. Либо представьте сервер предоставляющий информацию о времени, где действительно не имеет значени, если одно из сообщений потеряется. Также, некоторые приложения могут посылать UDP сообщения на сервер, а затем, при отсутствии отклика в течение некоторого времени, считать, что сообщение было потеряно.

На самом деле, Вы будете делать большинство сетевых приложений с протоколом TCP, и только некоторые будут испольовать UDP. Существует более полное описание UDP, включающее примеры, в первой редакции книги (доступных на CD ROM вместе с книгой, либо свободно загружаемы с сайта www.BruceEckel.com).



@Deprecated


Это используется для ярлыка особенностей, которые были заменены улучшенными особенностями. Ярлык deprecated советует вам больше не использовать эту определенную особенность, так как когда нибудь в будущем она будет удалена. Метод, помеченный как @deprecated заставляет компилятор выдавать предупреждение, если он используется.



Деревья


Использование JTree может быть также просто, как об этом сказано:

add(new JTree( new Object[] {"this", "that", "other"}));

Так отображается примитивное дерево. Однако API для деревьев достаточно обширно — несомненно, одно из самых больших в Swing. Это означает, что вы можете делать с деревьями все, что угодно, но более изощренные задачи могут требовать немного исследований и экспериментов.

К счастью, библиотека обеспечивает ядро: компонент дерева “по умолчанию” обычно делает то, что вам надо. Так что большую часть времени вы будете использовать эти компоненты, и только в специальных случаях вам нужно будет исследовать и понимать деревья более глубоко.

Следующий пример использует компонент дерева “по умолчанию” для отображения дерева в апплете. Когда вы нажмете кнопку, добавится новое поддерево в текущей выделенной ноде (если нода не выбрана, используется корневая нода):

//: c13:Trees.java

// Простой пример Swing дерева. Деревья могут

// быть сделаны намного более сложными, чем здесь.

// <applet code=Trees

// width=250 height=250></applet>

import javax.swing.*; import java.awt.*; import java.awt.event.*; import javax.swing.tree.*; import com.bruceeckel.swing.*;

// Берется массив Strings и создается первый

// элемент ноды, а оставшиеся оставляются:

class Branch { DefaultMutableTreeNode r; public Branch(String[] data) { r = new DefaultMutableTreeNode(data[0]); for(int i = 1; i < data.length; i++) r.add(new DefaultMutableTreeNode(data[i])); } public DefaultMutableTreeNode node() { return r; } }

public class Trees extends JApplet { String[][] data = { { "Colors", "Red", "Blue", "Green" }, { "Flavors", "Tart", "Sweet", "Bland" }, { "Length", "Short", "Medium", "Long" }, { "Volume", "High", "Medium", "Low" }, { "Temperature", "High", "Medium", "Low" }, { "Intensity", "High", "Medium", "Low" }, }; static int i = 0; DefaultMutableTreeNode root, child, chosen; JTree tree; DefaultTreeModel model; public void init() { Container cp = getContentPane(); root = new DefaultMutableTreeNode("root"); tree = new JTree(root); // Это добавляется для заботы о скроллировании:


cp.add(new JScrollPane(tree), BorderLayout.CENTER); // Получение модели дерева:

model =(DefaultTreeModel)tree.getModel(); JButton test = new JButton("Press me"); test.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e){ if(i < data.length) { child = new Branch(data[i++]).node(); // Что было последним, на чем вы щелкнули?

chosen = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent(); if(chosen == null) chosen = root; // Модель будет создавать

// соответствующие события. В ответ

// дерево будет обновлять себя:

model.insertNodeInto(child, chosen, 0); // Здесь помещается новая нода

// в текущую выбранную ноду.

} } }); // Изменение цвета кнопки:

test.setBackground(Color.blue); test.setForeground(Color.white); JPanel p = new JPanel(); p.add(test); cp.add(p, BorderLayout.SOUTH); } public static void main(String[] args) { Console.run(new Trees(), 250, 250); } } ///:~

Первый класс, Branch, это инструмент для получения массива String и построения DefaultMutableTreeNode с первым элементом String в качестве корня, а другие элементы не трогаются. Затем может быть вызван node( ) для производства корня этого “branch”.

Класс Trees содержит двумерный массив из String, из которого могут быть сделаны Branch (ветви), и static int i для подсчета в массиве. Объект DefaultMutableTreeNode содержит ноды, а физическое представление на экране управляется JTree и ассоциированной с ним моделью - DefaultTreeModel. Обратите внимание, что когда JTree добавляется в апплет, он оборачивается в JScrollPane — это все, что нужно сделать для автоматического скроллинга.

JTree управляется своей моделью. Когда вы делаете изменения в модели, модель генерирует событие, которое является причиной того, что JTree выполняет необходимые обновления для отображения представления дерева. В init( ) модель захватывается вызовом getModel( ). Когда нажимается кнопка, создается новая “ветвь(branch)”. Затем находится текущий выделенный компонент (или используется корень, если ничего не выбрано) и метод модели insertNodeInto( ) выполняет всю работу по изменению дерева и является причиной его обновления.

Пример, подобный приведенному мной выше, может дать вам то, что вы хотите от дерева. Однако деревья имеют мощь для выполнения всего, что вы можете вообразить — везде, где вы видите слова “по умолчанию” в приведенном выше примере, вы можете заменить своим собственным классом для получения другого поведения. Но остерегитесь: почти все классы имеют большие интерфейсы, так что вы можете потратить много времени на борьбу с пониманием интерфейсов деревьев. Несмотря на это, в этом заключается хороший дизайн, а альтернативы обычно хуже.


Детали расчета


Инструкция:

char c = (char)(Math.random() * 26 + 'a');

заслуживает более подробного рассмотрения. Math.random( ) производит double, так что значение 26 переводится в double для выполнения умножения, которое также производит double. Это означает, что ‘a’ должно переводится в double для выполнения сложения. Результат типа double переводится назад к char с помощью приведения.

Что делает приведение к char? То есть, если вы имеете значение 29.7 и вы приводите его к char, будет ли результирующее значение равно 30 или 29? Ответ можно найти в этом примере:

//: c03:CastingNumbers.java

// Что случается, когда вы приводите float

// или double к целому значению?

public class CastingNumbers { public static void main(String[] args) { double

above = 0.7, below = 0.4; System.out.println("above: " + above); System.out.println("below: " + below); System.out.println( "(int)above: " + (int)above); System.out.println( "(int)below: " + (int)below); System.out.println( "(char)('a' + above): " + (char)('a' + above)); System.out.println( "(char)('a' + below): " + (char)('a' + below)); } } ///:~

Вот результат:

above: 0.7 below: 0.4 (int)above: 0 (int)below: 0 (char)('a' + above): a (char)('a' + below): a

Так что ответ такой: приведение float или double к целому значению происходит простым обрезанием.

Второй вопрос относительно Math.random( ). Тут производится значение от нуля до одного, включая или не включая значение ‘1’? На математическом языке: (0,1) или [0,1], или (0,1] или [0,1)? (Прямоугольная скобка означает “включая”, а круглая скобка означает “не включая”.) И в этот раз тестовая программа поможет получить ответ:

//: c03:RandomBounds.java

// Может ли Math.random() производить 0.0 и 1.0?

public class RandomBounds { static void usage() { System.out.println("Usage: \n\t" + "RandomBounds lower\n\t" + "RandomBounds upper"); System.exit(1); } public static void main(String[] args) { if(args.length != 1) usage(); if(args[0].equals("lower")) { while(Math.random() != 0.0) ; // Продолжаем пробовать

System.out.println("Produced 0.0!"); } else if(args[0].equals("upper")) { while(Math.random() != 1.0) ; // Продолжаем пробовать

System.out.println("Produced 1.0!"); } else usage(); } } ///:~

Для запуска программы наберите в командной строке:

java RandomBounds lower

или

java RandomBounds upper

В обоих случаях вы можете прервать программу в ручную в том случае, если окажется, что Math.random( ) никогда не производит 0.0 или 1.0. Но такой экспериметн может обмануть. Если вы узнаете, [26] что есть примерно 262 различных значений типа double в пределах от 0 до 1, вероятность достижения любого единичного значения экспериментально может превышать время жизни компьютера и даже экспериментатора. Считается, что 0.0 - включается в выходные значения Math.random( ). или, на математическом языке, [0,1).



Дилемма домоводства: Кто должен убирать?


Каждый объект требует ресурсов для существования, большинство - много памяти. Когда объект более не нужен, он должен быть очищен так, чтобы его ресурсы были освобождены для повторного использования. В некоторых случаях программирования вопрос о том, как очищать объект не встает: вы создаете объект, используете его столько, сколько нужно, а затем он должен быть разрушен. Это не сложно, однако, встречаются задачи, в которых ситуация более сложная.

Предположим, например, вы разрабатываете систему для управления воздушным движением аэропорта. (Эта же модель может также работать для управления ящиками на складе или системой видео проката, или собачьим питомником.) На первый взгляд это выглядит просто: Создать контейнер для хранения аэропланов, затем создать новый объект аэроплана и поместите его в контейнер для каждого аэроплана, который входит в зону регулировки воздушного движения. Для очистки - просто удалите объект аэроплана, когда он оставляет зону.

Однако вы имеете несколько другую систему для записи данных об аэроплане; возможно, что данные не требуются такого же пристального внимания, как и главная функция управления. Возможно, эта запись о летящем самолете, одном из всех маленьких самолетов, вылетевших из аэропорта. Так что вы имеете второй контейнер маленьких самолетов и когда бы вы ни создали объект аэроплана, вы так же помещаете его во второй контейнер, если это маленький самолет. Затем некоторый фоновый процесс выполняет операцию над объектами этого контейнера в моменты минимальной занятости.

Теперь проблема более сложная: как вы можете знать, когда разрушать объект? Когда вы закончили работу с объектом, некоторые другие части системы могут еще работать с ним. Эта же проблема может возникнуть и в других ситуациях и в системах программирования (таких как C++) в которых вы должны явно удалять объект, когда вы закончили работу с ним и это будет достаточно сложно.

В Java сборщик мусора предназначен, чтобы позаботится о проблеме освобождения памяти (хотя это не включает другие аспекты очистки объекта). Сборщик мусора “знает”, когда объект более не используется, и он автоматически освобождает память этого объекта. Это (совместно с тем фактом, что все объекты наследуются от одного корневого класса Object, и то, что вы можете создать объект только одним способом, в куче) делает процесс программирования в Java проще, чем в C++. Вам нужно принимать гораздо меньше решений и преодолевать гораздо меньше препятствий.



Динамическое построение событий


Одна из выгод модели событий Swing заключена в гибкости. Вы можете добавлять и удалять обработку события одним вызовом метода. Следующий пример демонстрирует это:

//: c13:DynamicEvents.java

// Вы можете динамически изменить проявление события.

// Также показывается различные акции для события.

// <applet code=DynamicEvents

// width=250 height=400></applet>

import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.util.*; import com.bruceeckel.swing.*;

public class DynamicEvents extends JApplet { ArrayList v = new ArrayList(); int i = 0; JButton b1 = new JButton("Button1"), b2 = new JButton("Button2"); JTextArea txt = new JTextArea(); class B implements ActionListener { public void actionPerformed(ActionEvent e) { txt.append("A button was pressed\n"); } } class CountListener implements ActionListener { int index; public CountListener(int i) { index = i; } public void actionPerformed(ActionEvent e) { txt.append("Counted Listener "+index+"\n"); } } class B1 implements ActionListener { public void actionPerformed(ActionEvent e) { txt.append("Button 1 pressed\n"); ActionListener a = new CountListener(i++); v.add(a); b2.addActionListener(a); } } class B2 implements ActionListener { public void actionPerformed(ActionEvent e) { txt.append("Button2 pressed\n"); int end = v.size() - 1; if(end >= 0) { b2.removeActionListener( (ActionListener)v.get(end)); v.remove(end); } } } public void init() { Container cp = getContentPane(); b1.addActionListener(new B()); b1.addActionListener(new B1()); b2.addActionListener(new B()); b2.addActionListener(new B2()); JPanel p = new JPanel(); p.add(b1); p.add(b2); cp.add(BorderLayout.NORTH, p); cp.add(new JScrollPane(txt)); } public static void main(String[] args) { Console.run(new DynamicEvents(), 250, 400); } } ///:~

Новшества этого примера в том, что:

Есть более одного слушателя, прикрепленного в каждой Button. Обычно компоненты обрабатывают события как групповые (multicast), в том смысле, что вы можете зарегистрировать много слушателей для единственного события. В специальных компонента, в которых события обрабатываются индивидуально (unicast), вы получите TooManyListenersException.

Во время выполнения программы слушатели динамически добавляются и удаляются из Button b2. Добавление происходит тем же способом, который вы видели ранее, но каждый компонент имеет также метод removeXXXListener( ) для удаления слушателя каждого типа.

Гибкость такого рода обеспечивает большую мощь вашему программированию.

Вы должны заметить, что слушатели событий вызываются не гарантированно после того, как они добавлены (хотя большинство реализаций делает работу, фактически, этим способом).



Динамическое выражение instanceof


Метод объекта Class isInstance предоставляет способ динамического вызова оператора instanceof. Таким образом, все эти скучные выражения instanceof могут быть удалены, что и показано в примере PetCount:

//: c12:PetCount3.java // Использование isInstance(). import java.util.*;

public class PetCount3 { public static void main(String[] args) throws Exception { ArrayList pets = new ArrayList(); Class[] petTypes = { Pet.class, Dog.class, Pug.class, Cat.class, Rodent.class, Gerbil.class, Hamster.class, }; try { for(int i = 0; i < 15; i++) { // Смещение на 1, чтобы исключить класс Pet.class: int rnd = 1 + (int)( Math.random() * (petTypes.length - 1)); pets.add( petTypes[rnd].newInstance()); } } catch(InstantiationException e) { System.err.println("Cannot instantiate"); throw e; } catch(IllegalAccessException e) { System.err.println("Cannot access"); throw e; } HashMap h = new HashMap(); for(int i = 0; i < petTypes.length; i++) h.put(petTypes[i].toString(), new Counter()); for(int i = 0; i < pets.size(); i++) { Object o = pets.get(i); // Использование isInstance для исключения индивидуальных // выражений instanceof: for (int j = 0; j < petTypes.length; ++j) if (petTypes[j].isInstance(o)) { String key = petTypes[j].toString(); ((Counter)h.get(key)).i++; } } for(int i = 0; i < pets.size(); i++) System.out.println(pets.get(i).getClass()); Iterator keys = h.keySet().iterator(); while(keys.hasNext()) { String nm = (String)keys.next(); Counter cnt = (Counter)h.get(nm); System.out.println( nm.substring(nm.lastIndexOf('.') + 1) + " quantity: " + cnt.i); } } } ///:~

Вы видите, что метод isInstance( ) исключает использование выражения instanceof. В дополнение, это означает также, что Вы можете добавить новые типы класса Pet просто изменив массив petTypes; остальная часть программы не требует никаких изменений (в отличие от использования выражения instanceof).



Директивы JSP


Директивы являются сообщениями для JSP контенера и указываются символом “@”:

<%@ directive {attr="value"}* %>

Директивы ничего не посылают в поток out, но они важны в настройке атрибутов ваших JSP страниц и взаиможействий с JSP контейнером. Например строка:

<%@ page language="java" %>

сообщает, что на JSP странице используется язык скриптов Java. На самом деле JSP спецификации только описывают, что семантика языка скриптов аналогична “Java”. Назначение этой директивы состоит в придании гибкости технологии JSP. В будующем, если вы выберите другой язык, скажем Python (хороший выбор для написания скриптов), и этот язык будет иметь поддержку Java Run-time Environment, с применением технологии Java объектыных моделей для среды скриптов, особенно это относится к определенным выше переменным, свойствам JavaBeans и публичным методам.

Наиболее важная директива - это директива страницы. Она определяет номер страницы в зависимости от атрибутов и взаимодействия между этими атрибутами и JSP контейнером. К таким атрибутам относятся: language, extends, import, session, buffer, autoFlush, isThreadSafe, info и errorPage. Например:

<%@ page session=”true” import=”java.util.*” %>

Эта строка указывает, что страница требует участия HTTP сессии. Так как мы не установили директивы языка, JSP контейнер по умолчанию использует Java и подразумеваемую переменную языка скриптов, называемую session типа javax.servlet.http.HttpSession. Если бы директива была ложна, то неявная переменная session была бы недоступна. Если переменная session не указана, то используется значение по умолчанию “true”.

Аттрибут import описывает типы, которые доступны в окружении скриптов. Этот атрибут используется так, как если бы он использовался в языке программирования Java, т.е. это разделеный запятыми список, как и обычное выражение import. Этот список импортируется реализацией транслированной JSP страницы и доступен для среды скрипта. Опять таки, пока это определено, когда значение директивы языка - “java”.



Для чего нужен finalize( )?


После всего изложенного выше вы можете предположить, что вы не должны использовать finalize( ) в целях очистки общего назначения. Насколько это хорошо?

Третье, что вы должны помнить:

Сборка мусора относится только к памяти.

То есть, главная причина существования сборщика мусора состоит в освобождении памяти, которую ваша программа более не использует. Таким образом, любые действия, которые ассоциируются со сборкой мусора, больше всего подходят для вашего метода finalize( ), и должны относится только к памяти и ее освобождению.

Значит ли это, что если ваш объект содержит другие объекты, finalize( ) должен явно освободить эти объекты? Нет, сборщик мусора позаботится об освобождении памяти всех объектов, независимо от того, как были созданы объекты. Оказывается, что потребность в finalize( ) ограничивается особыми случаями, в которых объекты могут резервировать некоторое хранилище другим способом, отличным от создания объектов. Но, вы можете заметить, что все в Java - это объекты. Как же такое может быть?

Таким образом, finalize( ) занимает свое место, потому что существует возможность, что вы выполнили подобное C резервирование памяти, используя механизм, отличный от естественного для Java. Это может произойти, в основном, в родных методах, которые являются способом вызова не Java кода из Java. (Родные методы обсуждаются в Приложении B.) C и C++ являются теми языками, которые в настоящее время поддерживаются родными методам, но так как они могут вызывать подпрограммы других языков, вы можете, на самом деле, вызвать все, что угодно. Внутри не Java кода семейство функций malloc( ) из C может быть вызвано для резервирования хранилища, и до тех пор, пока вы не вызовите free( ), это хранилище не будет освобождено, что приводит к утечке памяти. Конечно, free( ) является функцией C и C++, так что вам необходимо вызывать ее в родном методе внутри вашего finalize( ).

После того, что вы прочли, вы, вероятно, пришли к мысли, что вам чаще всего не нужно использовать finalize( ). Вы правы; это не подходящее место для выполнения обычной очистки. Тогда где должна выполнятся обычная очистка?



Для чего нужно finally?


В языках без сборщика мусора и без автоматического вызова деструктора [54], finally очень важно, потому что оно позволяет программисту гарантировать освобождение памяти независимо от того, что случилось в блоке try. Но Java имеет сборщик мусора, так что освобождение памяти, фактически, не является проблемой. Также, язык не имеет деструкторов для вызова. Так что, когда вам нужно использовать finally в Java?

finally необходимо, когда вам нужно что-то установить, отличное от блока памяти, в его оригинальное состояние. Это очистка определенного вида, такое как открытие файла или сетевого соединения, рисование на экране или даже переключение во внешний мир, как смоделировано в следующем примере:

//: c10:OnOffSwitch.java

// Почему используется finally?

class Switch { boolean state = false; boolean read() { return state; } void on() { state = true; } void off() { state = false; } } class OnOffException1 extends Exception {} class OnOffException2 extends Exception {}

public class OnOffSwitch { static Switch sw = new Switch(); static void f() throws OnOffException1, OnOffException2 {} public static void main(String[] args) { try { sw.on(); // Код, который может выбросить исключение...

f(); sw.off(); } catch(OnOffException1 e) { System.err.println("OnOffException1"); sw.off(); } catch(OnOffException2 e) { System.err.println("OnOffException2"); sw.off(); } } } ///:~

Цель этого примера - убедится, что переключатель выключен, когда main( ) будет завершена, так что sw.off( ) помешена в конце блока проверки и в каждом обработчике исключения. Но возможно, что будет выброшено исключение, которое не будет поймано здесь, так что sw.off( ) будет пропущено. Однако с помощью finally вы можете поместить очищающий код для блока проверки только в одном месте:

//: c10:WithFinally.java

// Finally гарантирует очистку.

public class WithFinally { static Switch sw = new Switch(); public static void main(String[] args) { try { sw.on(); // Код, который может выбросить исключение...


OnOffSwitch.f(); } catch(OnOffException1 e) { System.err.println("OnOffException1"); } catch(OnOffException2 e) { System.err.println("OnOffException2"); } finally { sw.off(); } } } ///:~

Здесь sw.off( ) была перемещена только в одно место, где она гарантировано отработает не зависимо от того, что случится.

Даже в случае исключения, не пойманного в этом случае набором предложений catch, finally будет выполнено прежде, чем механизм обработки исключений продолжит поиск обработчика на более высоком уровне:

//: c10:AlwaysFinally.java

// Finally выполняется всегда.

class FourException extends Exception {}

public class AlwaysFinally { public static void main(String[] args) { System.out.println( "Entering first try block"); try { System.out.println( "Entering second try block"); try { throw new FourException(); } finally { System.out.println( "finally in 2nd try block"); } } catch(FourException e) { System.err.println( "Caught FourException in 1st try block"); } finally { System.err.println( "finally in 1st try block"); } } } ///:~

Вывод этой программы показывает что происходит:

Entering first try block Entering second try block finally in 2nd try block Caught FourException in 1st try block finally in 1st try block

Инструкция finally также будет исполнена в ситуации, когда используются инструкции break и continue. Обратите внимание, что наряду с помеченным break и помеченным continue, finally подавляет необходимость в использовании инструкции goto в Java.


Do-while


Форма для do-while следующая:

do

инструкция while(Логическое выражение);

Главное отличие между while и do-while в том, что инструкция в цикле do-while всегда выполняется не менее одного раза, даже если вычесленное выражение ложное с самого начала. В цикле while, если условие ложное в первый раз, инструкция никогда не выполнится. На практике do-while используется реже, чем while.



Добавление атрибутов и полезных интерфейсов


Использование многослойных объектов для динамического и прозрачного добавления ответственности индивидуальным объектам, называется шаблоном декорации. (Шаблоны [57] являются предметом обсуждения Thinking in Patterns with Java, доступной на www.BruceEckel.com.) Шаблон декорации определяет, что все объекты, которые крутятся вокруг вашего начального объекта, имеют один и тот же интерфейс. Это делает основное использование декораторов прозрачным — вы посылаете объекту одни и те же с сообщения не зависимо от того, был он декорирован или нет. Это причина существования “фильтрующих” классов в библиотеке ввода/вывода в Java: абстрактный “фильтрующий” класс - это базовый класс для всех декораторов. (Декоратор должен иметь такой же интерфейс, что и объект, который он декорирует, но декоратор так же может расширить интерфейс, что случается в некоторых “фильтрующих” классах.

Декорирование часто используется, когда простое использование подклассов в результате приводит к большому числу подклассов, способных удовлетворить каждую возможную необходимую комбинацию, что становится непрактично. Библиотека ввода/вывода Java требует много различных комбинаций особенностей, которые являются причиной использования шаблона декоратора. Однако для шаблона декоратора есть препятствие. Декораторы дают вам много больше гибкости, когда вы пишите программу (так как вы можете легко смешивать и сравнивать атрибуты), но они привносят сложность в ваш код. Причина того, что библиотека Java неудобна в использовании, состоит в том, что вы должны создавать много классов — “центральные” типы ввода/вывода, плюс все декораторы — для того, чтобы создать единственный объект ввода/вывода, который вам нужен.

К классам, обеспечивающим интерфейс декоратора для управления определенным InputStream или OutputStream, относятся FilterInputStream и FilterOutputStream — которые не имеют интуитивно понятных имен. FilterInputStream и FilterOutputStream являются абстрактными классами, наследованными от базовых классов библиотеки ввода/вывода InputStream и OutputStream, которые являются ключевым требованием декоратора (так как он обеспечивает общий интерфейс для всех объектов, которые будут декорироваться).



Добавление клонируемости в класс


Несмотря на то что метод клонирования определен в классе Object, являющемся базовым для всех классов Java, это не означает что он автоматически может быть применен к любому классу [81]. Казалось бы, это идет в разрез с принципом наследования дочерними объектами методов родительских классов. Действительно, в Java клонирование идет вразрез с этим принципом. Поэтому, если вы хотите сделать эту функцию доступной для вашего класса, вы должны написать соответствующий код, обеспечивающий правильную работу метода клонирования.

Использование приема с protected

Для блокирования возможности клонирования во всех классах Java, в базовом классе Object метод clone() был описан как защищенный (protected). Это не только исключает возможность использования метода клонирования программистом, просто использующим (не расширяющим) этот класс, но и означает что вы не можете использовать clone() используя ссылку на базовый класс. (Хотя это может показаться полезным. Например, при полиморфном клонировании связок классов Object). Такой метод применен для того, чтобы на этапе компиляции информировать о том что данный объект является неклонируемым. Как ни странно, большинство классов стандартных библиотек Java неклонируемые. Поэтому, написав:

Integer x = new Integer(1);

x = x.clone();

на этапе компиляции это приведет к возникновению ошибки. Компилятор выдаст сообщение о том что метод clone() недоступен (поскольку Integer не переопределяет его и он по умолчанию является защищенным (protected)). Однако, если вы работаете с классом, производным от  Object (а это все классы языка Java), то у вас есть возможность вызвать метод Object.clone(), поскольку этот метод является защищенным (protected), а ваш объект является объектом-наследником по отношению к классу Object. Метод clone() класса Object обладает полезными функциональными возможностями - он осуществляет поразрядное дублирование передаваемого класса объекта, что и является основной операцией при клонировании объекта. Тем не менее вам будет необходимо написать собственный метод клонирования и описать ее как public. Итак, два ключевых момента при реализации клонировании это:


обязательный вызов метода super.clone()

написание собственного public метода клонирования 

В дальнейшем вам возможно понтребуется переопределить ваш метод clone() для классов-наследников, поскольку иначе при их клонировании будет использоваться ваш (теперь уже public) метод clone(), который может не выполнять своих функций для этих классов (хотя, поскольку создание копии самого объекта осуществляет метод Object.clone(), подобных проблем может и не быть). Такой прием с переопределением защищенного (protected) метода clone() может применяться только когда вы наследуете не клонируемый класс и хотите на его базе создать класс, поддерживающий клонирование. При этом для все классы, наследующие ваш класс, в свою очередь унаследуют и созданный вами метод clone(), поскольку в Java нельзя изменять статус наследуемых методов. Иными словами, если ваш класс является клонируемым, то и все наследующие его классы также будут клонируемыми, если только вы не примените приемы "отключения" клонируемости (они подробно рассмотрены далее).

Реализация интерфейса Cloneable


Для создания клонируемых объектов вам понадобятся навыки реализации Cloneable интерфейса. Этот интерфейс примечателен уже тем, что он совершенно пустой!

interface Cloneable {}

Очевидно, что причины наследования пустого интерфейса никак не связаны с последующим использованием его методов. В данном случае интерфейс используется в нестандартных целях. Он служит своего рода "меткой" для типа класса. Существуют две причины существования интерфейса Cloneable. Первая заключается в том, что вы можете использовать ссылки на базовый тип и при этом не знать, является ли он клонируемым или нет. В таких случаях вы можете использовать ключевое слово instanceof (рассмотренное в главе 12) для выяснения, является ли объект с которым связана ссылка клнируемым:

if(myReference instanceof Cloneable) // ...

Вторая причина связана с вышеупомянутой блокировкой клонирования в классах. Перед началом работы метод Object.clone() осуществляет проверку класса на реализацию интерфейса Cloneable и, если класс не реализует этот интерфейс, возвращает значение CloneNotSupportedException. Поэтому для поддержки клонирования вы вынуждены реализовать интерфейс Cloneable.


Домашний интерфейс


Каждый содающийся Enterprise Bean должен иметь ассоциированный домашний интерфейс. Домашний интерфейс используется как фабрика для вашего EJB. Клиент использует Домашний интерфейс для нахождения экземпляра вашего EJB или создания нового экземпляра вашего EJB.



Дополнительная информация


Вы можете найти более подробное объяснение, включая примеры кода на С (скорее чем С++) и дискуссию относительно подхода Microsoft в Приложении А первой редакции этой книги (находиться на CD поставляемого с этой книгой или на Web-сайте www.BruceEckel.com). Более подробная информация находиться на сайте java.sun.com (в поисковой системе выберите “training & tutorials”, а в качестве ключа “native methods”). Глава 11 книги Core Java 2, Volume II, by Horstmann & Cornell (Prentice-Hall, 2000) содержит всеобъемлющее описание собственных методов.

[ Предыдущая глава ] [ Оглавление ] [ Содержание ] [ Индекс ] [ Следующая глава ]



Доступ к Java строкам


В качестве примера доступа к JNI функции рассмотрим код MsgImрl.cpp. Здесь аргумент env типа JNIEnv используется для доступа к типам String в Java. Строки в Java хранятся в формате Unicode, поэтому если вы хотите передать их в качестве параметра в функцию, которая Unicode не поддерживает (printf() например), необходимо вначале преобразовать строку в ASCII с помощью GetStringUTFChars(). Данная функция принимает String и преобразует в строку в формате UTF-8. (Для хранения ASCII достаточно 8 бит и 16 бит для Unicode. Если исходная строка 8-ми битовая ASCII, то результирующая строка будет также ASCII.)

GetStringUTFChars( ) одна из функций-членов JNIEnv. Для доступа к JNI функции мы используем типичный C++ синтаксис для вызова функции-члена несмотря на указатель. Можно использовать приведенную выше форму для доступа ко всем JNI функциям.



Доступ к JNI функциям: аргументы JNIEnv


Под функциями JNI подразумеваются функции, которые взаимодействуют с JVM из собственных методов. Как вы могли видеть в приведенном выше примере, каждый собственный метод JNI получает специальный аргумент в качестве первого параметра: это и есть JNIEnv аргумент, который является указателем на специальную структура данных типа JNIEnv_. Один из элемент структуры данных JNI является указателем на массив генерируемый JVM. Массив состоит из указателей на JNI функции. JNI функции могут быть вызваны из собственного метода путем разыменования данных указателей (это проще чем кажется). Каждая JVM обеспечивает собственной реализацией JNI функций, но их адреса всегда остаются на определенном месте.

С помощью аргументов JNIEnv программе доступно большое количество функций. Эти функции могут быть сгруппированы в следующие категории:

Получение информации о версии

Выполнение операций с классами и объектами

Использование глобальных и локальных ссылок на Java объекты Доступ к полям ссылки и статическим полям Вызов ссылочных методов и статических методов Выполнение операций со строками и массивами Генерация и перехват Java исключений

Количество JNI функций достаточно большое и не может быть описано здесь. Вместо этого покажем логическое обоснование использования этих функций. Более детальная информация находиться в документации по JNI.

Если посмотреть на заголовочный файл jni.h можно видеть что внутри условий препроцессора #ifdef __cplusplus структура JNIEnv_ определена как класс когда компилируется С++ компилятором. Данный класс содержит несколько функций, которые позволяют вам получить доступ к JNI функциям через простой и знакомый синтаксис. В качестве иллюстрации приведем строку кода из рассмотренного примера:

env->ReleaseStringUTFChars(jMsg, msg);

также может быть вызвано из С следующим образом:

(*env)->ReleaseStringUTFChars(env, jMsg, msg);

Вы заметили, что строка на С гораздо сложнее (что естесственно) - вам необходимо двойное разыменование указателя на env. Вы также должны передать этот указатель в качестве первого параметра при вызове JNI функции. Примеры данного приложения используют С++ стиль.



Доступ класса


В Java, спецификаторы доступа могут также использоваться для определения того, какие классы внутри библиотеки будут доступны пользователям библиотеки. Если Вы хотите сделать класс доступным клиентскому программисту, Вы располагаете ключевое слово public где-нибудь перед открывающей фигурной скобкой тела класса. Это определяет, может ли клиентский программист создать объект этого класса.

Для контроля доступа к классу, спецификатор должен располагаться перед ключевым словом class. Итак, Вы можете написать:

public class Widget {

Если имя Вашей библиотеки mylib любой клиентский программист может получить доступ к Widget с помощью

import mylib.Widget;

либо

import mylib.*;

Однако, существует несколько дополнительных ограничений:

Может существовать только один публичный класс в одном модуле компиляции (файле). Идея состоит в том, что один модуль компиляции имеет один публичный интерфейс, представленный этим публичным классом. Он может иметь так много поддерживающих “дружественных” классов, сколько Вам необходимо. Если у Вас больше одного публичного класса в модуле компиляции, компилятор выдаст сообщение об ошибке. Имя публичного класса должно полностью совпадать, с именем файла, содержащего соответствующий модуль компиляции, включая регистры символов. Так, например, для класса Widget, имя файла должно быть Widget.java, но никак не widget.java или WIDGET.java. Итак, Вы получите ошибку компиляции, если Вы с этим не согласны. Возможно, но не типично, что у Вас будет модуль компиляции вообще без публичного класса. В этом случае, Вы можете называть файл как хотите.

А что, если у Вас есть такой класс внутри библиотеки mylib, который Вы используете для выполнения задач представленных классом Widget или каким-то другим публичным классом в mylib? Вы не хотите создавать документацию для клиентского программиста, и думаете, что когда-нибудь позже Вы захотите все изменить, либо вообще удалить класс, заменяя его другим. Чтобы иметь такую возможность, Вам нужно убедиться, что ни один клиентский программист не зависит от Ваших деталей реализации, скрытых внутри mylib. Для достижения этого, Вы удаляете ключевое слово public из класса, в этом случае он становится дружественным. (Этот класс может быть использован только внутри этого пакета.)


Обратите внимание, что класс не может быть private (это сделало бы его никому не доступным кроме самого этого класса), или protected[35]. Итак, у Вас есть только выбор из двух вариантов: “дружественный” или публичный. Если Вы не хотите, чтобы кто-то другой имел доступ к классу, Вы можете сделать все конструкторы приватными, этим запрещая любому кроме Вас, создание объекта этого класса внутри статического члена класса.[36]. Вот пример:

//: c05:Lunch.java

// Демонстрирует спецификаторы доступа к классу.

// Делает класс приватным

// с помощью приватных конструкторов:

class Soup { private Soup() {} // (1) Позволяет создание с помощью статического метода:

public static Soup makeSoup() { return new Soup(); } // (2) Создание статического объекта

// возвращается ссылка на запрос.

// (шаблон "Singleton"):

private static Soup ps1 = new Soup(); public static Soup access() { return ps1; } public void f() {} }

class Sandwich { // Использует Lunch

void f() { new Lunch(); } }

// В файле только один публичный класс:

public class Lunch { void test() { // Вы не можете сделать это! Приватный контруктор:

//! Soup priv1 = new Soup();

Soup priv2 = Soup.makeSoup(); Sandwich f1 = new Sandwich(); Soup.access().f(); } } ///:~

До сих пор, большинство методов возвращали либо void либо примитивный тип, и описание:

public static Soup access() { return ps1; }

может вначале привести в замешательство. Слово перед именем метода (access) говорит о том, что возвращает метод. Пока чаще всего был тип void, и это означало, что метод не возвращает ничего. Но Вы можете возвратить также ссылку на объект, что и происходит здесь. Этот метод возвращает ссылку на объект класса Soup.

Класс Soup показывает как предотвратить прямое создание класса, сделав все конструкторы приватными. Запомните, что если Вы не сознаете явно ни одного конструктора, конструктор по умолчанию (конструктор без аргументов) сам будет создан для Вас. Если Вы напишите конструктор по умолчанию, он не будет создаваться автоматически. Если Вы сделаете его приватным, то никто не сможет создать объект этого класса. Но сейчас, как кто-нибудь сможет использовать этот класс? Пример выше показывает два варианта. Первый, с помощью статического метода создается объект типа Soup и возвращается ссылка на него. Это может быть полезно, если Вы хотите выполнить несколько дополнительных операций с классом Soup, перед тем как его возвратить, либо если Вы хотите хранить количество создаваемых объектов типа Soup (возможно для контроля их популяции).



Второй вариант использует так называемый шаблон разработки, который описан в книге Thinking in Patterns with Java, доступной на с www.BruceEckel.com. Этот специфический шаблон называется “singleton” потому что он позволяет создавать только один объект. Объект класса Soup создается как статический приватный член класса Soup, и существует один и только один объект, и Вы не можете получить его никаким другим способом, кроме как с помощью публичного метода access( ).

Как ранее было упомянуто, если Вы вообще не ставите идентификатор доступа для класса, он становится “дружественным.” Это означает, что объект этого класса может быть создан в любом другом классе того же пакета, но не за его пределами. (Запомните, все файлы в одном каталоге не имеющие явного выражения package, принадлежат пакету по умолчанию для этого каталога.) Однако, однако, если статический член этого класса - публичный, то клиентский программист сможет получить доступ к этому статическому члену, даже если он не сможет создать объект этого класса.


Доступ "наружу" из множественно вложенных классов


[41]Совершенно не играет роли, как глубоко может быть вложен внутренний класс, он может совершенно прозрачно получить доступ ко всем элементам всех классов, в которых он вложен, ниже этому пример:

//: c08:MultiNestingAccess.java

// Вложенные классы могут получить доступ ко всем элементам

// всех классов, в которые они вложены.

class MNA { private void f() {} class A { private void g() {} public class B { void h() { g(); f(); } } } }

public class MultiNestingAccess { public static void main(String[] args) { MNA mna = new MNA(); MNA.A mnaa = mna.new A(); MNA.A.B mnaab = mnaa.new B(); mnaab.h(); } } ///:~

Вы можете видеть, что в MNA.A.B, методы g( ) и f( ) востребуются без каких либо ограничений (несмотря даже на тот факт, что они private). Этот пример также демонстрирует синтаксис требуемый для создания объектов многократно вложенных внутренних классов, когда Вы создаете объект в другом классе. New предоставляет правильную область действия, поэтому вам не нужно квалифицировать имя класса в вызове конструктора.



Дружественный доступ “Friendly”


А что, если Вы вообще не определяете спецификатор доступа, как это было сделано во всех примерах до настоящей главы? Доступ по умолчанию не имеет ключевого слова, но обычно называется дружественным - “friendly.” Это значит, что все другие классы в том же пакете имеют доступ к дружественным членам, но для классов за пределами этого пакета, члены являются приватными (private). Т.к. файл модуля компиляции может принадлежать только одному пакету, все классы внутри этого единичного модуля компиляции автоматически являются дружественными друг другу. Таким образом, говорят, что дружественные элементы имеют доступ на уровне пакета.

Дружественный доступ позволяет Вам объединять связанные классы в пакете, так, что они могут легко общаться друг с другом. Когда Вы располагаете классы вместе в одном пакете, (определив таким образом совместный доступ для дружественных членов), Вы “владеете” кодом в этом пакете. Во многих языках, Вам волей-неволей приходится организовывать определения в файлах, но Java Вас заставляет создавать их в разумной форме. К тому же, Вы, возможно, захотите исключить классы, которые не должны иметь доступ к классам в том же пакете.

Класс управляет тем, какой код имеет доступ к его членам. И нет никакого магического способа “прорваться внутрь.” Код из другого пакета не может появиться и сказать, “Привет, Я друг Боба!” и затем посмотреть все защищенные, дружественные и приватные члены Боба. Единственный путь получить доступ, это:

Сделать этот член публичным. И кто угодно, откуда угодно сможет получить к нему доступ. Сделайте это член дружественным, удалив все спецификаторы доступа, и расположите классы в одном пакете.

Как Вы увидите в Главе 6, когда наследование определено, унаследованный класс получает доступ к защищенным членам, а также к публичным членам (но не приватным). Этот класс может получить доступ к дружественным членам, только если эти два класса находятся в одном пакете. Но Вам не стоит беспокоиться об этом сейчас. Предоствавьте методы “accessor/mutator” (также известные как “get/set” методы), которые читают и изменяют значение какого-то поля класса. Это самый цивилизованный подход в терминах ООП, и это основной подход в JavaBeans, как Вы увидите в Главе 13.



Дублирующие ссылки (aliacing)


Термин "дублирующие ссылки" означает что с одним и тем же объектом связана более чем одна ссылка. Проблема с дублирующими ссылками возникают при попытке изменения данных в объекте. Если владельцы других ссылок не ожидают что объект изменился, такой поворот судьбы может преподнести им неприятный сюрприз. Приведем пример:

//: Приложение А:Alias1.java

// Две дублирующие ссылки на один и тот же объект.

public class Alias1 { int i; Alias1(int ii) { i = ii; } public static void main(String[] args) { Alias1 x = new Alias1(7); Alias1 y = x; // Дублирующая ссылка

System.out.println("x: " + x.i); System.out.println("y: " + y.i); System.out.println("Увеличиваем x"); x.i++; System.out.println("x: " + x.i); System.out.println("y: " + y.i); } } ///:~

В строке:

Alias1 y = x; // Дублирующая ссылка

создается новая ссылка Alias1, но вместо того чтобы указывать на созданный с использованием команды new новый объект, ей присваивается значение уже существующей ссылки. Следовательно, содержимое ссылки x (то есть адрес расположения объекта,на который указывает эта ссылка) присваивается ссылке y. Таким образом обе ссылки x и y связаны с одним и тем же объектом и увеличение значения x.i в выражении:

x.i++;

также повлечет за собой изменение значения y.i, что и наблюдается в результате выполнения примера:

x: 7 y: 7 Увеличиваем x x: 8 y: 8

Единственный способ избежать подобных ситуаций - отказ от использования дублирующих ссылок. Постарайтесь в своих программах не допускать одновременного существования более одной ссылки на один и тот же объект. Это сделает код ваших программ более удобочитаемым и простым в отладке. Однако, при передаче сслыки другому методу в качестве параметра (Java позволяет такие операции) эта ссылка автоматически дублируется и операции совершаемые с ней в методе могут влиять на состояние "внешнего" объекта (т.е. на объект, созданный вне данного метода). Например:

//: Приложение А:Alias2.java


// Вызванный метод изменяет внешний объект

// используя передаваемую в качестве параметра ссылку.

public class Alias2 { int i; Alias2(int ii) { i = ii; } static void f(Alias2 reference) { reference.i++; } public static void main(String[] args) { Alias2 x = new Alias2(7); System.out.println("x: " + x.i); System.out.println("Вызов метода f(x)"); f(x); System.out.println("x: " + x.i); } } ///:~

Результатом будет:

x: 7 Вызов метода f(x) x: 8

Метод используя ссылку, передаваемую ему в качестве параметра, изменяет внешний объект. В подобных случаях вам следует предусмотреть все возможные негативные последствия, чтобы эти изменения не оказались неожиданностью для пользователя и не привели к сбоям в работе программы.

То есть, создавая метод, изменяющий внешние объекты, вы должны четко проинформировать пользователя о результатах его выполнения. Но, во избежание "волчьих ям", лучше воздерживаться от изменения внешних объектов.

Если вам все же необходимо внести изменения в объект, переданный в качестве параметра, но при этом вы не хотите изменять внешний объект (т.е. изменения будут внесены лишь на время выполнения данного метода), тогда вам следует предварительно скопировать его в вашем методе. Тому как это лучше сделать и будет посвящена большая часть этого Приложения.


EJB-Jar файл


EJB-Jar файл - это обычный java jar файл, который содержит ваш EJB, Домашний и Удаленный интерфейсы наряду с описателем развертывания.