Инициализация статических данных
Когда данные являются статическими, происходит то же самое; если это примитивные типы и вы не инициализируете их, они получают стандартное начальное значение примитивных типов. Если это ссылка на объект, она становится равной null, если вы не создадите новый объект и не присоедините его к этой ссылке.
Если вы хотите поместить инициализацию в точку определения, это выглядит точно так же, как и для не статических членов. Есть единственный кусочек хранилища для static, не зависимо от того, сколько объектов создано. Но вопрос о том, когда инициализируется static хранилище, остается. Этот пример снимает этот вопрос:
//: c04:StaticInitialization.java
// Указание начальных значений в
// определении класса.
class Bowl { Bowl(int marker) { System.out.println("Bowl(" + marker + ")"); } void f(int marker) { System.out.println("f(" + marker + ")"); } }
class Table { static Bowl b1 = new Bowl(1); Table() { System.out.println("Table()"); b2.f(1); } void f2(int marker) { System.out.println("f2(" + marker + ")"); } static Bowl b2 = new Bowl(2); }
class Cupboard { Bowl b3 = new Bowl(3); static Bowl b4 = new Bowl(4); Cupboard() { System.out.println("Cupboard()"); b4.f(2); } void f3(int marker) { System.out.println("f3(" + marker + ")"); } static Bowl b5 = new Bowl(5); }
public class StaticInitialization { public static void main(String[] args) { System.out.println( "Creating new Cupboard() in main"); new Cupboard(); System.out.println( "Creating new Cupboard() in main"); new Cupboard(); t2.f2(1); t3.f3(1); } static Table t2 = new Table(); static Cupboard t3 = new Cupboard(); } ///:~
Bowl позволяет вам наблюдать за созданием класса, а Table и Cupboard создают static-члены Bowl вперемешку в определении класса. Обратите внимание, что Cupboard создает не-static Bowl b3 перед static определением. На выводе вы видите, что произошло:
Bowl(1) Bowl(2) Table() f(1) Bowl(4) Bowl(5) Bowl(3) Cupboard() f(2) Creating new Cupboard() in main Bowl(3) Cupboard() f(2) Creating new Cupboard() in main Bowl(3) Cupboard() f(2) f2(1) f3(1)
Static инициализация происходит только при необходимости. Если вы не создаете объект Table и никогда не обращаетесь к Table.b1 или Table.b2, static Bowl b1 и b2 никогда не будут созданы. Однако они инициализируются, только когда создается первый объект Table (или при возникновении первого static доступа static). После этого static объекты не инициализируются повторно.
Порядок инициализации таков: сначала инициализируются static, если они ранее не были инициализированы при создании предыдущего объекта, а затем инициализируются не static объекты. Вы ясно можете видеть в результатах работы программы.
Полезно просуммировать информацию о процессе создания объекта. Рассмотрим класс с названием Dog:
В начале создания объекта типа Dog, или при первом обращении к static методу или static полу класса Dog, интерпретатор Java должен найти Dog.class, что он выполняет, производя поиск по classpath.
После загрузки Dog.class (создания объекта Class, о котором вы узнаете позже), выполняются все static инициализации. Таким образом, static инициализации выполняются только однажды, когда объект Class загружается в первое время. Когда вы создаете new Dog( ), в процессе создания объекта Dog сначала резервируется хранилище для объекта Dog в куче.
Это хранилище заполняется нулями, автоматически присваивая всем переменным примитивных типов этого объекта Dog их начальное значение (ноль для числовых и эквивалент для boolean и char), а все ссылки в null.
Выполняются все инициализации, производящиеся в точке определения.
Выполняется конструктор. Как вы увидите в Главе 6, это может стать источником повышенной активности, особенно когда привлекается наследование.
Инициализация в конструкторе
Конструктор может быть использован для выполнения инициализации и это даст вам отличную гибкость вашим программам, так как вы можете вызывать методы и выполнять действия во время выполнения для определения начальных значений. Но одно вы должны иметь в виду: вы не препятствуете автоматической инициализации, которая происходит перед входом в конструктор. Так, например, если вы скажете:
class Counter { int i; Counter() { i = 7; } // . . .
то i сначала будет инициализирована 0, а затем 7. Это верно для всех примитивных типов и для ссылок на объекты, включая те, которые имеют явную инициализацию в точке определения. По этой причине компилятор не пробует ограничить вас в инициализации элементов в любом месте конструктора, или перед тем, как они будут использоваться — инициализация гарантирована [30].
Инициализирование полей в интерфейсах
Поля определенные в интерфейсах автоматически становятся static и final. Они не могут быть пустыми (чистыми) final переменными, но они могут быть инициализированы не постоянными выражениями. К примеру:
//: c08:RandVals.java
// Инициализирование полей интерфейса
// не постоянными инициализаторами.
import java.util.*;
public interface RandVals { int rint = (int)(Math.random() * 10); long rlong = (long)(Math.random() * 10); float rfloat = (float)(Math.random() * 10); double rdouble = Math.random() * 10; } ///:~
Поскольку все поля static, то они инициализируются при первой загрузке класса, что происходит при первом доступе к любой переменной. Вот пример:
//: c08:TestRandVals.java
public class TestRandVals { public static void main(String[] args) { System.out.println(RandVals.rint); System.out.println(RandVals.rlong); System.out.println(RandVals.rfloat); System.out.println(RandVals.rdouble); } } ///:~
Эти поля, естественно, не являются частью интерфейса, вместо этого они размещены в static хранилище этого интерфейса.
Инкрементная разработка
Одним из преимуществ наследования является поддержка инкрементной разработки, при помощи которой Вы можете создавать новый код, без внесения возможных ошибок в уже существующий. При этом новые ошибки так же остаются внутри нового кода. Наследуя из существующего, функционального класса и добавляя методы и поля данных, а так же переопределяя существующие методы, Вы оставляете в первозданном виде уже существующий код, тем самым кто-то сможет воспользоваться им нетронутым и без новых ошибок. Если же вдруг случится ошибка, Вы будете знать, что она в вашем коде, и при этом ее найти будет проще и быстрее, чем если бы Вы модифицировали уже существующий и отлаженный код.
То, как чисто разделяются классы может показаться удивительно. Вам не нужен исходный код, поскольку вы можете использовать технологию повторного использования исходного кода. Самое большое, что вам нужно сделать, это импортировать пакет. И это справедливо и для наследования и для композиции.
Важно понимать, что инкрементальная разработка программы всего лишь процесс, похожий на обучение человека. Вы можете анализировать вашу будущую программу сколько угодно, но все равно останутся вопросы которые возникнут только в процессе разработки проекта. Ваш проект будет более удачлив и более гибким, если Вы будете растить его как органическую структуру, как эволюционирующее создание, по сравнению, если бы Вы начали создавать его как единообразной квадратно-стеклянный небоскреб, пытаясь учесть в нем все нюансы.
Несмотря на то, что наследование для экспериментов может быть просто превосходной техникой, однако после некоторой точки стабилизации Вам необходимо окинуть взором вашу иерархию классов и привести ее в разумные размеры. Помните, что наследование определяет связь - Этот новый класс такого же типа, как и тот старый. Ваша программа не должен разбрасываться битами вокруг, а вместо этого создавать и манипулировать объектами многих типов для выражения модели в терминах проблемной области.
Иногда это работает так или иначе
Оказывается, что в некоторых случаях кажется, что все работает правильно без обратного приведения к типу. Один случай достаточно особенный: класс String получает дополнительную помощь от компилятора, что делает работу более гладкой. Даже когда компилятор ожидает объект String, но не получает его, он автоматически вызывает метод toString( ), который определен для Object, и может быть перегружен любым классом Java. Этот метод производит желаемый объект String, который затем используется везде, где он ожидается.
Таким образом, все что вам нужно сделать - создать печать для класса, перегрузив метод toString( ), как показано в следующем примере:
//: c09:Mouse.java
// Перегрузка toString().
public class Mouse { private int mouseNumber; Mouse(int i) { mouseNumber = i; } // Перегружаем Object.toString():
public String toString() { return "This is Mouse #" + mouseNumber; } public int getNumber() { return mouseNumber; } } ///:~
//: c09:WorksAnyway.java
// В особых случаях кажется,
// что вещи работают правильно.
import java.util.*;
class MouseTrap { static void caughtYa(Object m) { Mouse mouse = (Mouse)m; // Приведение от Object
System.out.println("Mouse: " + mouse.getNumber()); } }
public class WorksAnyway { public static void main(String[] args) { ArrayList mice = new ArrayList(); for(int i = 0; i < 3; i++) mice.add(new Mouse(i)); for(int i = 0; i < mice.size(); i++) { // Приведение не нужно, автоматически
// вызовется Object.toString():
System.out.println( "Free mouse: " + mice.get(i)); MouseTrap.caughtYa(mice.get(i)); } } } ///:~
Вы можете видеть toString( ) перегруженную в Mouse. Во втором цикле for в main( ) вы видите инструкцию:
System.out.println("Free mouse: " + mice.get(i));
После знака ‘+’ компилятор ожидает увидеть объект String. get( ) производит Object, поэтому, для получения желаемого String компилятор обязательно вызовет toString( ). К сожалению, вы можете работать с таким видом магии только для String; это не поддерживается для любого другого типа.
Второй подход для упрятывания приведения помещен внутри MouseTrap. Метод caughtYa( ) получает не Mouse, а Object, который затем приводится к Mouse. Конечно, это достаточно дерзко, так как принимаемый Object может быть чем угодно, при передаче в этот метод. Однако если приведение некорректно — если вы передали неправильный тип — вы получите исключение времени выполнения. Это не так хорошо, как проверка времени компиляции, но это все еще устойчиво. Обратите внимание, что при использовании этого метода:
MouseTrap.caughtYa(mice.get(i));
приведение не нужно
Instanceof против эквивалентности объектов Class
При получении информации о типе существует важное различие между любой формой instanceof (это instanceof либо isInstance(), которые приводят к одинаковым результатам) и прямым сравнением объектов Class. Вот пример, демонстрирующий эту разницу:
//: c12:FamilyVsExactType.java // Разница между instanceof и class
class Base {} class Derived extends Base {}
public class FamilyVsExactType { static void test(Object x) { System.out.println("Testing x of type " + x.getClass()); System.out.println("x instanceof Base " + (x instanceof Base)); System.out.println("x instanceof Derived " + (x instanceof Derived)); System.out.println("Base.isInstance(x) " + Base.class.isInstance(x)); System.out.println("Derived.isInstance(x) " + Derived.class.isInstance(x)); System.out.println( "x.getClass() == Base.class " + (x.getClass() == Base.class)); System.out.println( "x.getClass() == Derived.class " + (x.getClass() == Derived.class)); System.out.println( "x.getClass().equals(Base.class)) " + (x.getClass().equals(Base.class))); System.out.println( "x.getClass().equals(Derived.class)) " + (x.getClass().equals(Derived.class))); } public static void main(String[] args) { test(new Base()); test(new Derived()); } } ///:~
Метод test( ) выполняет проверку типа, используя обе формы instanceof. Затем получает ссылку на объект Class и использует выражение "==" и equals( ) для проверки эквивалентности объектов Class. Вот результаты:
Testing x of type class Base x instanceof Base true x instanceof Derived false Base.isInstance(x) true Derived.isInstance(x) false x.getClass() == Base.class true x.getClass() == Derived.class false x.getClass().equals(Base.class)) true x.getClass().equals(Derived.class)) false Testing x of type class Derived x instanceof Base true x instanceof Derived true Base.isInstance(x) true Derived.isInstance(x) true x.getClass() == Base.class false x.getClass() == Derived.class true x.getClass().equals(Base.class)) false x.getClass().equals(Derived.class)) true
Конечно, instanceof и isInstance( ) выдают абсолютно идентичные результаты, также как и equals( ) и "==". Однако, исход работы разный. В общем представлении типа, instanceof говорит, “является ли объект этим классом, либо наследником этого класса?” С другой стороны, если Вы сравниваете объекты Class, используя "==", наследование не имеет значения, это либо точно такой же тип, либо нет.
Инструмент подсказки
Предыдущий пример добавляет “инструмент подсказки” к кнопке. Почти все классы, которые вы будите использовать для создания интерфейса пользователя, наследуются от JComponent, который содержит метод, называемый setToolTipText(String). Поэтому, фактически, для всего, что вы помещаете на форму, все, что вам нужно сделать, это сказать (для объекта jc любого класса, унаследованного от JComponent):
jc.setToolTipText("My tip");
и когда мышь задержится над этим JComponent на предопределенное время, возле мыши всплывет крошечный прямоугольник, содержащий ваш текст.
Интерфейс и реализация
Контроль доступа часто называют скрытием реализации. Завертывание методов и данных в классах в комбинации со скрытием реализации называется часто инкапсуляцией[34]. Результат - это тип данных с определенными характеристиками и поведением.
Контроль доступа накладывает ограничения по двум важным причинам. Первая - необходимость определения того, что клиентский программист может использовать, а что нет. Вы можете построить Ваш внутренний механизм в структуре класса, не беспокоясь о том, что клинетские программисты случайно воспримут внутреннюю реализацию как часть интерфейса, которой они должны использовать.
Отсюда следует вторая причина, это разделение описания и реализации. Если эта структура используется в нескольких программах, но клиентские программисты не могут ничего общения с публичными членами, то Вы можете менять как угодно все, что не является публичным (e.g., “дружественным,” защищенным, либо приватным), без модификаций клиентского кода.
Мы живем в мире объектно-ориентированного программирования, где class обычно описывает “класс объектов,” как Вы можете описать класс рыб или класс птиц. Любой объект, принадлежащий этому классу разделит эти характеристики и поведение. Класс - это описание того, как все объекты этого типа будут выглядеть и действовать.
В оригинальном объектно-ориентированном языке, Simula-67, ключевое слово class использовалось для описания нового типа данных. То же самое ключевое слово используется в большинстве объектно-ориентированных языков. Это основной момент целого языка: создание нового типа данных, который является чем-то большим, чем просто контейнером содержащим данные и методы.
Класс - основная концепция ООП в Java. Это одно из ключевых слов, которое не будет выделено жирным шрифтом в этой книге —чтобы не было путаницы со словом повторяемым также часто, как и “класс.”
Для упрощения, Вы можете выбрать стиль создания классов, в котором сначала располагаются публичные члены, затем защищенные, дружественные и, наконец, частные. Выгода в том что пользователь этого класса сможет, просматривая файл сначала, увидеть сразу то, что важно для него (публичные члены, т.к. к ним может быть получен доступ за пределами файла), и прекратить просмотр при достижении непубличных членов, которые являются частью внутренней реализации:
public class X { public void pub1( ) { /* . . . */ } public void pub2( ) { /* . . . */ } public void pub3( ) { /* . . . */ } private void priv1( ) { /* . . . */ } private void priv2( ) { /* . . . */ } private void priv3( ) { /* . . . */ } private int i; // . . .
}
Это всего лишь частично упростит чтение, т.к. описание и реализация все еще находятся вместе. То есть, Вы еще видите исходный код —реализацию—, поскольку она находится здесь же, в классе. К тому же, документация из комментариев, поддерживаемая утилитой javadoc (описанной в Главе 2) преуменьшает важность чтения кода клиентским программистом. Отображение интерфейса для пользователя класса это, на самом деле, занятие браузера классов, инструмента, чья работа состоит в том, чтобы просмотреть все доступные классы и показать Вам, что Вы можете делать с ними (т.е. показать все доступные члены), в удобной форме. К тому времени, как Вы прочитаете это, такие браузеры должны быть частью любой хорошей среды разработки Java.
Интерфейсы
Ключевое слово interface осуществляет, на шаг дальше, концепцию, реализованную в abstract. Вы можете думать, что это просто чисто abstract класс. Он позволяет создателю заложить форму (структуру) класса: имена методов, списки аргументов, возвращаемые типы, но только не тела методов. Interface также может содержать поля, но все они будут, хотя и косвенно static и final. Interface предоставляет только форму, образ, но не предоставляет его реализацию.
Interface "говорит": "Все классы, реализующие этот особый интерфейс будут выглядеть одинаково". Поэтому, любой код, использующий interface знает, какой из методов может быть вызван для этого interface, впрочем, это все. Так что interface используется в качестве установления "протокола" между классами. (Некоторые ООЯ имеют даже встроенное ключевое слово protocol, делающее то же самое действие.)
Что бы создать interface, используйте ключевое слово interface вместо ключевого слова class. Как и у класса, Вы можете добавить ключевое слово public до interface (но только если этот интерфейс определен в файле с тем же именем) или оставить его пустым, тогда он станет "friendly" и его можно будет использовать только членам одного с ним пакета.
Для создания класса согласованного с особенным interface (или группой interface-ов) используйте ключевое слово implements. Тем самым Вы объявляете "Interface это на что похож мой класс, а теперь я скажу, как он должен работать." Все остальное, кроме этого, выглядит, как наследование. Диаграмма для примера с инструментами:
![](image/tij223.gif)
Как только Вы примените interface, то этот класс сразу же становится обычным и в последствии он может быть расширен обычным способом.
Вы можете выбрать явно объявления методов в interface как public. Но они таковыми являются, даже если Вы этого и не объявляете. Так что, когда Вы реализуете interface, методы из него должны быть определены как public. В противном случае, они будут по умолчанию friendly и Вы будете ограничены в доступе к ним во время наследования, поскольку доступ будет запрещен компилятором.
Это Вы можете увидеть в измененном примере Instrument. Заметьте, что каждый метод в interface строго определен, только так компилятор и позволяет делать. В дополнение, ни один из методов в Instrument не определен как public, но они автоматически public по любому:
//: c08:music5:Music5.java
// Интерфейсы.
import java.util.*;
interface Instrument { // Константа времени компиляции:
int i = 5; // static & final
// Не могут быть получены определения методов:
void play(); // автоматически public
String what(); void adjust(); }
class Wind implements Instrument { public void play() { System.out.println("Wind.play()"); } public String what() { return "Wind"; } public void adjust() {} }
class Percussion implements Instrument { public void play() { System.out.println("Percussion.play()"); } public String what() { return "Percussion"; } public void adjust() {} }
class Stringed implements Instrument { public void play() { System.out.println("Stringed.play()"); } public String what() { return "Stringed"; } public void adjust() {} }
class Brass extends Wind { public void play() { System.out.println("Brass.play()"); } public void adjust() { System.out.println("Brass.adjust()"); } }
class Woodwind extends Wind { public void play() { System.out.println("Woodwind.play()"); } public String what() { return "Woodwind"; } }
public class Music5 { // Не беспокойтесь о типе, добавленные типы
// продолжают работать правильно:
static void tune(Instrument i) { // ...
i.play(); } static void tuneAll(Instrument[] e) { for(int i = 0; i < e.length; i++) tune(e[i]); } public static void main(String[] args) { Instrument[] orchestra = new Instrument[5]; int i = 0; // Приведение к базовому типу во время добавления в массив:
orchestra[i++] = new Wind(); orchestra[i++] = new Percussion(); orchestra[i++] = new Stringed(); orchestra[i++] = new Brass(); orchestra[i++] = new Woodwind(); tuneAll(orchestra); } } ///:~
Этот кусок кода работает точно так же. Не имеет значения, если Вы приводите к базовому типу, к обычному классу Instrument, abstract классу Instrument, или к интерфейсу Instrument. Поведение остается одно и то же. В частности, Вы можете видеть в методе tune( ), что в нем нет никаких доказательств того, что Instrument это обычный класс или abstract класс или же интерфейс. Это и есть цель: каждый подход (принцип) дает программисту различные варианты контроля над путем создания и использования объектов.
Интерфейсы и внутренние классы
Интерфейсы и внутренние классы предоставляют более изощренные пути для организации и контроля над объектами в вашей системе.
C++, к примеру, не поддерживает данный механизм, но грамотный программист в состоянии сэмулировать его. Тот факт, что этот механизм существует в Java, говорит о том, что он настолько важен, что для него даже созданы специальные ключевые слова.
В Главе 7, Вы узнали о ключевом слове abstract, которое позволяет вам создавать один или несколько методов в классе, которые не имеют определений, Вы предоставляете только интерфейс, а его реализация будет осуществлена уже в наследниках. Ключевое слово interface создает полностью абстрактный класс, который не предоставляет никаких реализаций ни одного своего компонента. Вы далее узнаете, что interface есть нечто большее, чем просто абстрактный класс, доведенный до конца абстракции, поскольку он позволяет вам создавать вариации C++ множественного наследования, посредством создания класса, который может быть приведен к базовому типу больше раз, чем к один.
Во-первых, внутренний класс выглядит, как некий механизм скрытия кода: Вы помещаете его внутри другого класса. Вы так же узнаете, что внутренний класс, не только существует сам по себе, а может соединяться с окружающими классами и такой вид кода будет написан вами более чисто и правильно. Поскольку будет представлена новая концепция кода. Но пройдет некоторое время, пока использование внутренних классов будет достаточно комфортным для вас.
Internet против intranet
Web является наиболее общим решением проблемы клиент/сервера, так что это наводит на мысль, что вы можете использовать эту же технологию для решения набора проблем, обычно классических проблем клиент/сервера внутри компании. При традиционном подходе клиент/сервера ваша проблема в том, что у вас разные типы клиентских компьютеров, так как при этом трудно устанавливать новое клиентское программное обеспечение, обе эти проблемы легко решаются с помощью Web броузера и программирования стороны клиента. Когда Web технология используется для информационных сетей, что ограничивается компанией, это называется intranet. Intranet обеспечивает большую безопасность, чем Internet, так как вы физически контролируете доступом к серверам в пределах вашей компании. В терминах обучения это выглядит так, как будто люди однажды поняли общую концепцию броузера, что для них намного легче, чтобы иметь дело с различиями в путях страниц и видов апплетов, так что кривая обучения выглядит понижающейся.
Проблема безопасности выявляет одну из частей, которая, кажется, формируется автоматически в мире программирования стороны клиента. Если ваша программа работает в Internet, вы не знаете под какой платформой вы будите работать, и вы будите очень осторожны и не будете распространять код с ошибками. Вам необходимо нечто кросс-платформенное и безопасное, как язык сценариев или Java.
Если вы работаете в intranet, вы можете иметь набор ограничений. Не секрет, что все ваши машины могут быть под платформой Intel/Windows. В intranet, вы отвечаете за качество вашего собственного кода и можете исправить ошибки, когда они обнаружатся. В дополнение, вы можете уже иметь тело верного кода, который вы будите использовать с более традиционным клиент/серверным подходом, посредством чего вы должны каждый раз физически устанавливать клиентские программы и выполнять обновления. Время, теряемое при установке обновлений, это наиболее непреодолимая причина для перехода к броузеру, поскольку обновления становятся невидимыми и автоматическими. Если вы вовлечены в такую intranet, наиболее лучший подход для укорочения пути - использование существующего базового кода, чем попытки переписать вашу программу на новом языке.
Когда встречаетесь с этим сбивающим с толку множеством решений проблем программирования клиентской стороны, лучший план - это оценка стоимости. Относительно ограничений вашей проблемы: что может сократить путь вашего решения. Так как программирование клиентской стороны - это все-таки программирование, это всегда хорошая идея выбрать быстрый способ разработки для вашего собственного решения. Эта агрессивная позиция необходима, чтобы приготовиться к неизбежным столкновениям с проблемами разработки программы.
Исходный код
Все исходные тексты примеров в данной книге являются зарегистрированными и свободно распространяемыми в виде единого пакета и доступны для загрузки с web-сайта www.BruceEckel.com. Чтобы быть уверенным, что вы получили самую последнюю версию пакета данный сайт является официальным распространителем исходного кода и электронной версии книги. Вероятно вы найдете зеркала данного сайта (и адреса некоторых из них приведены на нашем сайте www.BruceEckel.com), но убедитесь, что зеркало содержит самую последнюю редакцию. Допустимо свободно использовать примеры книги для обучения или в других подобных целях. Основная идея авторства на приведенный исходный код заключается в том, чтобы убедиться, что исходный код процитирован верно, и не используется нелегального в других изданиях. (Однако код содержащий авторское право может быть свободно использован в других печатных изданиях). В каждом примере будет приведена следующая информация для зашиты авторских прав:
//:! :CopyRight.txt Copyright ©2000 Bruce Eckel Source code file from the 2nd edition of the book "Thinking in Java." All rights reserved EXCEPT as allowed by the following statements: You can freely use this file for your own work (personal or commercial), including modifications and distribution in executable form only. Permission is granted to use this file in classroom situations, including its use in presentation materials, as long as the book "Thinking in Java" is cited as the source. Except in classroom situations, you cannot copy and distribute this code; instead, the sole distribution point is http://www.BruceEckel.com (and official mirror sites) where it is freely available. You cannot remove this copyright and notice. You cannot distribute modified versions of the source code in this package. You cannot use this file in printed media without the express permission of the author. Bruce Eckel makes no representation about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty of any kind, including any implied warranty of merchantability, fitness for a particular purpose or non-infringement. The entire risk as to the quality and performance of the software is with you. Bruce Eckel and the publisher shall not be liable for any damages suffered by you or any third party as a result of using or distributing software. In no event will Bruce Eckel or the publisher be liable for any lost revenue, profit, or data, or for direct, indirect, special, consequential, incidental, or punitive damages, however caused and regardless of the theory of liability, arising out of the use of or inability to use software, even if Bruce Eckel and the publisher have been advised of the possibility of such damages. Should the software prove defective, you assume the cost of all necessary servicing, repair, or correction. If you think you've found an error, please submit the correction using the form you will find at www.BruceEckel.com. (Please use the same form for non-code errors found in the book.) ///:~
Допустимо использовать код либо в ваших проектах, либо для преподавания (включая ваш материал) до тех пор, пока сохраняется информация об авторском праве.
Искажение имен и сигнатура функций
JNI использует преобразование имен (называемое name mangling - искажением имен) собственных методов. Это важно, так как это является частью механизма, с помощью которого виртуальная машина компонует Java вызовы собственных методов. В основном все собственные методы начинаются со слова "Java", за которым слкдует имя класса в котором присутствует собственный вызов Java, следом идет имя Java метода. Символ подчеркивания используется как разделитель. Если собственный Java метод перекрывается, то к имени также добавляется сигнатура функции; вы можете видеть собственную сигнатуру в комментариях предшествующих прототипу. Дополнительную информацию об искажении имен и сигнатурах собственных методов можно найти в документации по JNI.
Использование Appletviewer
JDK от SUN (бесплатно доступен на java.sun.com) имеет инструмент, называемый Appletviewer, который выбирает ярлык <applet> из HTML файла и запускает апплет без отображения окружающего HTML текста. Из-за того, что Appletviewer игнорирует все, кроме ярлыка APPLET, вы можете поместить эти ярлыки в исходный код Java как комментарий:
// <applet code=MyApplet width=200 height=100>
// </applet>
Этим способом вы можете запустить “appletviewer MyApplet.java” и вам не нужно будет создавать маленький HTML файл для запуска теста. Например, вы можете добавить закомментированный HTML ярлык в Applet1.java:
//: c13:Applet1b.java
// Встроенный ярлык апплета для Appletviewer.
// <applet code=Applet1b width=100 height=50>
// </applet>
import javax.swing.*; import java.awt.*;
public class Applet1b extends JApplet { public void init() { getContentPane().add(new JLabel("Applet!")); } } ///:~
Теперь вы можете вызвать апплет командой
appletviewer Applet1b.java
В этой книге эта форма будет использоваться для простого тестирования апплетов. Вскоре вы увидите другой способ кодирования, который позволит вам выполнять апплеты из командной строки без Appletviewer.
Использование других компонентов
Когда бы вы ни пожелали использовать предопределенные классы в вашей программе, компилятор должен знать, где они расположены. Конечно, класс может существовать в том же самом файле исходного кода, из которого он вызывается. В этом случае вы просто используете класс — даже если класс определен дальше по файлу. Java устранит проблему “ранней ссылки”, так что вам нет необходимости думать об этом.
Что можно сказать о классе, который существует в другом файле? Вы можете подумать, что компилятор должен быть достаточно умным, чтобы найти его, но это проблема. Вообразите, что вы хотите использовать класс с определенным именем, но существует более одного определения этого класса (по-видимому, это разные определения). Или хуже, вообразите, что вы написали программу и, когда вы строили ее, вы добавили новый класс в вашу библиотеку, который конфликтует с именем существующего класса.
Для решения этой проблемы вы должны устранить любую потенциальную двусмысленность. Это выполняется путем точного сообщения компилятору Java классов, которые вы хотите использовать с помощью ключевого слова import. import говорит компилятору о введении пакета, который является библиотекой классов. (В других языках библиотеки могут состоять из функций и данных так же, как и из классов, но помните, что весь код в Java должен быть написан внутри класса.)
Большую часть времени вы будите использовать компоненты из стандартных библиотек Java, которые идут вместе с компилятором. Поэтому, вам нет необходимости заботиться о длинных, реверсированных доменных именах; вы просто скажите, например:
import java.util.ArrayList;
чтобы сказать компилятору, что вы хотите использовать Java класс ArrayList. Однако util содержит несколько классов, и вы можете использовать некоторые из них, не объявляя их точно. Это легче всего выполнить, используя ‘*’, чтобы указать чистую карту:
import java.util.*;
Это более общий способ для импорта набора классов, в отличие от индивидуального импорта каждого класса.
Использование импорта для изменения поведения
В языке C существует условная компиляция, которая позволяет устанавливать различное поведение Вашего кода без изменения самого кода. В Java такой возможности нет. Причина, по которой эта функция отсутствует в Java, возможно в том, что в языке C эта функция в основном использовалась для создания кросс-платформенных приложений: компилировались различные куски кода, в зависимости от платформы, на которой они работали. Поскольку Java автоматически поддерживает кросс-платформенность, необходимости в такой функции нет.
Однако, есть и другая необходимость в условной компиляции. Самое распространенное использование - отладочный код. Отладка включается в процессе разработки, и отключается в конечном продукте. Аллен Холуб (Allen Holub) (www.holub.com) предложил идею - использования пакетов, для имитации условной компиляции. Он использовал это для создания Java-версии очень полезного механизма контроля (assertion) из языка C, с помощью которого Вы можете сказать “это должно быть истинно” либо “это должно быть ложно” и, если выражение не удовлетворяет этому контролю, Вы узнаете об этом. Такой инструмент является очень полезным во время отладки.
Вот класс, который Вы можете использовать для отладки:
//: com:bruceeckel:tools:debug:Assert.java
// Инструмент контроля для отладки.
package com.bruceeckel.tools.debug;
public class Assert { private static void perr(String msg) { System.err.println(msg); } public final static void is_true(boolean exp) { if(!exp) perr("Assertion failed"); } public final static void is_false(boolean exp){ if(exp) perr("Assertion failed"); } public final static void is_true(boolean exp, String msg) { if(!exp) perr("Assertion failed: " + msg); } public final static void is_false(boolean exp, String msg) { if(exp) perr("Assertion failed: " + msg); } } ///:~
Этот класс просто инкапсулирует булевские тесты, и печатает сообщение об ошибке, если эти тесты завершаются неудачно. В Главе 10, Вы познакомитесь с более изощренным инструментом для борьбы с ошибками, называемым обработка исключений, а пока метод perr( ) будет отлично работать.
Результат отправляется на консоль в поток стандартных ошибок - System.err.
Когда Вам необходимо использовать этот класс, Вы добавляете одну строку в свою программу:
import com.bruceeckel.tools.debug.*;
Для отключения этого контроля, Вы можете использовать код, где реализован второй класс Assert, находящийся в другом пакете:
//: com:bruceeckel:tools:Assert.java
// Отключение контроля
package com.bruceeckel.tools;
public class Assert { public final static void is_true(boolean exp){} public final static void is_false(boolean exp){} public final static void is_true(boolean exp, String msg) {} public final static void is_false(boolean exp, String msg) {} } ///:~
Так, если Вы измените предыдущее выражение import на:
import com.bruceeckel.tools.*;
программа больше не будет печатать контрольные данные. Вот пример:
//: c05:TestAssert.java
// Демонстрация инструмента контроля.
// Комментируете первую или вторую строчку и
// получаете различные результаты:
import com.bruceeckel.tools.debug.*; // import com.bruceeckel.tools.*;
public class TestAssert { public static void main(String[] args) { Assert.is_true((2 + 2) == 5); Assert.is_false((1 + 1) == 2); Assert.is_true((2 + 2) == 5, "2 + 2 == 5"); Assert.is_false((1 + 1) == 2, "1 +1 != 2"); } } ///:~
Изменением импортируемого пакета, Вы производите переход от отладочной к конечной версии. Эта техника может быть использована для создания отладочного кода любого типа.
Использование литералов класса
Интересно посмотреть, как будет выглядеть пример PetCount.java, переписанный с использованием литералов класса. В результате код получается гораздо лучше:
//: c12:PetCount2.java // Использование литералов класса. import java.util.*;
public class PetCount2 { 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); if(o instanceof Pet) ((Counter)h.get("class Pet")).i++; if(o instanceof Dog) ((Counter)h.get("class Dog")).i++; if(o instanceof Pug) ((Counter)h.get("class Pug")).i++; if(o instanceof Cat) ((Counter)h.get("class Cat")).i++; if(o instanceof Rodent) ((Counter)h.get("class Rodent")).i++; if(o instanceof Gerbil) ((Counter)h.get("class Gerbil")).i++; if(o instanceof Hamster) ((Counter)h.get("class Hamster")).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); } } } ///:~
Здесь, массив typenames был удален за счет того, что строка имени типа достается из объекта Class. Заметьте, что система может различать классы и интерфейсы.
Вы также видите, что создание petTypes не нужно окружать блоком try, т.к. оно вычисляется во время компиляции, и, потому, не может выбросить никаких исключений, в отличие от метода Class.forName().
Когда объекты Pet динамически созданы, Вы видите, что случайное число ограничено 1 и petTypes.length и не включает 0. Это потому, что 0 ссылается на Pet.class, и, наверное, базовый класс Pet нам не интересен. Однако, т.к. Pet.class является частью petTypes, в результате, все классы Pet посчитаны.
Использование ограниченных ресурсов
Можете думать о программе с одним процессом как об одиноком объекте, решающим ваши проблемы и выполняющем только одно действие за единицу времени. Из-за того, что это единственный объект, вам никогда не придется думать о проблеме использования одного и того же ресурса разными объектами в одно и то же время, подобно тому, как два человека пытаются припарковаться в одном и том же месте, или пройти через дверь в одно и то же время, или даже говорить в одно и то же время.
Столкновения при использовании ресурса должны быть предотвращены, иначе у вас будет два процесса, пытающихся одновременно изменить значение одного денежного вклада в базе данных банка, или печатать на один принтер, или изменять значения переменной и т.д.
Использование операторов Java
Оператор принимает один или больше аргументов и производит новое значение. Аргументы располагаются по-другому, в отличие от обычного вызова метода, но эффект тот же самый. Вы будете чувствовать себя остаточно комфортно с общей концепцией операторов, основываясь на ваш предыдущий опыт программирования. Сложение (+), вычитание и унарный минус (-), умножение (*), деление (/) и присвоение (=) всегда работают так же, как и в других языках программирования.
Все операции производят значения из своих операндов. В дополнение, оператор может сменить значение операнда. Это называется побочным действием. Самое общее в использовании операторов, которые модифицируют свои операнды, то, что они генерируют побочное действие, но вы должны держать в уме, что производимое значение доступно для вашего использования только в операторах без побочных действий.
Почти все операторы работают только с примитивными типами. Исключение составляют ‘=’, ‘==’ и ‘!=’, которые работают со всеми объектами (и являются смущающим местом для объектов). Вдобавок, класс String поддерживает ‘+’ и ‘+=’.
Использование процессов для пользовательского интерфейса
Вот теперь появилась возможность разрешить проблему из примера Counter1.java с процессами. Решение заключается в правильном размещении подзадачи, т.е. цикла, расположенного внутри go(), который поместим внутрь метода run(). Когда пользователь нажимает кнопку start
процесс запускается, но затем создание процесса завершается, и, хотя процесс запущен, основная работа программы, которая заключается в реагировании на действия пользователя, продолжается. Вот решение этой проблемы:
//: c14:Counter2.java
// A responsive user interface with threads.
// <applet code=Counter2 width=300 height=100>
// </applet>
import javax.swing.*; import java.awt.*; import java.awt.event.*; import com.bruceeckel.swing.*;
public class Counter2 extends JApplet { private class SeparateSubTask extends Thread { private int count = 0; private boolean runFlag = true; SeparateSubTask() { start(); } void invertFlag() { runFlag = !runFlag; } public void run() { while (true) { try { sleep(100); } catch(InterruptedException e) { System.err.println("Interrupted"); } if(runFlag) t.setText(Integer.toString(count++)); } } } private SeparateSubTask sp = null; private JTextField t = new JTextField(10); private JButton start = new JButton("Start"), onOff = new JButton("Toggle"); class StartL implements ActionListener { public void actionPerformed(ActionEvent e) { if(sp == null) sp = new SeparateSubTask(); } } class OnOffL implements ActionListener { public void actionPerformed(ActionEvent e) { if(sp != null) sp.invertFlag(); } } public void init() { Container cp = getContentPane(); cp.setLayout(new FlowLayout()); cp.add(t); start.addActionListener(new StartL()); cp.add(start); onOff.addActionListener(new OnOffL()); cp.add(onOff); } public static void main(String[] args) { Console.run(new Counter2 (), 300, 100); } } ///:~
Counter2 совершенно прямолинейная программа, основное предназначение которой в создании пользовательского интерфейса. Но теперь, когда пользователь нажал кнопку start, код обработки событий не вызовет метод, а будет создан процесс SeparateSubTask, после чего цикл обработки события Counter2 продолжиться.
Класс SeparateSubTask простое расширение от Thread с конструктором, который запускает процесс вызовом start(), а затем run(), который в сущности содержит код от go() из примера Counter1.java.
Из-за того, что SeparateSubTask внутренний класс, он может напрямую обращаться к JTextField t в Counter2; можно видеть как это происходит внутри run(). Поле t во внешнем классе определено как private, поскольку SeparateSubTask может получить к нему доступ без применения специальных разрешений, и всегда желательно делать поле настолько private, насколько это возможно, для того чтобы оно не могло быть случайно изменено извне вашего класса.
Когда нажимаем кнопку onOff она меняет runFlag внутри объекта SeparateSubTask. Данный процесс (когда он проверяет флаг) может самостоятельно остановиться или запуститься. Нажатие кнопки onOff вызывает тут же заметную реакцию. Конечно, в реальности реакция не мгновенная, счетчик остановится только тогда, когда процесс получит свой квант времени от CPU и проверит изменение флага.
Можно видеть, что внутренний класс SeparateSubTask есть private, а это значит, что к его полям и методам существует доступ по умолчанию (за исключением run(), который должен быть public поскольку он public в классе предка). Внутренний Private класс недоступен никому, за исключением Counter2 и эти два класса крепко связаны. Всегда, когда вы замечаете классы, которые оказываются крепко связанными друг с другом, рассмотрите возможность оптимизации своего кода и поддержки за счет использования внутренних классов.
Использование слушающих адаптеров для упрощения
В приведенной выше таблице вы можете видеть, что некоторые интерфейсы слушателей имеют только один метод. Они очень просты для реализации, так как вы реализуете его, только когда напишите этот определенный метод. Однако интерфейсы слушателей, имеющие несколько методов, менее приятны в использовании. Например, то, что вы должны всегда делать при создании приложения, это обеспечение WindowListener для JFrame, так что когда вы получаете событие windowClosing( ), вы могли бы вызвать System.exit( ) для выхода из приложения. Но так как WindowListener - это интерфейс, вы должны реализовать все другие методы, даже если они ничего не делают. Это может раздражать.
Для решения проблемы некоторые (но не все) из интерфейсов слушателей, которые имеют более одного метода, снабжаются адаптерами, имена которых вы можете видеть в приведенной выше таблице. Каждый адаптер обеспечивает по умолчанию пустые методы для каждого метода интерфейса. Поэтому все, что вам нужно сделать - это наследовать от адаптера и перекрыть только те методы, которые нужно изменить. Например, типичный WindowListener, который вы будете использовать, выглядит так (помните, что это было помещено внутрь класса Console в com.bruceeckel.swing):
class MyWindowListener extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); } }
Основное назначение адаптеров состоит в облегчении создания слушающих классов.
Однако есть темная сторона адаптеров, из-за которой можно попасть в ловушку. Предположим, что вы написали WindowAdapter как показано выше:
class MyWindowListener extends WindowAdapter { public void WindowClosing(WindowEvent e) { System.exit(0); } }
Это не работает и это может свести вас с ума в попытке узнать почему, так как все прекрасно компилируется и запускается — за исключением того, что окно при закрытии окна не происходит выход из программы. Вы видите проблему? Она в имени метода WindowClosing( ) вместо windowClosing( ). Однако это не тот метод, который вызывается при закрытии окна, так что вы не получаете желаемый результат. Несмотря на неудобства, интерфейс гарантирует, что методы будут реализованы правильно.
Использование ссылок URL внутри апплета
Апплеты могут отобразить любую ссылку URL через Web браузер, внутри которого запускается апплет. Вы можете выполнить это с помощью следующей строки:
getAppletContext().showDocument(u);
в которой u - это объект URL. Вот простой пример, который перенаправляет Вас на другую Web страничку. Хотя, Вы перенаправляетесь на HTML страничку, Вы также можете перенаправить на программу CGI.
//: c15:ShowHTML.java
// <applet code=ShowHTML width=100 height=50>
// </applet>
import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.net.*; import java.io.*; import com.bruceeckel.swing.*;
public class ShowHTML extends JApplet { JButton send = new JButton("Go"); JLabel l = new JLabel(); public void init() { Container cp = getContentPane(); cp.setLayout(new FlowLayout()); send.addActionListener(new Al()); cp.add(send); cp.add(l); } class Al implements ActionListener { public void actionPerformed(ActionEvent ae) { try { // Это может быть программа CGI вместо
// HTML странички.
URL u = new URL(getDocumentBase(), "FetcherFrame.html"); // Отображается вывод URL используя
// Web браузер, как обычную страничку:
getAppletContext().showDocument(u); } catch(Exception e) { l.setText(e.toString()); } } } public static void main(String[] args) { Console.run(new ShowHTML(), 100, 50); } } ///:~
Красота класса URL в том, насколько сильно Вас он защищает от тонкостей реализации стороны сервера. Вы можете присоединиться к Web серверу практически ничего не зная, что происходит у него внутри.
Использование существующего кода
Наиболее легкий метод реализовать собственные методы JNI - начать с написания прототипов собственных методов в Java классе, компиляции данного класса и запуске полученного .class файла используя javah. Но что делать если уже имеется большой код который хотелось бы вызывать из Java? Переименование всех вызовов функций в нашей DLL для соответствия именованиям JNI не самый реальный путь. Наиболее приемлемое решение заключается в написании оболочки для вызова функций оригинальной DLL. В этом случае Java код вызывает функции из новой DLL которая в свою очередь вызывает функции из оригинальной DLL. Данный путь не так уж бессмыслен, в большинстве случаев вам все равно придется сделать это, так как вам необходимо вызывать функции JNI в описании объектов до того как они будут использованы.
Использование удаленных объектов
Главная цель RMI состоит в упращении использования удаленных объектов. Вы должны сделать только самую важную вещь в вашей клиентской программе: это поиск и получение удаленного интерфейса с сервера. Во всем остальном - это обычное программирование на Java: посылка сообщений объекту. Ниже приведена программа, использующая PerfectTime:
//: c15:rmi:DisplayPerfectTime.java
// Испольование удаленного объекта PerfectTime.
package c15.rmi; import java.rmi.*; import java.rmi.registry.*;
public class DisplayPerfectTime { public static void main(String[] args) throws Exception { System.setSecurityManager( new RMISecurityManager()); PerfectTimeI t = (PerfectTimeI)Naming.lookup( "//peppy:2005/PerfectTime"); for(int i = 0; i < 10; i++) System.out.println("Perfect time = " + t.getPerfectTime()); } } ///:~
Строка идентификатора такая же, как и та, что использовалась при регистрации объекта с помощью Naming, а первая часть представляет URL и номер порта. Так как вы используете URL, вы можете также указать машину в Internet.
То, что возвращается из Naming.lookup( ) должно быть преобразовано к удаленному интерфейсу, а не к классу. Если вы будите использовать класс, вы получите исключение.
Вы виите вызов метода
t.getPerfectTime()
так как вы имеете ссылку на удаленный объект, то с точки зрения программирования, это не отличается от работы с локальным объектом (с одним отличием: удаленные методы выбрасывают RemoteException).
Использование устойчивости
Достаточно привлекательно использовать технологию сериализации для хранения некоторых состояний вашей программы, чтобы в последствии вы могли легко восстановить программу до текущего состояния. Но прежде, чем сделать это, необходимо ответить на некоторые вопросы. Что случится, если вы сериализуете два объекта, оба из которых имеют ссылки на один объект? Когда вы восстановите эти два объекта из их сериализованного состояния, будите ли вы иметь только один экземпляр третьего объекта? Что, если вы сериализуете два объекта в различные файлы, а десериализуете их в различных частях кода?
Вот пример, показывающий эту проблему:
//: c11:MyWorld.java
import java.io.*; import java.util.*;
class House implements Serializable {}
class Animal implements Serializable { String name; House preferredHouse; Animal(String nm, House h) { name = nm; preferredHouse = h; } public String toString() { return name + "[" + super.toString() + "], " + preferredHouse + "\n"; } }
public class MyWorld { public static void main(String[] args) throws IOException, ClassNotFoundException { House house = new House(); ArrayList animals = new ArrayList(); animals.add( new Animal("Bosco the dog", house)); animals.add( new Animal("Ralph the hamster", house)); animals.add( new Animal("Fronk the cat", house)); System.out.println("animals: " + animals);
ByteArrayOutputStream buf1 = new ByteArrayOutputStream(); ObjectOutputStream o1 = new ObjectOutputStream(buf1); o1.writeObject(animals); o1.writeObject(animals); // Запись второго класса
// Запись в другой поток:
ByteArrayOutputStream buf2 = new ByteArrayOutputStream(); ObjectOutputStream o2 = new ObjectOutputStream(buf2); o2.writeObject(animals); // Теперь получаем назад:
ObjectInputStream in1 = new ObjectInputStream( new ByteArrayInputStream( buf1.toByteArray())); ObjectInputStream in2 = new ObjectInputStream( new ByteArrayInputStream( buf2.toByteArray())); ArrayList animals1 = (ArrayList)in1.readObject(); ArrayList animals2 = (ArrayList)in1.readObject(); ArrayList animals3 = (ArrayList)in2.readObject(); System.out.println("animals1: " + animals1); System.out.println("animals2: " + animals2); System.out.println("animals3: " + animals3); } } ///:~
Одна вещь, которая интересна здесь, состоит в возможности использовать сериализацию объекта через массив байт, как способ выполнения “глубокого копирования” любого объекта с интерфейсом Serializable. (Глубокое копирование означает, что вы дублируете всю паутину объектов, а не просто основной объект и принадлежащие ему ссылки.) Более глубоко копирование освещено в Приложении А.
Объекты Animal содержат поля типа House. В main( ) создается ArrayList из этих Animal, и он сериализуется дважды в один поток, а затем снова в другой поток. Когда это десериализуется и распечатается, вы увидите следующий результат одного запуска (объекты будут располагаться в разных участках памяти при каждом запуске):
animals: [Bosco the dog[Animal@1cc76c], House@1cc769 , Ralph the hamster[Animal@1cc76d], House@1cc769 , Fronk the cat[Animal@1cc76e], House@1cc769 ] animals1: [Bosco the dog[Animal@1cca0c], House@1cca16 , Ralph the hamster[Animal@1cca17], House@1cca16 , Fronk the cat[Animal@1cca1b], House@1cca16 ] animals2: [Bosco the dog[Animal@1cca0c], House@1cca16 , Ralph the hamster[Animal@1cca17], House@1cca16 , Fronk the cat[Animal@1cca1b], House@1cca16 ] animals3: [Bosco the dog[Animal@1cca52], House@1cca5c , Ralph the hamster[Animal@1cca5d], House@1cca5c , Fronk the cat[Animal@1cca61], House@1cca5c ]
Конечно, вы ожидаете, что десериализованные объекты имеют адреса, отличные от первоначальных. Но обратите внимание, что в animals1 и animals2 появляется один и тот же адрес, включая ссылки на объект House, который они оба разделяют. С другой стороны, когда восстанавливается animals3, у системы нет способа узнать, что объекты в этом потоке являются алиасами объектов первого потока, так что при этом создается полностью отличная паутина объектов.
Если вы сериализовали что-то в единственный поток, вы будете способны восстановить ту же паутину объектов, которую вы записали, без случайного дублирования объектов. Конечно, вы можете изменить состояние ваших объектов в промежутке между временем первой и последней записи, но это ваше дело — объекты будут записаны не зависимо от того, в каком бы состоянии они не были (и со всеми соединениями, которые они имеют с другими объектами) в то время, когда вы сериализуете их.
Самым безопасным для сохранение состояния системы является сериализация, как “атомная” операция. Если вы сериализуете какие-то вещи, выполняете какую-то работу и сериализуйте еще, и т.д., то вы не будете держать систему в безопасности. Вместо этого поместите все объекты, которые относятся к состоянию вашей системы, в единственный контейнер и просто запишите этот контейнер в одной операции. Затем вы можете восстановить его так же единственным вызовом метода.
Следующий пример относится к мнимой вспомогательной системе компьютерного дизайна (CAD), который демонстрирует такой подход. Кроме того, здесь примешана проблема полей static — если вы взглянете на документацию, вы увидите, что если Class является сериализуемым, то должно быть легким хранение static поля простой сериализацией объекта Class. Тем не менее, такой подход выглядит достаточно важным.
//: c11:CADState.java
// Запись и восстановление состояния
// симулятора системы CAD.
import java.io.*; import java.util.*;
abstract class Shape implements Serializable { public static final int RED = 1, BLUE = 2, GREEN = 3; private int xPos, yPos, dimension; private static Random r = new Random(); private static int counter = 0; abstract public void setColor(int newColor); abstract public int getColor(); public Shape(int xVal, int yVal, int dim) { xPos = xVal; yPos = yVal; dimension = dim; } public String toString() { return getClass() + " color[" + getColor() + "] xPos[" + xPos + "] yPos[" + yPos + "] dim[" + dimension + "]\n"; } public static Shape randomFactory() { int xVal = r.nextInt() % 100; int yVal = r.nextInt() % 100; int dim = r.nextInt() % 100; switch(counter++ % 3) { default: case 0: return new Circle(xVal, yVal, dim); case 1: return new Square(xVal, yVal, dim); case 2: return new Line(xVal, yVal, dim); } } }
class Circle extends Shape { private static int color = RED; public Circle(int xVal, int yVal, int dim) { super(xVal, yVal, dim); } public void setColor(int newColor) { color = newColor; } public int getColor() { return color; } }
class Square extends Shape { private static int color; public Square(int xVal, int yVal, int dim) { super(xVal, yVal, dim); color = RED; } public void setColor(int newColor) { color = newColor; } public int getColor() { return color; } }
class Line extends Shape { private static int color = RED; public static void serializeStaticState(ObjectOutputStream os) throws IOException { os.writeInt(color); } public static void deserializeStaticState(ObjectInputStream os) throws IOException { color = os.readInt(); } public Line(int xVal, int yVal, int dim) { super(xVal, yVal, dim); } public void setColor(int newColor) { color = newColor; } public int getColor() { return color; } }
public class CADState { public static void main(String[] args) throws Exception { ArrayList shapeTypes, shapes; if(args.length == 0) { shapeTypes = new ArrayList(); shapes = new ArrayList(); // Добавляем ссылку в объект класса:
shapeTypes.add(Circle.class); shapeTypes.add(Square.class); shapeTypes.add(Line.class); // Создаем какие-то образы:
for(int i = 0; i < 10; i++) shapes.add(Shape.randomFactory()); // Устанавливаем все статические цвета в GREEN:
for(int i = 0; i < 10; i++) ((Shape)shapes.get(i)) .setColor(Shape.GREEN); // Запись вектора состояния:
ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream("CADState.out")); out.writeObject(shapeTypes); Line.serializeStaticState(out); out.writeObject(shapes); } else { // Есть аргументы командной строки
ObjectInputStream in = new ObjectInputStream( new FileInputStream(args[0])); // Читаем в том же порядке, в котором была запись:
shapeTypes = (ArrayList)in.readObject(); Line.deserializeStaticState(in); shapes = (ArrayList)in.readObject(); } // Отображаем образы:
System.out.println(shapes); } } ///:~
Класс Shape реализует интерфейс Serializable, так что все, что наследуется от Shape, автоматически реализует Serializable. Каждый Shape содержит данные, а каждый наследуемый от Shape класс содержит статическое поле, определяющее цвет всех этих Shape. (Помещение статического поля в базовый класс приведет к тому, что будет существовать только одно поле, так как статическое поле не дублируется для наследуемых классов.) Методы базового класса могут быть перекрыты для установки цвета для различных типов (статические методы не имеют динамических ограничений, так что это обычные методы). Метод randomFactory( ) создает различные объекты Shape при каждом вызове, используя случайные значения для данных Shape.
Circle и Square являются прямым расширением Shape; отличия только в том, что Circle инициализирует color в точке определения, а Square инициализирует его в конструкторе. Дискуссию относительно Line пока отложим.
В main( ) используется один ArrayList для хранения объектов Class, а другой для хранения образов. Если вы не задействовали аргумент командной строки, создается shapeTypes ArrayList, и добавляются объекты Class, а затем создается ArrayList shapes, и в него добавляются объекты Shape. Далее, все значения static color устанавливаются равными GREEN, и все сериализуется в файл CADState.out.
Если вы укажите аргумент командной строки (предположительно CADState.out), этот файл будет открыт и использован для восстановления состояния программы. В обеих ситуациях распечатывается результирующий ArrayList из Shape. Вот результат одного запуска:
>java CADState [class Circle color[3] xPos[-51] yPos[-99] dim[38] , class Square color[3] xPos[2] yPos[61] dim[-46] , class Line color[3] xPos[51] yPos[73] dim[64] , class Circle color[3] xPos[-70] yPos[1] dim[16] , class Square color[3] xPos[3] yPos[94] dim[-36] , class Line color[3] xPos[-84] yPos[-21] dim[-35] , class Circle color[3] xPos[-75] yPos[-43] dim[22] , class Square color[3] xPos[81] yPos[30] dim[-45] , class Line color[3] xPos[-29] yPos[92] dim[17] , class Circle color[3] xPos[17] yPos[90] dim[-76] ]
>java CADState CADState.out [class Circle color[1] xPos[-51] yPos[-99] dim[38] , class Square color[0] xPos[2] yPos[61] dim[-46] , class Line color[3] xPos[51] yPos[73] dim[64] , class Circle color[1] xPos[-70] yPos[1] dim[16] , class Square color[0] xPos[3] yPos[94] dim[-36] , class Line color[3] xPos[-84] yPos[-21] dim[-35] , class Circle color[1] xPos[-75] yPos[-43] dim[22] , class Square color[0] xPos[81] yPos[30] dim[-45] , class Line color[3] xPos[-29] yPos[92] dim[17] , class Circle color[1] xPos[17] yPos[90] dim[-76] ]
Вы можете видеть, что значения xPos, yPos и dim были успешно сохранены и восстановлены, но при восстановлении статической информации произошли какие-то ошибки. Везде на входе имели “3”, но на выходе этого не получили. Circle имеет значение 1 (RED, как это определено), а Square имеет значение 0 (Помните, что он инициализировался в конструкторе). Это похоже на то, что static не сериализовался совсем! Это верно, несмотря на то, что класс Class реализует интерфейс Serializable, он не делает того, что вы от него ожидаете. Так что если вы хотите сериализовать statics, вы должны сделать это сами.
Это то, для чего нужны статические методы serializeStaticState( ) и deserializeStaticState( ) в Line. Вы можете видеть, что они явно вызываются как часть процесса сохранения и восстановления. (Обратите внимание, что порядок записи в файл сериализации и чтения из него должен сохранятся). Таким образом, чтобы CADState.java работал корректно, вы должны:
Добавить serializeStaticState( ) и deserializeStaticState( ) к образам.
Удалить ArrayList shapeTypes и весь код, относящийся к нему.
Добавить вызов новых статических методов сериализации и десериализации образов.
Другую проблему вы можете получить, думая о безопасности, так как сериализация сохраняет данные с модификатором private. Если вы имеете проблемы безопасности, эти поля должны помечаться, как transient. Затем вы должны разработать безопасный способ для хранения такой информации, чтобы когда вы делали восстановление, вы могли установить эти private переменные.
Использование Windows Explorer
Если вы используете Windows, вы можете упростить процесс запуска Java программ из командной строки путем конфигурирования Windows Explorer — файл-менеджера в Windows, не Internet Explorer — так что теперь вы можете просто дважды щелкнуть мышкой на файле .class для его выполнения. Для этого нужно выполнить несколько шагов.
Первое, загрузить и установить язык программирования Perl с ww.Perl.org. Вы найдете инструкцию и документацию языка на этом сайте.
Далее, создать следующий сценарий без первой и последней строки (этот сценарий является частью пакета исходного кода книги):
//:! c13:RunJava.bat
@rem = '--*-Perl-*-- @echo off perl -x -S "%0" %1 %2 %3 %4 %5 %6 %7 %8 %9 goto endofperl @rem '; #!perl $file = $ARGV[0]; $file =~ s/(.*)\..*/\1/; $file =~ s/(.*\\)*(.*)/$+/; ?java $file?; __END__ :endofperl ///:~
Теперь откройте Windows Explorer, выберете “Вид (View)”, “Параметры (Folder Options)”, затем щелкните на закладке “Типы файлов (File Types)”. Нажмите кнопку “Новый тип (New Type)”. В качестве “Описания (Description of Type)” введите “Java class file”. В качестве “Стандартного расширения (Associated Extension)” введите “class”. Под пунктом “Действия (Actions)” нажмите кнопку “Создать (New)”. В пункте “Действие (Action)” введите “Open”, а в поле “Приложение, запускающее действие (Application used to perform action)” введите строку, как показано здесь:
"c:\aaa\Perl\RunJava.bat" "%L"
Вы должны настроить путь перед “RunJava.bat”, чтобы он соответствовал месту, в которое вы поместили пакетный файл.
Как только вы выполните эту установку, вы можете запускать любую программу Java, просто выполнив двойной щелчок на .class файле, содержащем main( ).
Источники и приемники данных
Почти все оригинальные классы потоков ввода/вывода имеют соответствующие классы Reader и Writer для обеспечения родных манипуляций в Unicode. Однако есть некоторые места, где байт-ориентированные InputStream и OutputStream являются корректным решением; на практике библиотеки из java.util.zip скорее байт-ориентированные, чем символьно-ориентированные. Так что наиболее разумным подходом будет попытка использования классов Reader и Writer там, где это возможно, и вы обнаружите ситуации, когда будете вынуждены использовать байт-ориентированные библиотеки, потому что ваш код не будет компилироваться.
Здесь приведена таблица, которая показывает соответствие между источниками и приемниками информации (то есть, куда данные приходят на физическом уровне или куда они уходят) в двух иерархиях.
InputStream | Reader конвертер: InputStreamReader |
OutputStream | Writer конвертер: OutputStreamWriter |
FileInputStream | FileReader |
FileOutputStream | FileWriter |
StringBufferInputStream | StringReader |
(соответствующего класса нет) | StringWriter |
ByteArrayInputStream | CharArrayReader |
ByteArrayOutputStream | CharArrayWriter |
PipedInputStream | PipedReader |
PipedOutputStream | PipedWriter |
В общем случае вы обнаружите, что интерфейсы для этих двух различных иерархий сходны, если не идентичны.
Итерации
while, do-while и for управляют циклом и иногда классифицируются как итерационные инструкции. Инструкция повторяется до тех пор, пока управляющее логическое выражение не станет ложным. Форма цикла while следующая:
while(Логическое выражение) инструкция
Логическое выражение вычисляется один раз в начале цикал, а затем каждый раз перед каждой будующей итерацией для интсрукции
Здесь приведен пример, который генерирует случайные числа, пока пока не достигнится определенное состояние:
//: c03:WhileTest.java
// Демонстрация цикла while.
public class WhileTest { public static void main(String[] args) { double r = 0; while(r < 0.99d) { r = Math.random(); System.out.println(r); } } } ///:~
Здесь используется статический метод random( ) из библиотеки Math, который генерирует значения типа double в пределах от 0 до 1. (Это включает 0, но не включает 1.) Сравнительное выражение для while говорит, “продолжать выражение этого цикла, пока не встретится число 0.99 или больше”. Всякий раз, когда вы запускаете программу, вы будете получать список чисел разной длины.
Итераторы
В любом контейнерном классе вы должны иметь способ поместить вещь внутри и способ достать вещь наружу. Кроме этого, первичная задача контейнера — хранить вещи. В случае ArrayList: add( ) - способ, который вставляет объекты, а get( ) - один из способов получит вещи наружу. ArrayList достаточно гибок, вы можете выбрать все что угодно в любое время и выбирать различные элементы одновременно, используя разные индексы.
Если вы хотите начать думать на более высоком уровне, то есть препятствие: вам необходимо знать точный тип контейнера для правильного его использования. Сначала это может не показаться плохим, но что, если вы начнете использовать ArrayList, а позже в вашей программе вы обнаружите, что в связи со способом использования контейнера более эффективным будет использование LinkedList вместо него? Или, предположим, вы хотите написать кусок общего кода, который не будет знать или заботится о типе контейнера, с которым он работает, так что может ли он использовать разные типы контейнеров без переписывания кода?
Концепция итераторов может быть использована для достижения этой абстракции. Итератор - это объект, чья работа заключается в перемещении по последовательности объектов и выборе каждого объекта в такой последовательности, чтобы клиентский программист не знал или не заботился о подлежащей структуре этой последовательности. Кроме того, итераторы это обычно то, что называется “легковесными” объектами: объекты, дешевые в создании. По этому, вы часто будите находить несколько странными на вид ограничения для итераторов; например, некоторые итераторы могут перемещаться только в одном направлении.
Java Iterator - это пример итератора с такого рода ограничениями. Вы многое можете делать с ним, включая:
Просить контейнер передать вам Iterator, используя метод, называемый iterator( ). Этот Iterator будет готов к возврату первого элемента последовательности при первом вызове метода next( ).
Получать следующий объект в последовательности с помощью next( ).
Проверять есть ли еще объекты в последовательности с помощью hasNext( ).
Удалять последний элемент, возвращенный итератором, с помощью remove( ).
Это все. Это простая реализация итератора, но достаточно мощная (и существуют более изощренный ListIterator для List). Чтобы посмотреть, как это работает, позвольте вновь использовать программу CatsAndDogs.java, введенную ранее в этой главе. В оригинальной версии метод get( ) был использован для выбора каждого элемента, но в следующей измененной версии используется итератор:
//: c09:CatsAndDogs2.java
// Простой контейнер с итератором.
import java.util.*;
public class CatsAndDogs2 { public static void main(String[] args) { ArrayList cats = new ArrayList(); for(int i = 0; i < 7; i++) cats.add(new Cat(i)); Iterator e = cats.iterator(); while(e.hasNext()) ((Cat)e.next()).print(); } } ///:~
Вы можете видеть, что последние несколько строк используют Iterator, чтобы пройти по последовательности вместо цикла for. С помощью итератора вам нет необходимости заботится о числе элементов в контейнере. Об этом беспокоится за вас hasNext( ) и next( ).
В качестве другого примера, рассмотрим создание метода печати общего назначения:
//: c09:HamsterMaze.java
// Использование итератора.
import java.util.*;
class Hamster { private int hamsterNumber; Hamster(int i) { hamsterNumber = i; } public String toString() { return "This is Hamster #" + hamsterNumber; } }
class Printer { static void printAll(Iterator e) { while(e.hasNext()) System.out.println(e.next()); } }
public class HamsterMaze { public static void main(String[] args) { ArrayList v = new ArrayList(); for(int i = 0; i < 3; i++) v.add(new Hamster(i)); Printer.printAll(v.iterator()); } } ///:~
Пристальнее всмотритесь в метод printAll( ). Обратите внимание, что здесь нет информации о типе последовательности. Все что у вас есть - это Iterator, и это все, что вам нужно знать о последовательности: так как вы можете получить следующий объект и так как вы знаете, когда вы подойдете к концу. Эта идея, получение контейнера объектов и прохождение по нему для выполнения операции для каждого элемента - достаточно мощная и будет просматриваться повсюду в этой книге.
Пример является более общим, так как он косвенным образом использует метод Object.toString( ). Метод println( ) перегружается для всех примитивных типов так же, как и для Object; в каждом случае автоматически производится String путем вызова соответствующего метода toString( ).
Хотя в этом нет необходимости, но вы можете быть более точны при использовании приведения, которое имеет тот же эффект, что и вызов toString( ):
System.out.println((String)e.next());
Однако в общем случае вы захотите сделать что-то большее, нежели вызов методов Object, так что вы вновь будете применять приведение типов. Вы должны принимать во внимание, что вам интереснее получить Iterator для последовательности определенного типа и приводить результирующие объекты к этому типу (если вы ошиблись, получите исключение времени выполнения).
в Java широко используются ссылки
Поскольку в Java широко используются ссылки и поскольку каждый создаваемый объект создается в heap и становится мусором сразу же после того как перестает использоваться, поведение и манипуляция с объектом изменяется, особенно при передаче и возврате объектов. Например, в Си или Си++, если вы хотите инициализировать некоторые фрагменты памяти в методе, вы можете использовать для получения этого адреса получаемый методом параметр. Иначе вам пришлось бы беспокоиться на счет того, существует ли до сих пор необходимый вам объект или он был уничтожен. Поэтому интерфейс подобных методов несколько усложнен. Но в Java вы не должны волноваться о существовании объекта, за вас обо всем позаботятся. Вы можете создавать объекты тогда, когда вам захочется, не беспокоясь о самой механике создания объекта: вы просто передаете ссылку. Иногда такая простота практически незаметна, а иногда просто поражает.
За эти волшебные возможности от вас требуется учитывать следующие два момента:
Вам придется мириться с некоторой потерей производительности, связанной с управлением памятью (хотя она может быть весьма незначительной) и неопределенностью в скорости работы программы(поскольку при недостатке памяти может быть активирован сборщик мусора). Для большинства приложений выгоды превышают недостатки а наиболее узкие места можно обойти, используя native методы (см. Приложение B)
Дублирующие ссылки: иногда вы можете случайно столкнуться с возникновением двух ссылок на один и тот же объект, что может привести к негативным последствиям лишь в том случае, если обеим ссылкам присваиваются указатели на различные объекты. Вот где потребуется уделить более пристальное внимание и, там где это необходимо, использовать метод clone() для объекта, чтобы защититься от непредвиденных изменений. В качестве альтернативы, когда для для повышения эффективности целесообразно использовать дублирующие ссылки, вы можете применить неизменные объекты, которые могут возвращать объекты того же или иного типа, но не могут использоваться для изменения первоначального объекта, так что эти изменения не отразятся на дублирующих ссылках.
Некоторые люди считают что клонирование в Java плохо реализовано и при наследовании используют собственные версии клонирования [84] не пользуясь вызовом метода Object.clone(), что избавляет от необходимости наследования интерфейса Cloneable и перехвата CloneNotSupportedException. Это весьма подходящий прием, поскольку clone() весьма редко поддерживается в пределах стандартных библиотек Java, к тому же он довольно безопасен.
Изучение Java
Примерно в то же самое время, когда вышла в свет моя первая книга Думай на С++ (Osborne/McGraw-Hill, 1989) я начал преподавательскую деятельность. Обучение языкам программирования стало моей профессией; я видел "клюющих носом", пустые лица и недоуменные выражения аудиторий по всему миру с того самого 1989 года. Когда же я начал давать частные уроки небольшой группе учеников, то обнаружилось, что даже те, кто развлекался или дремал во время занятий были в замешательстве по многим вопросам. Возглавляя отделение по С++ наКонференции Разработчиков Программного Обеспечения в течение нескольких лет я понял, что и я, и другие преподаватели стараются дать слишком много информации за слишком малый промежуток времени. Обычно, в зависимости от уровня подготовки и моего способа изложения материала, к концу семинара я терял часть аудитории. Возможно это о многом говорит, но я из тех людей, которые против традиционных способов проведения лекций (и как и для большинства людей, я уверен, что подобное сопротивление возникает от скуки), и мне хотелось бы обучение шло с максимальной скоростью. Поэтому в течении определенного времени я стал проводить короткие выступления, а само занятие заканчивалось экспериментами и повторениями (способ, который отлично работает также и при программировании на Java). В результате я создал курс, основанный на моем прошлом опыте, который я и хотел бы преподавать. Согласно тому курсу мы приступаем к решению задачи по частям, с простых шагов. На практических семинарах (идеальные с точки зрения обучения) множество задач следуют сразу за коротким объяснением. В настоящий момент я преподаю данный курс на публичных Java семинарах, ознакомиться с которыми вы можете на www.BruceEckel.com. (Кроме информации на Web-сайте, вводная часть семинара также есть на CD-ROM). Ответная реакция от слушателей, которую я получаю на каждом семинаре, помогает мне изменять или пересматривать материал до тех пор, пока я не решу, что он уже достаточно хорош для преподавания. Однако данная книга не является просто конспектом лекций семинара, в ней я постарался собрать и структурировать как можно больше информации, чтобы увлечь вас чтением каждой новой главы. Более того, книга создана для читателей изучающих новый язык программирования самостоятельно.
Извлечение BeanInfo с помощью Инспектора
Одна из наиболее критичных частей компонентной схемы возникает, когда вы перетаскиваете компонент (Bean) из палитры и бросаете его в форму. Построитель приложения должен быть способен создать компонент (Bean) (что выполняется с помощью конструктора по умолчанию), а затем, без доступа к исходному коду компонента (Bean), получить всю необходимую информацию для создания страничек свойств и обработчиков событий.
Часть решения ясно видна в конце Главы 12: рефлексия Java позволяет обнаружить все методы анонимных классов. Это совершенное решение проблемы компонента (Bean) без введения любых дополнительных ключевых слов, которые требуются в других визуальных языках программирования. Фактически, одна из главнейших причин добавления рефлексии в Java была в поддержке компонентов (Bean) (хотя рефлексия также поддерживает сериализацию объектов и удаление обращений к методам). Так что вы можете ожидать, что создатель построителя приложения будет рефлектировать каждый компонент (Bean) и охотится за его методами для нахождения свойств и событий для этого компонента (Bean).
Это, конечно, возможно, но разработчики Java хотели обеспечить стандартный инструмент, не только для упрощения использования компонент (Bean), но и для обеспечения стандартного подхода для создания более сложных компонент (Bean). Этим инструментом является класс Introspector, и наиболее важным методом этого класса является static getBeanInfo( ). Вы передаете ссылку на Class в этот метод, и он полностью опрашивает этот класс и возвращает объект BeanInfo, который вы можете затем раскрыть для нахождения свойств, методов и србытий.
Обычно вы не заботитесь об этом — вероятно, вы получите большинство ваших компонентов (Bean) от продавца, и вам не нужно будет знать всю магию, которая происходит внутри. Вы просто перетаскиваете ваш компонент (Bean) на вашу форму, затем конфигурируете его свойства и пишите обработчик для интересующих вас событий. Однако очень интересно и познавательно использовать Introspector для отображения информации о компоненте (bean), так что вот инструмент, который делает это:
//: c13:BeanDumper.java
dmpr.actionPerformed( new ActionEvent(dmpr, 0, "")); } public static void main(String[] args) { Console.run(new BeanDumper(), 600, 500); } } ///:~
BeanDumper.dump( ) - это метод, который делает всю работу. Сначала он пробует создать объект BeanInfo, и если это происходит успешно, вызывает метод BeanInfo, который производит информацию о свойствах, методах и событиях. В Introspector.getBeanInfo( ), вы увидите второй аргумент. Это говорит Introspector, где остановится в иерархии наследования. Здесь он остановится прежде, чем разберет все методы от Object, так как мы не интересуемся ими.
Для свойств: getPropertyDescriptors( ) возвращает массив из PropertyDescriptor. Для каждого PropertyDescriptor вы можете вызвать getPropertyType( ) для нахождения класса объекта, который передается и получается через методы свойства. Затем, для каждого свойства вы можете получить псевдоним (получается из имени метода) с помощью getName( ), метод для чтения с помощью getReadMethod( ), и метод для записи с помощью getWriteMethod( ). Последние два метода возвращают объект Method, который может на самом деле использоваться для вызова соответствующего метода объекта (это часть рефлексии).
Для public методов (включая методы свойств) getMethodDescriptors( ) возвращает массив MethodDescriptor. Для каждого их них вы можете получить ассоциированный объект Method и напечатать его имя.
Для событий getEventSetDescriptors( ) возвращает массив (как вы думаете, чего?) EventSetDescriptor. Каждый элемент массива может быть опрошен для нахождения класса слушателя, методов класса слушателя и методов добавления (add-) и удаления (remove-). Программа BeanDumper печатает всю эту информацию.
После запуска программа форсирует вычисления frogbean.Frog. То, что получается на выходе, после удаления дополнительных деталей, ненужных здесь, вы видите здесь:
class name: Frog Property type: Color Property name: color Read method: public Color getColor() Write method: public void setColor(Color) ==================== Property type: Spots Property name: spots Read method: public Spots getSpots() Write method: public void setSpots(Spots) ==================== Property type: boolean
Property name: jumper Read method: public boolean isJumper() Write method: public void setJumper(boolean) ==================== Property type: int
Property name: jumps Read method: public int getJumps() Write method: public void setJumps(int) ==================== Public methods: public void setJumps(int) public void croak() public void removeActionListener(ActionListener) public void addActionListener(ActionListener) public int getJumps() public void setColor(Color) public void setSpots(Spots) public void setJumper(boolean) public boolean isJumper() public void addKeyListener(KeyListener) public Color getColor() public void removeKeyListener(KeyListener) public Spots getSpots() ====================== Event support: Listener type: KeyListener Listener method: keyTyped Listener method: keyPressed Listener method: keyReleased Method descriptor: public void keyTyped(KeyEvent) Method descriptor: public void keyPressed(KeyEvent) Method descriptor: public void keyReleased(KeyEvent) Add Listener Method: public void addKeyListener(KeyListener) Remove Listener Method: public void removeKeyListener(KeyListener) ==================== Listener type: ActionListener Listener method: actionPerformed Method descriptor: public void actionPerformed(ActionEvent) Add Listener Method: public void addActionListener(ActionListener) Remove Listener Method: public void removeActionListener(ActionListener) ====================
Вот большая часть того, что видит и производит Introspector в качестве объекта BeanInfo для вашего компонента (Bean). Вы можете видеть, что тип свойств и их имена независимы. Обратите внимание на нижний регистр в имени свойства. (Это не случается, когда имя свойства начинается с более чем одной большой буквы в строке.) Запомните, что имена методов, которые вы видите здесь (такие как методы чтения и записи), на самом деле произведены объектом Method, который может быть использован для вызова ассоциированного метода объекта.
Список public методов включает методы, которые не связаны со свойствами или событиями, такие как croak( ). Здесь все методы, которые вы можете вызвать программно для компонента (Bean), и построитель приложения может выбрать список всех, когда вы выполняете вызов метода, для облегчения вашей задачи.
Наконец, вы можете видеть события, передающиеся в слушатели, методы слушателей, и методы добавления и удаления слушателей. В общем, так как вы имеете BeanInfo, вы можете найти все, что важно для компонента (Bean). Вы можете также вызвать методы для этого компонента (Bean), даже если у вас нет другой информации, за исключением объекта (опять с помощью рефлексии).
Извлечение полей и значений
Приведенный ниже пример сож с приведенным ранее в разделе, посвященном сервлетам. При первом обращении к странице он определяет, что у вас нет полей и возвращает страницу, содержащую форму, используя тот же самй код, что и в примере с сервлетом, но в формате JSP. Когда вы отсылаете форму с заполненными полями на тот же самый JSP URL, он находит поля и отображает их. Это хорошая техника, поскольку она позволяет вам иметь и страницу, содержащую форму для заполнения пользователем, и код ответа на эту форму, как единый файл, что облегчает создание и поддержку.
//:! c15:jsp:DisplayFormData.jsp
<%-- Fetching the data from an HTML form. --%> <%-- This JSP also generates the form. --%> <%@ page import="java.util.*" %> <html><body> <H1>DisplayFormData</H1><H3> <% Enumeration flds = request.getParameterNames(); if(!flds.hasMoreElements()) { // No fields %>
<form method="POST" action="DisplayFormData.jsp"> <% for(int i = 0; i < 10; i++) { %> Field<%=i%>: <input type="text" size="20"
name="Field<%=i%>" value="Value<%=i%>"><br> <% } %> <INPUT TYPE=submit name=submit value="Submit"></form> <%} else { while(flds.hasMoreElements()) { String field = (String)flds.nextElement(); String value = request.getParameter(field); %> <li><%= field %> = <%= value %></li> <% } } %> </H3></body></html> ///:~
Наиболее интересная особенность этого примера состоит в том, что он демонстрирует, как код скриплета может быть смешан с HTML кодом, даже в точке генерации HTML с помощью цикла for из Java. Это особенно хорошо для построения всех видов форм, в которых присутствует повторяющийся HTML код.
Ярлыки документации класса
Наряду со встроенным HTML и ссылками @see, документация класса может включать ярлыки для информации о версии и имени автора. Документация класса также может быть использована для интерфейса (смотрите Главу 8).
Ярлыки документации методов
Так же как и встроенная документация и ссылки @see, методы допускают ярлыки документации для параметров, возвращаемых значений и исключений.
Ярлыки документации переменных
Документация переменных может включать только встроенный HTML код и ссылки @see.
Java
Если языки сценариев могут решить 80 процентов проблем программирования стороны клиента, что можно сказать об остальных 20 процентов “действительно сложных задач”? Наиболее популярным решением сегодня является Java. Не только потому, что это мощный язык программирования, построенный для безопасности, кросс-платформенности и интернациональности, но Java постоянно расширяется, чтобы обеспечить такие особенности языка и библиотеки, которые элегантно решают проблемы, которые сложны для традиционных языков программирования, такие как многопоточность, доступ к базам данных, сетевое программирование и распределенные вычисления. Java обеспечивает программирование на стороне клиента через апплет.
Апплет - это мини-программа, которая запускается только под управлением Web броузера. Апплет скачивается автоматически, как часть Web странички (как, например, графика скачивается автоматически). Когда активируется апплет, то выполняется программа. Это часть прекрасного — это обеспечивает вам способ автоматического распределения клиентского программного обеспечения с сервера в то время, когда это необходимо пользователю и не ранее. Пользователи получают последнюю версию клиентского программного обеспечения без ошибок и без сложных переинсталяций. Поэтому, в том способе, который разработан в Java, программисту необходимо создать только одну программу, а эта программа автоматически работает на всех компьютерах, которые имею броузеры со встроенным Java интерпретатором. (Это благополучно включают большинство машин.) Так как Java полноценный язык программирования, вы можете выполнить столько работы, сколько может клиент как перед, так и после выполнения запроса на сервер. Например, вы не хотите посылать запрос через Internet, чтобы узнать, что данные или какой-то параметр неверны, а ваш клиентский компьютер быстро выполнит работу по проверке данных, вместо ожидания от сервера проверки и передачи графического изображения к вам обратно. Вы не только получаете преимущество в скорости и отзывчивости, но это снизит сетевой трафик и в загрузку сервера, предотвращая от замедления весь Internet.
Java апплеты предпочтительнее других программ-сценариев, так как они имеют компилированную форму, так что исходный код не доступен для клиента. С другой стороны, Java апплет может быть декомпилирован без особых затруднений, но прятанье вашего кода чаще всего не самая важная задача. Два других фактора могут оказаться важнее, как вы увидите далее в этой книге, компилированные Java апплеты могут включать много модулей и занимать много отправок (обращений) сервера для скачивания. (В Java 1.1 и выше это минимизируется Java архивами, называемыми JAR файлами, что позволяет все требуемые модули паковать вместе и компрессировать для упрощения скачивания.) Программы-сценарии просто интерпретируются на Web странице как часть ее текста (и обычно маленькие и снижают обращения к серверу). Это важно для отзывчивости вашего Web сайта. Другой фактор - существенная кривая изучения. Независимо от того, что вы слышали, Java - это не простой язык для изучения. Если вы программируете на Visual Basic, переход к VBScript будет для вас более быстрым решением и, вероятно решит большинство типичных проблем клиент/сервер, которые вы можете с трудом преодолеть, изучая Java. Если вы имеете опыт в языках сценария, вам сначала полезнее будет взглянуть на JavaScript или VBScript, прежде чем переходить на Java, так как они могут легко удовлетворить вашим требованиям и ваша работа будет более продуктивной.
Java Апплеты и CORBA
Java апплеты могут выступать в роли CORBA клиентов. Таким образом апплеты могут получать доступ к удаленной информации и службам, существующим, как CORBA объекты. Но апплеты могут соединяться только с тем сервером, с которого их загрузили, так что все CORBA объекты, с которыми взаимодействует апплет, должны быть помещены на сервере. Это противоречит тому, для чего предназначен CORBA: дать вам полную независимость от местоположения.
Это особенность сетевой безопасности. Если вы в Интранете, одним из решений является отказ от системы безопасности броузера. Или в установки firewall на соединения со внешних серверов.
Некоторые из продуктов Java ORB предлагают потенциальное решение этой пролемы. Например, некоторые реализуют то, что называется тунелированием HTTP Tunneling, а другие имеют свои собственные особенности для firewall.
Это слишком сложный вопрос, чтобы он был освещен в приложении, но это то, в чем вы должны быть уверены.
Java архивы (JAR'ы)
Формат Zip также используется в файле, формата JAR (Java ARchive), который является способом сбора группы файлов в один компрессированный файл, так же как и Zip. Однако, как и все остальное в Java, JAR файлы являются кроссплатформенными, так что вам не нужно беспокоится о возможностях платформы. Вы также можете включить звуковой и графический файл наряду с файлами классов.
JAR файлы обычно полезны, когда вы имеете дело с Internet. До появления JAR файлов ваш Web броузер делал повторяющиеся запросы к Web серверу для загрузки всех файлов, из которых состоит апплет. Кроме того, каждый из этих файлов был не компрессирован. При сборе всех этих файлов для определенного апплета в единый JAR файл необходим только один запрос к серверу, а передача пройдет быстрее из-за компрессии. А каждое включение в JAR файл может иметь цифровую подпись для безопасности (обратитесь за деталями к документации по Java).
JAR файл состоит из единого файла, содержащего набор файлов, упакованных с помощью Zip, наряду с “манифестом”, который описывает их. (Вы можете создать свой собственный файл манифеста; в противном случае программа jar сделает это за вас.) Вы можете найти больше информации о файлах манифеста JAR в HTML документации для JDK.
Утилита jar, пришедшая вместе с JDK от Sun, автоматически компрессирует файлы по вашему выбору. Вы можете вызвать ее из командной строки:
jar [options] destination [manifest] inputfile(s)
Опции - это просто набор символов (не нужно ни дефисов, ни другой индикации). Пользователи Unix/Linux заметят сходство с опциями tar. Вот они:
c | Создает новый или пустой архив. | |
t | Список содержания. | |
x | Извлечь все файлы. | |
x file | Извлекает указанный файл. | |
f | Говорит: “Я дам тебе имя файла”. Если вы не используете это, jar поймет, что ввод должен идти через стандартный ввод или, если создается файл, вывод происходит через стандартный вывод. | |
m | Говорит о том, что первый аргумент будет именем файла манифеста, созданного пользователем. | |
v | Генерирует подробный вывод, описывающий то, что делает jar. | |
0 | Только хранение файлов; не компрессирует файлы (используйте для создания JAR файла, который вы можете поместить в ваш classpath). | |
M | Не выполняется автоматическое создание файла манифеста. |
Если поддиректории включаются в файлы, помещаемые в JAR файл, эти поддиректории добавляются автоматически, включая все вложенные поддиректории и т.д. Информация о пути тоже сохраняется.
Вот типичный способ вызова jar:
jar cf myJarFile.jar *.class
Это создает JAR файл, называемый myJarFile.jar, содержащий все файлы классов из текущей директории наряду с автоматически сгенерированным файлом манифеста.
jar cmf myJarFile.jar myManifestFile.mf *.class
Как и в предыдущем примере, но добавляется файл манифеста, созданный пользователем. Он называется myManifestFile.mf.
jar tf myJarFile.jar
Производится содержание файла myJarFile.jar.
jar tvf myJarFile.jar
Добавляет флаг “verbose”, чтобы получить более детальную информацию о файлах в myJarFile.jar.
jar cvf myApp.jar audio classes image
Принимая во внимание, что audio, classes и image являются поддиректориями, таким образом, все собирается в файл myApp.jar. Также включен флаг “verbose”, чтобы иметь обратную связь, пока работает программа jar.
Если вы создаете JAR файл, используя опцию 0, такой файл может быть помещен в ваш CLASSPATH:
CLASSPATH="lib1.jar;lib2.jar;"
После этого Java может искать файлы lib1.jar и lib2.jar.
Инструмент jar не является таким же полезным, как утилита zip. Например, вы не можете добавить или обновить файлы существующего JAR файла; вы можете создать JAR файл только с самого начала. Также вы не можете переместить файл в JAR файл и стереть его сразу, как только он будет перемещен. Однако JAR файл, созданный на одной платформе, может быть прочитан инструментом jar на любой другой платформе (проблема, которая иногда надоедает с утилитой zip).
Как вы увидите в Главе 13, JAR файлы также используются для упаковки JavaBeans.
Java Database Connectivity (JDBC)
Приблизительно было подсчитано, что половина всего программного обеспечения использует клиент/серверные операции. Многообещающей возможностью Java была способность строить платформонезависимые клиент/серверные прилажения для работы с базами данных. Это стало возможным благодаря Java DataBase Connectivity (JDBC).
Одна из основных проблемм при работе с базами данных - это война особенностей между компаниями, разрабатывающими базы данных. Есть “стандартный” язык базы данных, Structured Query Language (SQL-92), но вы обычно должны знать с базой данных какого производителя вы работаете, несмотря на стандарт. JDBC предназначена для независимости от платформы, так что вам нет необходимости заботится о том, какую базу данных вы используете при программировании. Однако все еще возможно делать зависимые от производителя вызовы из JDBC, так что вы не ограничены тем, что вы должны делать.
В одном месте программистам может понадобиться использовать SQL имена типов в SQL выражении TABLE CREATE, когда они создают новую таблицу данных и определяют SQL тип для каждой колонки. К сожалению существуют значительные различия между SQL типами, поддерживаемыми различными продуктами баз данных. Различные базы данных, поддерживающие SQL типы с одинаковой семантикой и структурой, могут иметь различные имена типов. Большинство наиболее известных баз данных поддерживают типы данных SQL для больших бинарных значений: в Oracle этот тип называется LONG RAW, Sybase называет его IMAGE, Informix называет его BYTE, а DB2 называет го LONG VARCHAR FOR BIT DATA. Поэтому, если переносимость между базами данных является вашей целью, вы должны попробовать обойтись только основными идентификаторами SQL типов.
Переносимость - это такая возможность при написании книги, при которой читатели могут проверить примеры в любом неизвестном хранилище данных. Я попробовал написать такие примеры настолько переносимыми, насколько это возможно. Также вы должны иметь в виду, что специфичный для базы данных код был изолирован, чтобы можно было централизовать все изменения, которые вам необходимо будет выполнить, чтобы примеры заработали в вашей среде.
JDBC, как и многие API в Java, предназначен для упрощения. Вызовы методов, которые вы делаете, соответствует логическим операциям, которые вы думаете выполнить для сбора данных из базы данных: подключиться к базе данных, создать выражение и выполнить запрос, затем посмотреть результирующую выборку.
Для получения платформонезависимости, JDBC предоставляет менеджер драйверов (driver manager) который динамически использует все объекты драйверов, которые необходимы для опроса вашей базы данных. Так что если у вас есть базы данных от трех производителей, к которым вы хотите подсоединиться, вам нужно три различных объекта драйверов. Объекты драйверов регистрируют себя с помощью менеджера драйверов вл время загрузки, а вы можете принудительно выполнить загрузку, используя Class.forName( ).
Для открытия базы данных вы должны создать “URL базы данных”, котрый указывает:
Что вы используете JDBC с помощью “jdbc.” “Подлежащий протокол”: имя драйвера или имя механизма соединения с базой данных. Так как назначение JDBC было вдохнавлено ODBC, первый доступный подлежащий протокол - это “jdbc-odbc мост”, обозначаемый “odbc”. Идентификатор базы данных. Он варьируется в зависимости от используемого драйвера базы данных, но обычно предоставляет логическое имя, которое отображается програмным обеспечением администрирования базы данных на физический директорий, в котором расположены таблицы базы данных. Для вас иденификатор базы данных имеет различные значения, вы должны зарегистрировать имя, используя ваше програмное обеспечение администирования базы данных. (Процесс регистрации различен для разных платформ.)
Вся эта информация комбинируется в одну строку: “URL базы даных”. Например, для подключения черед подлежащий протокол ODBC к базе данных с идентификатором “people”, URL базы данных может быть:
String dbUrl = "jdbc:odbc:people";
Если вы подключаетесь по сети, URL базы данных будет содержать информацию для подключения, идентифицирующую удаленную машину и может быть немного пугающим. Вот пример работы с базой данных CloudScape, которую вызывает удаленных клиент, использующий RMI:
jdbc:rmi://192.168.170.27:1099/jdbc:cloudscape:db
Этот URL базы данных на самом деле содержит два jdbc вызова в одном. Первая часть “jdbc:rmi://192.168.170.27:1099/” использует RMI для создания соединения с удаленной машиной баз данных, следящей за портом 1099 по IP адресу 192.168.170.27. Вторая часть URL, “jdbc:cloudscape:db” передает более привычные установки, используя подлежащий протокол и имя базы данных, но это произойдет только после того, как первая секция установит соединение с удаленной машиной через RMI.
Когда вы готовы присоединиться к базе данных, вызовите статический (static) метод DriverManager.getConnection( ) и передайте ему URL базы данных и пароль для входа в базу данных. Обратно вы получите объект Connection, который затем вы можете использовать для опроса и манипуляций с базой данных.
Следующий пример открывает контактную информацию базы данных и ищет имя человека, переданное из командной строки. Он выбирает только имена людей, имеющий электронные адреса, затем печатает те из них, имя которых совпадает с заданным:
//: c15:jdbc:Lookup.java
// Поиск электронных адресов в
// локальной базе данных с помощью JDBC.
import java.sql.*;
public class Lookup { public static void main(String[] args) throws SQLException, ClassNotFoundException { String dbUrl = "jdbc:odbc:people"; String user = ""; String password = ""; // Загружаем драйвер (регистрируем себя)
Class.forName( "sun.jdbc.odbc.JdbcOdbcDriver"); Connection c = DriverManager.getConnection( dbUrl, user, password); Statement s = c.createStatement(); // SQL код:
ResultSet r = s.executeQuery( "SELECT FIRST, LAST, EMAIL " + "FROM people.csv people " + "WHERE " + "(LAST='" + args[0] + "') " + " AND (EMAIL Is Not Null) " + "ORDER BY FIRST"); while(r.next()) { // Регистр не имеет значения:
System.out.println( r.getString("Last") + ", " + r.getString("fIRST") + ": " + r.getString("EMAIL") ); } s.close(); // Закрываем ResultSet
} } ///:~
Вы можете увидеть создание URL базы данных, как это описано выше. В этом примере нет защитного пароля для базы данных, поэтому имя пользователя и пароль представлены пустыми строками.
Как только соединение установлено с помощью DriverManager.getConnection( ), вы можете использовать полученный объект Connection для создания объекта Statement, используя метод createStatement( ). С помощью Statement вы можете вызвать executeQuery( ), передав в него строку, содержащую SQL выражение стандарта SQL-92. (Скоро вы увидите как вы можете генерировать это выражение автоматически, так что вам не нужно много знать об SQL.)
Метод executeQuery( ) возвращает объект ResultSet, который является итератором: метод next( ) перемещает итератор на следующую запись в выражении или возвращает false, если достигнут конец результирующего множества. Вы всегда получите назад объект ResultSet от executeQuery( ), даже если результатом запроса является пустое множество (если так, исключение не возникает). Обратите внимание, чтовы должны вызвать next( ) прежде, чем попробовать прочесть любую запись. Если результирующее множество - пустое, этот первый вызов next( ) вернет false. Для каждой записи результирующего множества вы можете выбрать поля, используя (наряду с другими подходами) имя поля, как строку. Также обратите внимание, что регистр в имени поля игнорируется — это не так с базой SQL данных. Вы определяете тип, который получите, вызвав getInt( ), getString( ), getFloat( ) и т.д. В этом месте вы получаете данные из вашей базы данных в родном формате Java и можете делать с ними все, что хотите, используя обычный Java код.
Java и Internet
Если Java, фактически, является еще одним языком программирования, вы можете спросить: почему он так важен и почему он преподносится, как революционный шаг в компьютерном программировании. Ответ не будет получен немедленно, если вы исходите из традиционного подхода к программированию. Хотя Java очень полезен для решения традиционных одиночных проблем программирования, он также важен, поскольку решает проблемы программирования в World Wide Web.
Java Naming и Directory Interface (JNDI)
Java Naming and Directory Interface (JNDI) используется в Enterprise JavaBeans в качестве службы указания имен для EJB компонент в сети и других службах контейнера, таких как транзакции. JNDI работает очень похоже с другими стандартами, такими как CORBA CosNaming, и может на самом деле быть реализован в виде надстройки над ним.
Java против C++?
Java во многом выглядит как C++ и так естественно кажется, что C++ будет заменен Java. Но я начал с вопроса о такой логике. Для одних вещей C++ все еще имеет некоторые особенности, которых нет в Java, и, хотя, имеется много обещаний относительно того, что однажды Java станет быстрее чем C++, мы видели равномерные усовершенствования, но никаких разительных достижений. Так же продолжается определенный интерес к C++, так что я не думаю, что этот язык скоро отомрет. (Языки, кажется, висят вокруг. Разговаривая на одном из моих “промежуточный/продвинутый семинар по Java”, Allen Holub заявил, что два наиболее часто используемых языка - это Rexx и COBOL, в таком порядке.)
Я начинаю думать, что сила Java лежит в небольшом отличие области действия, по сравнению с C++. C++ - это язык, который делает попытку заполнить шаблон. Несомненно, он был адаптирован определенными способами для решения определенных проблем. Некоторые инструменты C++ комбинируют библиотеки, модели компонентов и инструменты генерации кода для решения проблемы разработки оконных приложений для конечного пользователя (для Microsoft Windows). И теперь, И все таки, что используют большинством разработчиков для Windows? Microsoft Visual Basic (VB). Несмотря на факт, что VB производит код, который становится неуправляемым, когда программа становится несколько страниц длины (и синтаксис, который положительно может мистифицировать) Так как есть успех и популярность VB, но это не очень хороший пример языкового дизайна. Было бы хорошо иметь легкость и мощность VB без неуправляемого результирующего кода. И в этом, я думаю, Java будет блистать: как “следующий VB”. Вы можете содрогнуться или нет, услышав это, но думать о том, как много в Java предназначено для упрощения программисту решений проблем уровня приложения, таких как работа в сети и кросс-платформенность, и теперь есть дизайн языка, который позволяет создание очень больших и гибких тел кода. В добавок к этому Java фактически имеет наиболее крепкий тип проверки и обработки ошибок системы, из того что я видел в языках, так что вы можете сделать существенный прыжок вперед в производительности программирования.
Должны ли вы использовать в ваших проектах Java вместо C++? Не в Web апплетах есть две проблемы для исследования. Первая, если вы хотите использовать много существующих библиотек C++ (и вы, конечно, получите большую прибавку производительности), или вы имеете существующий базовый код на C или C++, то Java может замедлить вашу разработку, а не ускорить ее.
Если вы разрабатываете весь ваш код, начиная с шишек, то простота Java по сравнению с C++ значительно сократит время разработки — рассказы очевидцев (истории команд C++, с которыми я говорил и кто перешел на Java) сообщают об удвоении скорости против C++. Если производительность Java не имеет значения или вы можете чем-нибудь компенсировать это, явные проблемы времени-до-продажи делают затруднительным выбор C++ против Java.
Наибольшая проблема - производительность. Интерпретатор Java - медленный, даже в 20-50 раз медленнее, чем C по сравнению с обычным интерпретатором Java. Это улучшится через какое-то время, но все еще будет оставаться значительным числом. Компьютеры о скорости; если бы что-то значительно быстрее было сделать на компьютере, то вы бы делали это руками. (Я даже слышал советы, что вы занимаетесь Java чтобы сократить время разработки, чтобы затем, используя инструменты и библиотеки поддержки, переводите ваш код на C++, если вам необходимо высокая скорость выполнения.)
Ключевым моментом, делающим Java подходящим для большинства проектов - это появление ускорителей, называемый “just-in time” (JIT) компилятор, собственная “hotspot” технология Sun, и компиляторов платформозависимого кода. Конечно, компиляторы платформозависимого кода устранят рекламируемое кросс-платформенное выполнение скомпилированной программы, но они так же повысят скорость выполнения, приблизив ее к C и C++. А кросс-платформенная программа на Java будет много легче, чем если это делать на C или C++. (Теоретически, вы должны просто перекомпилировать, но это обещание было сделано и для других языков.)
Вы можете найти сравнения Java и C++ и обзор использования Java в первой редакции этой книги (Эта книга доступна на сопровождающем CD ROM, так же как и на www.BruceEckel.com).
Java Server Pages
Java Server Pages (JSP) является стандартным расширением Java, который определен на основании сервлетного Расширения. Целью JSP является упрощение создания и управления динамическими Web страницами.
Как упоминалось ранее, свободно распространяемое ПО Tomcat, которую можно получить с jakarta.apache.org автоматически поддерживает JSP.
JSP позволяет вам комбинировать HTML код Web страницы с кусочками Java кода в одном и том же документе. Код Java окружатся специальными ярлыками, которые говорят JSP контейнеру, что он должен использовать этот код для генерации сервлета илил его части. Преимущество JSP в том, что вы можете иметь единый документ, который представляет и страницу, и Java код, который включается в нее. Недостатотк в том, что поддерживающий JSP страницу человек должен быть опытен и в HTML и в Java (однако разработчик GUI сред для JSP к этому приближается).
В первый раз JSP загружается JSP контейнером (который обычно связан с Web сервером, или является его частью), код сервлета, помеченный JSP ярлыками, автоматически генерируется, компилируется и загружатся и контейнер сервлетов. Статическая часть HTML страницы воспроизводится путем посылки статического объекта String в метод write( ). Динамическая часть включается прямо в сервлет.
Исходя из этого, пока исходный текст JSP страницы не изменяется, она ведет себя так, как будто это статическая HTML страница с ассоциированным сервлетом (однако, весь HTML код на самом деле генерируется сервлетом). Если вы изменяете исходный код для JSP, он автоматически перекомпилируется и перегружается при следующем запросе этой страницы. Конечно, из-за всей этой динамики вы увидите замедленый ответ на первый запрос этой JSP страницы. Но поскольку JSP использует немного больше, чем просто изменения, обычно вы не встретите этой задержки.
Структура JSP страницы состоит из перемешивания сервлета и HTML страницы. JSP ярлыки начинаются и заканчиваются угловыми скобками, так же как и ярлыки HTML, но эти ярлыки также включают символ процентов, так что все JSP ярлыки обозначаются
<% JSP code here %>
За первым знаком процента могут следовать другие символы, которые означают часть JSP кода в ярлыке.
Ниже приведен очень простой пример JSP, который использует стандартную вызов Java библиотеки для получения текущего времени в милисекундах, затем это значение делится на 1000, для получение времени в секундах. Так как используется JSP выражение ( <%= ), результат вычислений конвертируется в String, и помещается на генерируемую Web страницу:
//:! c15:jsp:ShowSeconds.jsp
<html><body> <H1>The time in seconds is: <%= System.currentTimeMillis()/1000 %></H1> </body></html> ///:~
В JSP примерах этой книги первая и последняя строки не будут включаться в файл реального кода, который помещен в архив исходного кода, прилагающийся к этой книге.
Когда клиент создает запрос к JSP странице, Web сервер должен быть сконфигурирован, чтобы соответствовать запросам JSP контейнера, который затем вызывает страницу. Как упоминалось ранее, при первом вызове страницы, компоненты, указанные на странице компоненты генерируются и компилируются JSP контенером в один или несколько сервлетов. В приведенном выше примере сервлет будет содержать код для конфигурирования объекта HttpServletResponse, производящего объект PrintWriter (который всегда называется out), а затем происходит вычисление String, которая посылается в out. Как вы можете видеть, все это выполняется с помощью очень краткой инструкции, но среднестатистический HTML программист/Web дизайнер не имеют опыта в написании такого кода.
Java Transaction API/Java Transaction Service (JTA/JTS)
JTA/JTS используются в Enterprise JavaBeans в качестве API транзакции. Поставщик Enterprise Bean может использовать JTS для создания кода транзакции, хотя EJB Контейнер чаще всего реализует транзакцию в EJB на полезных EJB компонентах. Установщик может определить атрбуты транзакции EJB компонента во время развертывания. EJB Контейнер отвечает за обработку тразакции не зависимо от того, является ли она локальной или распределенной. Спецификация JTS является Java отображением на CORBA OTS (Object Transaction Service).
JavaBeans против EJB
Из-за схожести имен часто путаются между моделью компонент JavaBeans и спецификацией Enterprise JavaBeans. JavaBeans и спецификация Enterprise JavaBeans разделяют одинаковые цели: продвижения повторного использования, компактность Java кода при разработке и инструменты разработки с использованием стандартных шаблонов, но мотивы спецификации больше подходят для решения различных проблем.
Стандарт, определенный в модели компонент JavaBeans предназначен для создания повторного использования компонент, которые обычно используются в интегрированной среде разработки и часто, но не всегда, являются визуальными компонентами.
Спецификация Enterprise JavaBeans определяет модель компонентов для разработки Java кода стороны сервера. Поскольку EJB могут потенциально запускаться на различных серверных платформах — включая центральные машины, которые не имеют визуальных дисплеев — EJB не может использовать графические библиотеки, типа AWT или Swing.
Javah: генератор заголовочных файлов на С
Теперь скомпилируйте ваш исходный файл на Java и запустите javah с полученным файлом .class в качестве параметра, указав ключ —jni (это выполнится автоматически за вас с помощью makefile, присутствующим в исходном коде для книги):
javah —jni ShowMessage
javah читает файл Java класса, и для каждого описания собственного метода генерирует прототип функции в заголовочном файле С или С++. Ниже приведен результат вызова javah для нашего случая (слегка измененный, чтобы уместиться в книгу):
/* НЕ РЕДАКТИРУЙТЕ ЭТОТ ФАЙЛ - он сгенерирован машиной */
#include <jni.h> /* Заголовок для класса ShowMessage */
#ifndef _Included_ShowMessage #define _Included_ShowMessage #ifdef __cplusplus extern "C" { #endif /* * Class: ShowMessage * Method: ShowMessage * Signature: (Ljava/lang/String;)V */
JNIEXPORT void JNICALL Java_ShowMessage_ShowMessage (JNIEnv *, jobject, jstring);
#ifdef __cplusplus } #endif #endif
Как можно видеть с помощью препроцессорной директивы #ifdef __cplusplus данный файл может быть откомпилирован как С так и С++ компилятором. Первая директива #include включает jni.h, заголовочный файл, который кроме всего прочего, определяет типы, используемые далее. JNIEXPORT и JNICALL - это макросы который расширены чтобы соответствовать платформо-зависимым директивам. JNIEnv, jobject и jstring определение JNI типов данных, который скоро будут описаны.
Явная инициализация static
Java позволяет вам сгруппировать другие static инициализации внутри специального “static предложения конструирования” (иногда называемому статическим блоком) в классе. Это выглядит так:
class Spoon { static int i; static { i = 47; } // . . .
Он выглядит как метод, но это просто ключевое слово static, за которым следует тело метода. Этот код, как и другие static инициализации, выполняется только однажды, в первый раз, когда вы создаете объект этого класса или при первом обращении к static члену класса (даже если вы никогда не создадите объект этого класса). Например:
//: c04:ExplicitStatic.java
// Явная static инициализация
// с предложением "static".
class Cup { Cup(int marker) { System.out.println("Cup(" + marker + ")"); } void f(int marker) { System.out.println("f(" + marker + ")"); } }
class Cups { static Cup c1; static Cup c2; static { c1 = new Cup(1); c2 = new Cup(2); } Cups() { System.out.println("Cups()"); } }
public class ExplicitStatic { public static void main(String[] args) { System.out.println("Inside main()"); Cups.c1.f(99); // (1)
} // static Cups x = new Cups(); // (2)
// static Cups y = new Cups(); // (2)
} ///:~
Static инициализаторы для Cups запускаются, либо когда происходит обращение к static объекту c1 в строке, помеченной (1), а если строка (1) закомментирована, то в строке, помеченной (2), если ее раскомментировать. Если и строка (1), и (2) закомментированы, static инициализация для Cups никогда не происходит. Также не имеет значения, если одна из двух строк, помеченных (2) раскомментированы; статическая инициализация происходит только один раз.
Язык Определения Интерфейсов CORBA(CORBA Interface Definition Language) - IDL
CORBA предназначена для независимости от языков: объект клиента может вызывать методы серверного объекта различных классов, не зависимо от языка реализации этих объектов. Конечно, клиентский объект должен знать имена и сигнатуру методов, прадоставляемых серверным объеком. Для этого сделан IDL. CORBA IDL - это не зависимый от языков способ указания типов данных, атрибутов, операций, интерфейсов и многого другого. Синтаксис IDL схож с синтаксисом C++ или Java. Следующая таблица показывает соответствия между некоторыми общими концепциями этих трех языков, которые можно указать в CORBA IDL:
CORBA IDL | Java | C++ | |||
Module | Package | Namespace | |||
Interface | Interface | Pure abstract class | |||
Method | Method | Member function |
Концепция наследования поддерживается так же, как испоьзование оператора двоеточие в C++. Прогаммист создает IDL описание атрибутов, методов и интерфейсов, которые реализуются и используются сервером и клиентом. Затем IDL компилируется предоставляемым производителем IDL/Java компилятором, читающим исходный IDL код и генерирующим Java код.
IDL компилятор очень полезный инструмент: он не просто генерирует Java код, эквивалентный IDL, он также генерирует код, который будет использоваться при передаче аргументов методов и при произведении удаленных вызовов. Эот код, называемый кодом якорей и скелетов, разбит на несколько файлов Java программы, и обычно является частью одного Java пакета.
Языки сценариев
Встраиваемые модули стали результатом взрывного распространения языков сценария. У языков сценария вы встраиваете исходный код для вашей программы стороны клиента прямо в HTML страницу, а встраиваемый модуль, который интерпретирует этот язык, автоматически активируется при отображении HTML страницы. Языки сценариев достаточно легки для понимания и, потому что они являются простым текстом, как часть HTML страницы, они загружаются очень быстро, как часть одного щелчка, необходимого для производства страницы. Минус в том, что ваш код открыт каждому для просмотра (и воровства). Обычно, однако, вы не делаете удивительно сложные вещи с помощью языков сценария, так что это не встречает особых трудностей.
Это говорит о том, что языки сценариев, используемые внутри Web просмотрщиков, реально предназначены для решения специфических проблем, в первую очередь создание богатого и более интерактивного графического пользователя (GUI). Однако языки сценариев могут решить 80 процентов проблем, возникающих при программировании на стороне клиента. Ваши проблемы могут полностью попадать в эти 80 процентов, так как языки сценариев могут предоставить простоту и быстроту разработки, вам, вероятно, нужно рассмотреть язык сценариев, прежде чем рассматривать более сложные решения, такие как Java или ActiveX.
Наиболее часто обсуждаемые языки сценариев для броузеров - это: JavaScript (который не делает ничего , что может Java; его название - это просто способ отобрать часть рынка Java), VBScript (который выглядит как Visual Basic) и Tcl/Tk, который пришел из популярного кросс-платформенного языка GUI-разработки. Есть и другие, не редко более развитые.
JavaScript, вероятно, наиболее часто поддерживается. Он встроен и в Netscape Navigator и в Microsoft Internet Explorer (IE). В дополнение, вероятно, о JavaScript существует больше книг, чем о других языках броузера, а некоторые инструменты автоматически создают страницы, используя JavaScript. Однако если вы уже владеете Visual Basic или Tcl/Tk, для вас более продуктивным станет использование этих языков сценариев, чем учить новый. (У вас и без того будут проблемы с Web.)