    Глава 11 

    Пакеты java.util n iava.lang 

    В этой книге еще не рассказывалось о двух базовых пакетах Java, хотя 
классы, входящие в пакеты java.util и Java.long, очень часто используются в 
программах. Причина, по которой их рассмотрение отложено на конец книги, 
заключается в том, что эти классы являются настолько важными и 
основополагающими для объектно-ориентированного программирования на Java, что 
в них трудно разобраться без предварительного знакомства с основными 
"сгюсобами программирования, используемыми данным языком. 
    Можно представить, как трудно было бы изучать математику в начальной школе 
без конкретных примеров, начиная сразу с чистой абстракции. Вместо того, чтобы 
объяснить ученику, как решается задача о двух яблоках, ему стали бы объяснять, 
допустим, концепцию числового ряда, концепцию операций над числами и общую 
концепцию математических формул. Ученик, вполне возможно, так и не понял бы, 
что один плюс один равняется двум. 
    Так же обстоит дело со многими классами, входящими в пакеты java.util и 
Java.long. Например, в классе Runtime пакета Java.long представлена среда 
выполнения ВМ. Но если не объяснить сперва, что ВМ представляет собой 
динамический интерпретатор байт-кода Java, то класс Runtime останется полной 
загадкой. Подобный метод изложения материала был бы эквивалентен попытке 
плавания в бурном море Java без руля и без ветрил. Однако если читатель 
познакомился с предыдущими главами этой книги или обладает хотя бы 
поверхностными знаниями о языке Java, то представленное ниже введение в классы 
пакетов Java.long и java.util будет ему вполне доступным и понятным. 
    В пакет java.util входят классы хранения, и различные "вспомогательные" 
классы, используемые практически во всех апплетах и приложениях. К классам 
хранения относятся: класс простого стека объектов; класс расширяемых массивов 
(Vector); класс ассоциативных массивов, или хеш-таблиц (Hashtable); класс 
Properties, описывающий разновидность хеш-табли"ц7 используемых для хранения 
пар строк "ключ/значение", ТГ класс""Я/У5е7Г предназначенный для хранения и 
эффективной обработки больших массивов двоичных данных. В нашей книге почти в 
каждом коде используется хотя бы один из этих классов. Как во все основные 
библиотеки математических классов C++ включены стеки, массивы переменной 
длины, ассоциативные массивы, так и в базовых пакетах Java представлены 
аналогичные Jayg-Kfigccjbi. Эти классы эквивалентны классам MFC CStack, 
CMapXToY и прочим MFC-классам. Поскольку в 

    321 

    jnaiceT java.util включены классы для всех ВМ, то не нужно ни повторно 
реализовывать их, ни отдельно распространять соответствующие класс-файлы. 
    В пакете java.util имеются разнообразные вспомогательные классы. В 
объектах Date, являющихся экземплярами класса java.util.Date, могут храниться 
любые значения даты и времени (с точностью до секунды) с начала XX века и на 
века вперед. Для генерации случайных чисел, что является системно-зависимой 
задачей для многих компиляторов C/C++, имеется класс java.util.Random. Объект 
Random генерирует последовательность псевдослучайных чирел с помощью 
специального генератора, по своему действию аналогичного функциям rnd() и 
random() языка С. 
    Наконец, класс StreamTokenizer, подробно описанный в главе 9, тоже включен 
в данный пакет. Поскольку этот класс часто используют для анализа входных 
потоков, его описание помещено в главу, рассказывающую о потоках данных, их 
фильтровании и анализе, хотя названный класс принадлежит пакету java.util, а 
не пакету потоковых классов java.io. 
    В состав пакета java.lang входят классы, которые можно разделить на пять 
функциональных групп. К первой группе относятся классы, описывающие простые 
типы данных. Например, каждый объект класса Boolean представляет булево 
значение. Почему для представления булевых значений требуется отдельный класс, 
если в Java уже имеется встроенный булев тип? Он нужен для создания 
экземпляров, которым надо передавать объект, представляющий булево значение. 
Когда объекту типа Vector, в котором могут храниться только объекты 
(порожденные классом Object), хотят передать несколько булевых значений, 
необходимо создать объект, который будет их представлять. Именно для таких 
применений в пакете java.lang имеется класс Boolean. Аналогично, в пакете 
присутствуют классы для представления всех остальных встроенных типов данных — 
символов, целых чисел, чисел с плавающей запятой и чисел двойной точности. 
    Ко второй группе классов пакета java.lang относятся так называемые 
метаклассы. Они позволяют создавать Java-классы и получать о них информацию на 
этапе выполнения. Класс Class представляет заданные Java-классы в среде 
выполнения Java. Класс ClassLoader пакета java.lang дает возможность 
динамически загружать классы в приложения Java. Он позволяет создавать новые 
экземпляры объекта Class из класс-файла программы. Динамическая загрузка и 
динамическое создание классов являются мощными средствами, используемыми в 
различных апплетах и приложениях Java. Подробнее об этом рассказано далее в 
этой же главе. 
    Статические методы класса Math, являющегося классом с единственным 
экземпляром, позволяют выполнять основные математические операции, необходимые 
любому языку программирования. Тригонометрические функции, функции сравнения, 
функции округления и множество других — все они реализованы в виде статических 
методов этого класса. 
    Еще одна группа классов пакета java.lang позволяет некоторым образом 
управлять ВМ и средой выполнения. Классы Runtime и System обеспечивают доступ 
к процессам ВМ во время выполнения программы. Например, с 

    322	Visual J++: основы программирования 

    помощью этих классов можно получить доступ к переменным среды и выполнить 
внешние программы внутри приложения или апплета Java. Объект SecurityManager 
контролирует доступ к системным ресурсам, способным представлять угрозу 
системе защиты. Менеджер безопасности позволяет или запрещает выполнять 
определенные операции, такие как создание новых сокетов или серверов, открытие 
или создание файлов в локальной файловой системе и т.д. 
    Наконец, к пятой группе классов пакета java.lang относятся остальные 
классы, описывающие различные концепции Java. Класс Object, являющийся 
первичным родительским классом всех остальных классов Java, входит в пакет 
java.lang, равно как классы String и StringBuffer. Эти три класса описаны в 
главе 1. Классы Thread и ThreadGroup, а также интерфейс Runnable являются 
родительскими для большого количества классов и используются в Java при 
организации многопотоковой обработки. Большое количество классов типа 
Exception, в том числе и сам класс Exception, также входят в пакет java.lang. 

    Классы хранения пакета java.citil 

    Основным требованием, предъявляемым к большинству нетривиальных апплетов и 
приложений Java, является удобство хранения данных. Для облегчения процесса 
программирования служат стеки, расширяемые массивы, хеш-таблицы и классы 
хранения прочих типов, представленные в пакете java.lang. Несомненно, каждому 
программисту приходилось реализовывать собственные классы хранения вследствие 
отсутствия необходимых возможностей у классов пакета java.lang или ради 
выигрыша в скорости. Но вообще-то классы хранения пакета java.lang на 90% 
обеспечивают потребности, возникающие при организации структур хранения данных. 
    Возможно, реализациями этих классов хранения интересуются чаще, чем 
реализациями остальных классов и пакетов Java. На это есть две причины. Первая 
из них заключается в том, что при создании пользовательского класса хранения 
полезно знать, как устроены уже имеющиеся классы хранения. Вторая причина 
состоит в том, что иногда бывает удобно порождать классы от уже имеющихся 
классов хранения, дополняя их новыми методами. Чем чаще пользуются классами 
хранения, тем чаще возникает желание выяснить, как они реализованы. 

    Интерфейс Enumeration 

    Во многих ситуациях бывает необходимо передать какому-нибудь методу в 
качестве параметра последовательность объектов. Несложный интерфейс 
Enumeration позволяет это сделать некоторым стандартным образом. Интерфейс 
Enumeration может быть объявлен в любом классе, реализующем два простых 
метода: hasMoreElements() и nextElementQ. 

    Глава 11. Пакеты lava.util и iava.lan*	323 

    Перечисление — это способ, с помощью которого можно задать 
последовательность объектов. Предполагается, что последовательность 
однонаправленная, подобно потоку байтов, описанному в главе 9. Имея объект 
перечисления, можно выполнить две следующие операции: с помощью метода 
Enumeration.hasMoreElements() выяснить, все ли элементы перечислены, и 
получить следующий объект перечисления с помощью метода nextElementQ. Обычно 
перечисления используются в циклах while следующим образом: 

    Enumeration enum; 
    // Переменной enum была передана некоторая ссылка на объект... ' 
while(enum.hasMoreElements()) { 
    Object о = enum.nextElement(); 
    // Действия, осуществляемые над объектом о. } 

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

    Enumeration enum; 
    // Переменная enum задана некоторой ссылкой на объект... for(int index=0; 
enum.hasMoreElements(); index++) { 
    Object о = enum.nextElement(); 
    // Действия, осуществляемые над объектом о. } 

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

    Vector  vect  =  new Vector(); 
    //  Объект  типа Vector  содержит различные  объекты. 
    //  Позднее  будет  получен  объект типа Enumeration  с  содержимым 
    //  объекта Vector. 
    Enumeration  enun  = vect.elements(}; 
    // Выполнение операций над объектами,   содержащимися в объекте Vector. 
While(enum.hasMoreElements())    { 


    324	Visual J++: основы программирования 


    Object о = enum.nextElement(); 
    // Выполнение операций над объектом... 


    Класс Stack 

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

    Помещение объекта в стек (Т) 
    В стеке находится N объектов 
    Новый объект 
    В стеке находится N+1 объектов самый верхний объект помещен последним 
    Извлечение объекта из стека 
    В стеке находится N объектов 
    В стеке находится N-1 объектов, самый верхний объект помещен предпоследним 

    Рис. 11.1. Стек 

    Оба этих метода меняют состояние стека, то есть его содержимое изменяется 
при каждом вызове одного из методов. Метод peekQ возвращает ссылку на объект, 
находящийся в верхней части стека, не убирая его из стека, так что следующий 
вызов метода рор() или peekQ возвратит ссылку на~тот же объект. 

    Глава 11. Пакеты iava.util n Java.lan* 325 

    У единственного конструктора класса Stack нет параметров: 

    public Stack(); 

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

    Класс Vector 

    Объект типа Vector является расширяемым массивом объектов. Он ведет себя 
как массив, в котором хранящиеся элементы идентифицируются по индексу, начиная 
с нулевого. Кроме того, объект типа Vector может расширяться, потому что для 
него не существует наперед заданного числа элементов, которое нельзя было бы 
превысить, как в случае с обычным массивом элементов. При добавлении элемента, 
который уже не помещается в отведенный объекту участок памяти, объект Vector 
запрашивает дополнительный объем памяти. Разумеется, существует некоторый 
предел доступной памяти; это может помешать объекту Vector выделить 
дополнительный объем памяти для размещения нового объекта. Максимальное 
количество элементов, которые можно хранить, составляет что-то около двух 
миллионов, поскольку индекс, используемый для идентификации хранящихся 
объектов, является числом типа int. 
    *~" Класс Stack порожден от класса Vector. Следовательно, кроме методов 
push(), pop() и peek(), реализованных в классе Stack, можно использовать все 
методы, объявленные как public в классе Vector. Класс Vector позволяет 
проверять и изменять ссылки, хранящиеся под различными индексами в объекте 
Vector. Поэтому нет никаких ограничений на использование методов push() и 
рор() для помещения объекта в стек и извлечения его оттуда. 

    Добавление п удаление элементов 

    Каждый элемент, добавленный в объект Vector, имеет определенный индекс. 
Можно приписать индекс объекту с помощью методов setElementAtQ и 
insertElementAtQ класса Vector. Оба метода принимают по два параметра: ссылку 
на объект, который подлежит хранению, и индекс, под котором он хранится. Метод 
setElementAtQ замещает объект, хранящийся с данным индексом, новым объектом. 
Метод insertElementAt() сдвигает все хранящиеся элементы, начиная от данного 
индекса, на один вверх, чтобы освободить место для нового элемента. Из рис. 
11.2 можно понять разницу между действиями, выполняемыми методами 
setElementAtQ и insertElementAt(). 

    326	Visual J++: основы программирования 


    setElementAT 
    / 
    / N   N+l  N+2 
    & 
    vfr 
    При добавлении нового объекта старый объект с номером N удаляется 
    insertElementAT 
    t, 
    / 
    N   N+l  N+2 
    Старый объект с номером N сдвинут 

    Рис. 11.2. Действия, выполняемые методами set ElementAt() и insert ElementAt() 

    Метод addElementQ добавляет заданный объект к объекту Vector. Добавляемому 
элементу присваивается следующий свободный индекс, который является числом, 
возвращаемым методом sizeQ. Метод setSizeQ принимает один параметр, который 
определяет новое максимально допустимое значение индекса. Метод setSizeQ 
уничтожает все объекты, чьи индексы превышают названное значение. 
    Итак, можно использовать три метода для присоединения элементов к объекту 
Vector: метод addElementQ, добавляющий объекты в объект Vector, метод 
insertElementAtO, помещающий объект в определенное место, и метод 
setElementAllQ, замещающий объект с указанным индексом новым объектом. 
    Имеются три метода и для удаления объектов из объекта Vector: метод 
removeElementO удаляет заданный элемент из объекта Vector, метод 
remo-veElementAt() удаляет элемент с указанным индексом, а метод 
removeAllEle-ments() удаляет все элементы. Вызов метода removeAllElements() 
приводит к такому же результату, как и вызов метода setSizeQ с параметром, 
равным нулю. 

    Глава 11. Пакеты lava.util и iava.lang 327 


    Емкость объекта Vector 

    Объект Vector содержит массив ссылок на объекты. В качестве массива 
используется переменная elementDatafj класса Vector, объявленная как 
protected. При создании объекта Vector можно указать начальный размер, или 
емкость (capacity), этого массива. Кроме того, можно указать приращение 
массива — размер памяти, выделяемой заново каждый раз, когда требуется 
увеличить массив для размещения новых элементов. В классе Vector имеется три 
конструктора, объявления которых приведены ниже: 

    public Vector(); 
    public Vector(int initial Capacity); 
    public Vector(int initial Capacity, int capacitylncrement); 

    Конструктор без параметров для задания начальных значений емкости массива 
и приращения использует значения, принимаемые по умолчанию. Конструктор с 
одним параметром использует значение по умолчанию только для приращения массива. 
    Метод capacity() класса Vector возвращает текущую емкость массива, то есть 
число объектов, которое объект Vector может хранить в настоящий момент без 
дополнительного выделения памяти. Метод ensureCapacityQ используется для 
увеличения текущей емкости объекта Vector. 
    Метод trimToSizeO класса Vector удаляет лишние элементы elementDatafj 
(т.е. те, которые не используются). Он размещает в массиве такое число 
элементов, какое возвращает метод size(). 

    Получение элементов из объекта Vector 

    Метод containsQ возвращает булево значение, указывающее, содержится 
лизанный объект в объекте Vector. Получив ссылку на определенный объект, метод 
вернет значение true, если этот объект содержится в объекте Vector, и значение 
false, если не содержится. 
    В приведенной ниже программе объект Vector используется в качестве простой 
базы данных имен. Получаемый из командной строки аргумент представляет собой 
имя файла, в котором содержится список строк. Введите какую-нибудь строку, и 
программа вернет ее индекс в объекте типа Vector. 

    import Java.util.Vector; import java.io.*; 
    public class SimpleStringDB { 
    public static void main(String[] astrArgs) { Vector vect = new Vector(); 
    // Получение аргумента командной строки, являющегося // именем файла 
строк, if(I > astrArgs.length) { 
    System.err.printIn("Usage Error:\n 
    SimpleStringDB <filename>"); 
    System.exit(-1) ; ) 


    328	Visual J++: основы программирования 


    // Добавление строк из входного файла в объект Vector. DatalnputStream in; 
try { 
    in = new DatalnputStream( 
    new FilelnputStream(astrArgs[0])); 
    for(String str; (str=in.readLine()).length() > 0;) vect.addElement(str); 
    in.close(); 
    } catch(EOFException eof) { } catch(lOException ioe) { 
    System.err.printIn("Error:\n 
    error rearing from input file "); 
    System.err.println(astrArgs[0] + ioe); 
    System.exit(-1); } 
    // Чтение введенных пользователем данных, вывод на экран // индекса 
строки, которую ввел пользователь, try { 
    String user_in;. 
    DatalnputStream in2 = new DatalnputStream(System.in); 
    while((user_in = in2.readLine()).length() > 0) { int index; 
    if("QUIT".equals(user_in)) System.exit(-0); 
    if(-l != (index = vect.indexOf(user_in))) 
    System.out.println("\tlndex is : " + index); else 
    System.out.println("\t\" " + user_in + 
    "\" is not in the list"); } 
    } catch(EOFException eof) { } catch(lOException ioe) { 
    System.err.println("Error:\n Reading from user: " +ioe) ; System.exit(-1); 
f • -..I.' ..>•:.. •. 
    }        ••,••-...:	.                           . ^  : 
    }	_••••-.••

    Метод indexOf() возвращает индекс заданного элемента, если этот элемент 
находится в объекте Vector. В противном случае метод возвращает значение —1. В 
приведенной программе проверка 

    if(-l != (index = vect.indexOf(str) )) 
    является краткой формой записи следующего кода: 
    if(vect.contains(str) ) { 
    int index = vect.indexOf(str) ; 

    У другой перегруженной версии метода indexOf() есть второй параметр, 
указывающий индекс, с которого следует начинать поиск. Используя цикл, 

    Глава 11. Пакеты lava.util и iavaJantf	329 

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

    public int indexOf(int[]) an, Vector vect, Object o) { int count = 0; 
    for (int nlndex=0; -1 != an[count] = vect.indexOf(o, nlndex); 
    nlndex=an[count++]); 
    return count; } 

    Итак, метод indexOfQ позволяет получить все ссылки на объект, от нулевого 
и до последнего индекса объекта Vector. Метод last!ndexOf() позволяет найти 
наибольший индекс данного объекта. Вторая перегруженная версия метода 
lastIndexOf() принимает два параметра: объект, который нужно найти, и 
последний индекс, с которого надо начинать поиск. Следующая реализация метода 
indexOfQ выполняет то же самое, что и в предыдущем примере, только значения, 
записанные в массив an, будут размещены в этом же массиве в обратном порядке: 

    public int indexesOf(int an[], Vector vect, Object o) { int count = 0; 
    for(int nlndex=vect.size(); -l!=an[count] = vect.lastindexOf(o, nlndex); 
nlndex=an[count++]) ; 
    return count; } 


    Пруте возможности класса Vector 

    Класс Vector реализует интерфейс Clonable. Это означает, что в нем 
реализован метод clone(). Если требуется получить объект Vector, являющийся 
точной копией данного объекта Vector, то следует использовать метод clone(): 

    Vector vectCopy = vect.clone(); 

    Метод copylntoQ копирует объекты, содержащиеся в объекте Vector, в 
предложенный массив. Этот метод создает точную копию массива ele-mentData[] 
объекта Vector, копируя не больше элементов, чем определено методом sizeQ. 
Необходимо удостовериться, что массив, передаваемый этому методу, достаточно 
велик, чтобы вместить все элементы объекта Vector. В противном случае метод 
выдаст исключение ArraylndexOutOffioundsExcep-tion. Код выглядит следующим образом: 

    Object!]   aobj   =  new Object[vect.size()]; vect.copylnto(aobj); 


    330	Visual J++: основы программирования 


    Класс Hashtable 

    Хеш-таблица является средством хранения пар ключ/значение. Ключ — это 
объект, который идентифицирует объект-значение. Если в объекте Vector для 
идентификации отдельных элементов используются индексы, то в хеш-таблице для 
идентификации хранящихся в ней объектов используются объекты-ключи. 
    Имя класса, Hashtable, подсказывает, что ключи хранятся в виде 
хеш-значений. Это весьма эффективный способ хранения объектов, поскольку их 
очень легко искать. В классе Object имеется метод hashcodeQ. Этот метод 
наследуется всеми классами Java. Единственное назначение этого метода — 
предоставлять для всех объектов хеш-значение, используемое в качестве ключа 
для хеш-таблицы. (Конечно, для выполнения собственных хеш-вычислений в Java 
можно использовать также значение, возвращаемое методом hashCodeQ. Однако в 
базовых пакетах Java этот метод используется только в классе Hashtable.) 
    Чтобы сделать хеш-таблицы максимально эффективными, можно создать для 
своих классов собственную реализацию метода hashCodeQ. В этой реализации 
должно создаваться псевдоуникальное 32-битовое целое значение для каждого 
экземпляра класса. Чем больше уникальных значений сможет выдавать метод 
hashCodeQ, тем более эффективным будет поиск в хеш-таблице. 

    Добавление и удаление элементов хеш-таблицы 

    Метод put() класса Hashtable используется для добавления объекта в 
хеш-таблицу. Методу передают объект-ключ и объект-значение. Метод get() 
возвращает объект-значение, связанный с переданным в качестве параметра объектом-ключом. 
    Метод remove() класса Hashtable в качестве параметра принимает 
объект-ключ. Этот метод удаляет объект-значение, связанный с данным 
объектом-ключом. Метод clearQ убирает из хеш-таблицы все пары ключ/значение. В 
классе Hashtable нет метода, отыскивающего ключ по заданному значению. Можно 
только найти значение по ключу. 
    Метод containsQ класса Hashtable очень похож на метод containsQ класса 
Vector. Он возвращает значение true, если объект, переданный ему в виде его 
единственного параметра, является объектом-значением, хранящимся в 
хеш-таблице. Аналогично, метод containsKeyQ возвращает значение true, если 
переданный ему объект является объектом-ключом хеш-таблицы. 
    Метод sizeQ класса Hashtable выполняет то же самое, что и метод класса 
Vector с тем же именем. Он возвращает число пар ключ/значение, хранящихся в хеш-таблице. 
    Метод elementsQ класса Hashtable возвращает объект типа Enumeration, 
содержащий все объекты-значения, которые хранятся в хеш-таблице на данный 
момент. В таблице эти объекты могут быть расположены не в том порядке, в каком 
они возвращаются данным методом. Наличие объекта-значения в перечислении 
означает только то, что он находился в хеш-таблице в момент вызова метода elementsQ. 

    Глава 11. Пакеты iava.util и lava.lan*	331 

    Метод key() класса Hashtable аналогичен методу elementsQ. Он возвращает 
объект-перечисление, состоящий из всех объектов-ключей, находящихся в 
хеш-таблице на момент вызова метода. Нижеприведенный код возвращает все 
объекты-ключи для данного объекта-значения, хранящегося в хеш-таблице. Хотя с 
одним объектом-ключом может быть связан только один объект-значение, у одного 
объекта-значения может быть любое количество объектов-ключей. (Можно создать 
хеш-таблицу, состоящую из одного значения-объекта и различных объектов-ключей.) 

    public int getKeysForValue(Object aobj[], Hashtable h, Object val) { 
Enumeration enum = h.keysO; int count = 0; 
    while(enum.hasMoreElements()) { 
    Object objKey = enum.nextElement(); 
    if(val == h.get(objKey)) 
    aobj[count++] = objKey; } 
    return count; 


    Класс Properties 

    Хеш-таблицы используются для хранения пар ключ/значение. Поскольку 
хеш-таблицы очень удобны, в пакете java.util имеются классы, порожденные от 
класса Hashtable, с методами, предназначенными для хранения и выборки пар 
ключ/значение jHna_Strmg (то есть и объект-ключ, и объект-значение являются 
строками). Этот класс называется классом Properties. 
    Например, класс System, существующий в единственном экземпляре и 
определяющий некоторые параметры среды выполнения апплетов и приложений Java, 
хранит все переменные среды Java в виде свойств в объекте Properties. При 
вызове метода getProperties() класса System возвращается объект Properties, 
состоящий из пар строк имя/свойство. Приводимая ниже простая программа выводит 
список всех текущих системных свойств в поток System.outPrintStream. 

    import java.util.Properies; 
    public class ShowSystemProps { 
    public static void main(String astrArgsf]) { 
    // Аргументы командной строки игнорируются. 
    Properties props = System.getProperties(); props.list(System.out); 


    332 Visual J++: основы программирования 


    Ниже представлен листинг вывода программы ShowSystemProps: 
    --listing properties -
    java.home=c:\java\bin\.. 
    awt.toolkit=sun.awt.Win32.MToolkit 
    j ava.version=l.0.2 
    file.separator=\ 
    line.separator= 
    Java.vendor=Sun Microsystems Inc. 
    user.name=Wizard 
    os.arch=x86 
    os.name=Windows NT 
    j ava.vendor.url=http://www.sun.com/ 
    user .dir=C: \doc\bra in\ jakarta\chap!2\projects\... 
    Java .class .path=. ;C: \java\lib\classes . zip;c: \java\cla... 
    Java.class.version=45.3 
    os.version=3.50 
    path.separator=; 
    user.home=C:\users\default 

    Подробнее о классах System и Properties будет говориться ниже. Здесь же 
речь идет о том, что в классе Properties помимо методов, унаследованных от 
класса Hashtable, имеется специальный метод сериализации, называемый listQ. 
Метод list() записывает в поток пары строк ключ/значение таким образом, что их 
можно прочитать с помощью метода IqadQ. Если передать методу loadQ поток 
ввода, "он заполнит объект Properties парами ключ/значение. 
    Как и класс Hashtable, класс Properties реализует интерфейс Clonable. 
Поэтому, используя метод cloneQ, можно создать точную копию объекта Properties. 
    Однако, в отличие от класса Hashtable, у класса Properties имеются 
свойства по умолчанию. Один из конструкторов класса Properties принимает в 
виде единственного аргумента другой объект типа Properties для установки 
значений свойств по умолчанию. Если использовать конструктор класса Properties 
для создания объекта Properties, а в дальнейшем запросить у этого объекта 
значение, связанное с несуществующим ключом, то значения свойств будут 
определяться объектом Properties, который был передан конструктору. Таким 
образом, объект Properties вернет значения, отыскивая их в таком порядке: 
    1. Если строка ключа находится в самом объекте Properties, то возвращается 
связанная с ним строка значения. 
    2.   В противном случае запрашивается значение, связанное с данным ключом, 
у объекта Properties, который был передан конструктору в качестве объекта по 
умолчанию. Если был использован конструктор без параметров, возвращается null. 
    3.   Если у объекта Properties, переданного конструктору, нет для данного 
ключа значения по умолчанию, то он рекурсивно запрашивает свой собственный 
объект Properties, который был передан его конструктору. 

    Глава 11. Пакеты Java.util и java.lang	333 

    Пункт 3 подразумевает рекурсию, поэтому при желании можно создавать 
длинную цепочку объектов Properties, принимаемых по умолчанию. 
    Метод getPropertyO класса Properties выполняет то же самое, что и метод 
get() класса Hashtable. Единственное различие между ними заключается в том, 
что методу getPropertyO необходимо передавать строку-ключ, а методу get() 
класса Hashtable — объект-ключ. Существует перегруженная версия метода 
getPropertyO, имеющая второй аргумент: значение по умолчанию, которое 
возвращается, если с данной строкой-ключом в объекте Properties не связано 
никакое значения. 

    Класс BitSet 

    Последним из классов хранения, включенных в пакет java.util, является 
класс BitSet. Объект BitSet представляет собой большой массив битов, каждый из 
которых может иметь значение true или false. В реализации класса BitSet 
создается массив из 32-битовых целых чисел. Пользователю не обязательно знать 
об этом. Он просто ссылается на каждый бит объекта BitSet по индексу массива, 
отсчитываемому от нуля. 
    В классе BitSet массив битов представляется в виде бесконечного массива. 
Если запросить бит с номером, скажем, 10 миллиардов, то объект BitSet вернет 
его значение. Если запросить значение бита, выходящего за границы массива, то 
класс просто вернет false, избавляя программу от необходимости обрабатывать 
еще одно исключение. 
    Класс BitSet, аналогично классу Vector, представляет собой расширяемый 
массив. Когда устанавливают значение конкретного бита, объект BitSet 
предоставляет пространство для его хранения. Если значение какого-нибудь бита 
еще не установлено, то объект BitSet при запросе значения этого бита 
возвращает false. 
    Методы get(), set() и clear() этого класса принимают в качестве параметра 
индекс бита в массиве, отсчитываемый от нуля. Метод get() возвращает текущее 
значение заданного бита (равное false в момент создания объекта BitSet и 
остающееся таким вплоть до явного использования метода set(), который меняет 
это значение). Метод set() устанавливает заданный бит в состояние true, а 
метод clear() устанавливает бит в состояние false. 
    В классе BitSet имеются удобные методы, предназначенные для объединения 
объектов BitSet. Реализации этих методов гораздо эффективнее циклов с методами 
clear() и set(). Метод and() выполняет операцию побитового И для двух объектов 
BitSet. Следующий код осуществляет операцию побитового умножения объектов bs2 
и bsl типа BitSet, сохраняя результат в объекте bsl. 

    BitSet bsl = new BitSet(); BitSet bs2 = new BitSet (); 
    // Объекты заполняются значениями... 
    // Над объектами выполняется операция И. Результат помещается // в объект 
bsl. bsl.and(bs2); 

    334 Visual J++: основы программирования 

    Аналогичным образом методы ог() и хог() выполняют соответственно операции 
побитового ИЛИ и исключающего ИЛИ. Для того чтобы удалить из объекта BitSet 
все значения, можно произвести операцию исключающего^ ИЛИ с ним самим: 

    Bsl.xor(bsl); 

    Как и другие классы хранения данных пакета java.util, класс BitSet 
реализует интерфейс Clonable. Поэтому с помощью метода сюпе() класса BitSet 
можно создать точную копию объекта BitSet. 

    Коииепция наблюдатель-наблюдаемый 

    В пакете java.util определен общий механизм уведомления, аналогичный 
механизму класса Event пакета java.awt. Объект Observable является источником 
сообщений об асинхронных изменениях. Объект Observer является их получателем. 
    Допустим, вы хотите, чтобы объект Observer уведомлялся каждый раз, когда 
какой-нибудь элемент помещается в стек или удаляется из стека. Структура 
наблюдатель-наблюдаемый автоматически реализует большую часть кода, 
обеспечивающего уведомление о помещении элементов в стек и изъятии их из стека 
и при этом корректно работающего с фоновыми программами. Использование такой 
структуры экономит время и усилия, затрачиваемые на отладку вашей собственной 
реализации этой функции. На приведенном ниже примере будет показано, как 
реализуются возможности структуры наблюдатель-наблюдаемый. 

    Объекты Observable 

    Обычно имена интерфейсов в Java заканчиваются суффиксом "-able", 
показывающим, что обеспечиваемая интерфейсом возможность реализуется в 
соответствующем классе. Классы Vector, Stack и Hashtable реализуют интерфейс 
Clonable, название которого подсказывает, что все объекты классов могут 
копироваться унаследованными методами cloneQ. Объекты, которые можно 
использовать для создания фоновых программных потоков, реализуют интерфейс Runnable. 
    Для объектов Observer/Observable справедливо обратное. В структуре 
наблюдатель-наблюдаемый все наблюдаемые объекты в конечном итоге являются 
потомками класса Observable. В этом родительском классе, определенном и 
реализованном в пакете java.util, имеется большой блок кода, предназначенный 
для создания объектов Observable способом, исключающим ошибки при работе с 
фоновыми программными потоками. Как вы скоро увидите, интерфейс Observer 
следует реализовать для всех объектов, 

    Глава 11. Пакеты iava.util n Java.lan*	335 

    которые могут принимать уведомления об изменениях, посланные объектами Observable. 
    Интерфейс ImageObserver тоже вписывается в эту модель: объект, который 
должен получать уведомления о создании изображения в памяти, объявлен как 
реализующий интерфейс ImageObserver. Класс Component пакета java.awt, 
являющийся родительским классом для всех классов иерархии Component/Container, 
реализует интерфейс ImageObserver. Таким образом, любой компонент или 
контейнер может получать уведомления о ходе создания изображения. 
    Почему в данном случае используется не совсем точная терминология? В 
основном это происходит вследствие того, что ответственность за доставку 
уведомлений и за поддержку списка объектов Observer лежит на объекте 
Observable. Код, который поддерживает список объектов Observer и доставляет им 
уведомления, определен в объекте-источнике, т.е. в объекте Observable. 
Создатели структуры наблюдатель-наблюдаемый, возможно, могли бы избежать этой 
неточности в выборе имен, использовав термины "Notifier" (уведомитель") и 
"Notifiable" ("уведомляемый") вместо "Observer" ("наблюдатель") и "Observable" 
("наблюдаемый"). Но структура наблюдатель-наблюдаемый основана на общепринятой 
модели, часто используемой в объектно-ориентированном программировании. 
    "Все объекты Observable содержат скрытую переменную, представляющую собой 
внутренний список всех наблюдателей, которым они могут послать уведомление об 
обновлении. Этот список уже реализован в классе Observable. И наоборот, у 
одного объекта Observer может быть множество наблюдаемых, одновременно 
посылающих ему уведомления. Любой объект Component, автоматически наследующий 
реализацию интерфейса ImageObserver, может получать уведомления о ходе 
создания нескольких изображений. Аналогичным образом любой наблюдатель может 
получать уведомления от нескольких наблюдаемых. (Однако зачастую бывает легче 
реализовать классы типа Observer, которые принимают уведомления только от 
одного наблюдаемого.) На рис. 11.3 показано, как объекты Observable отправляют 
уведомления нескольким объектам Observer. 

    Рис. 11.3. Структура наблюдатель-наблюдаемый 

    336 Visual J++: основы программирования 


    Добавление объектов Observer в список объекта Observable и удаление их из него 

    Чтобы добавить к списку объектов, которым отсылаются уведомления об 
изменении, еще одного наблюдателя, используется метод addObserverQ класса 
Observable. У этого метода имеется один параметр, определяющий объект, который 
надо добавить к внутреннему списку. Для удаления наблюдателя из списка 
используется метод deleteObserverQ класса Observable. У него тоже один 
параметр — имя наблюдателя, которое следует удалить из списка. 
    Эти методы уже реализованы в классе Observable. По умолчанию в реализациях 
этих методов обновляются внутренние списки объектов таким образом, что каждый 
наблюдатель появляется в списке только один раз. Если будет сделана попытка 
вызвать метод addObserverQ дважды с ЬднимТГтем же аргументом, то второй вызов 
будет просто проигнорирован. 
    Метод deleteObserversQ полностью очищает внутренний список наблюдателей. 
После вызова этого метода никаким объектам Observer не посылается ни одного уведомления. 

    Измененные объекты Observable 

    Кроме внутреннего списка в объектах Observable хранится дополнительная 
информация о состоянии. Это бит изменения, показывающий, менялся ли объект 
Observable. Если наблюдаемый объект изменился, но еще не послал уведомление об 
этом, этот бит устанавливается в true . 
    Метод setChangedQ класса Observable устанавливает этот бит в состояние 
true. Метод hasChanged() может вызываться внешним объектом для того, чтобы 
выяснить, подвергался ли объект Observable изменениям. Метод clearChangedQ 
устанавливает бит в исходное состояние, так что при любом последующем вызове 
метод hasChanged() вернет значение false. 
    Если необходимо, чтобы объект мог разослать уведомления наблюдателям, 
значащимся в списке, этот бит должен быть установлен. Для проверки установки 
бита используется метод hasChangedQ объекта Observable. Если в программе 
используются сложные методы обнаружения изменения объекта (основывающиеся не 
только на бите изменения), то надо модифицировать реализацию метода 
hasChangedQ так, чтобы он возвращал значение true только при выполнении 
определенных условий. 

    Уведомление объектов Observer 

    Чтобы заставить объект Observable послать сообщения об изменении всем 
своим наблюдателям, надо всего лишь вызвать метод notifyObservers(). Внутри 
этого метода происходит только вызов метода update() объекта Observer. 
    Существует перегруженная версия метода notifyObserversQ, которой в 
качестве параметра передается один объект. Данный объект затем передается 
методу updateQ объекта Observer. Можно использовать эту версию метода 
notifyObserversQ для передачи дополнительной информации объектам Observer, 
поскольку обычный вызов метода update() указывает лишь, что объект изменился 
каким-то неизвестным образом. 

    Глава 11. Пакеты iava.util n iava.lang	337 

    Ниже приведен простой пример класса Observablelnteger. Следует отметить, 
что внутри метода setlntQ этого класса по очереди вызываются методы 
setChangeQ, notifyObservers() и clearChanged(). Это обычная последовательность 
вызовов для уведомления наблюдателей об осуществлении изменений в объекте 
Observable. Перед вызовом метода notifyObservers() следует вызвать метод 
setChanged(), чтобы установить бит изменения объекта Observable. Без этого 
вызов метода notifyObserversQ может быть проигнорирован, поскольку на момент 
вызова бит изменения может быть установлен в состояние false. 

    import java.util.Observable; 
    class Observablelnteger extends Observable ( 
    // Private-переменная состояния является целым числом. private int m_nVal 
= -1; 
    public Observablelnteger() { } 
    public ObservableInteger(int n) { 
    m_nVal = n; } 
    public synchronized void setVal(int n) { m_nval = n; 
    setChanged(); notifyObservers(); clearChangedO ; } 
    public int getVal() { 
    return m_nval; } 


    Интерфейс Observer 

    Любой объект, реализующий интерфейс Observer, может получать уведомления 
об изменении от любого объекта Observable. Для того чтобы добавить объект к 
списку наблюдателей, которым наблюдаемый отправляет уведомления, следует 
передать объект Observer методу addObserver() объекта Observable. После этого 
при каждом вызове метода notiryObserverQ объекта Observable (если внутренний 
бит изменения объекта установлен) наблюдатель будет получать уведомления обо 
всех изменениях наблюдаемого. 
    В интерфейсе Observer объявлен только один метод. Это метод updateQ: 

    public abstracts void update(Observable o,   Object arg) ; 
    -   При каждом отправлении уведомления об изменении вызывается метод 
updateQ объекта Observable. Параметр о является ссылкой на объект, который 

    338 Visual J++: основы программирования 

    посылает уведомления. Параметр arg — это тот же самый объект, который 
передается перегруженной версии метода notifyObserverQ объекта Observable с 
одним параметром. Таким образом, один и тот же объект передается (в качестве 
параметра arg) перегруженной версии метода notifyObserver() и методу updateQ 
объекта Observable. 

    Пример апплета 

    Ниже приведен пример апплета типа клиент/сервер, основанного на концепции 
"наблюдатель-наблюдаемый". Апплет связывается с программой-сервером, 
работающей на машине, из которой был получен через Internet сам апплет. Этот 
сервер посылает введенные пользователем данные любой программе-клиенту, 
которая подключается к нему. В апплете объект Observable хранит последние 10 
строк, посланные приложением-сервером через сетевое соединение. (Для чтения и 
записи объектов типа String через сетевое соединение используются методы UTF 
DatalnputStream/DataOutputStream). Фоновый поток апплета периодически 
запускается, вызывает метод notifyOb-serverQ объекта Observable и обнуляет бит 
изменения объекта Observable. Апплет реализует интерфейс Observer. При каждой 
модификации объекта Observable апплет обновляет список, отображающий 10 строк, 
которые хранятся в объекте Observable. Активизация фонового потока происходит 
каждые 5 секунд. 
    Ниже приведен код программы-сервера. Эта программа устанавливает 
соединения с программами-клиентами и записывает в каждое сетевое соединение 
все, что вводит пользователь. Система наблюдатель-наблюдаемый используется для 
уведомления объекта-клиента о вводе пользователем новой "строки. 

    import java.net.*; 
    import java.io.*; 
    import Java.util.Observable; 
    import Java.util.Observer; 
    public class OserlnputServer extends Observable implements Runnable { // К 
переменным состояния относятся объект ServerSocket и // номер порта сервера, с 
которым надо связаться, private ServerSocket m_sock; private int m_nPort; 
    // Метод main() принимает аргумент командной строки, который // определяет 
номер порта, ожидающего запрос на установление // соединения. 
    public static void main (StringU astrArgs){ if(l > astrArgs.length)( 
    System.err.println ("Usage Error:\n UserlnputServer <port>"); 
System.exit(-1); } 


    Глава 11. Пакеты java.util n Java.lan*	339 


    int   riPort=0; try   { 
    nPort=Integer.parselnt(astrArgs[0]); }   catch   (NumberFormatException  nfe){ 
    System.err.println   ("Usage Error:\n UserlnputServer <port>"); 
    System.exit(-1); } 
    new UserlnputServer(nPort); 
    } 
    public UserlnputServer(int nPort) { 
    // Создание потока, ожидающего ввода данных от пользователя. 
    m_nPort = nPort; 
    Thread t = new Thread(this); 
    t.start(); 
    // Ожидание создания сокета на стороне сервера. 
    try 
    { 
    while (null == m_sock) 
    Thread.currentThread().sleep(SOO); } catch (interruptedException e) { 
    // Это исключение не должно появиться. } 
    // Вводимые пользователем данные отсылаются различным // объектам 
Observer, try { 
    DatalnputStream in = new DatalnputStream(System.in); 
    for(String s; !("quit".equals(s = in.readLine()));) 
    { 
    setChanged(); 
    notifyObservers(new String (s)); 
    clearChangedO ; 
    } 
    m_sock.close() ; } catch (EOFException eof) ( } catch (Exception e) { 
    System.err.println ("Error:\n reading user input."); 
    System.exit(-1); } 
    // Метод run() выполняется фоновым потоком, который ожидает // 
установления соединений и для каждого из них создает объекты // типа Client, 
public void run() { try { 
    m_sock = new ServerSocket(m_nPort); 
    for(Socket s = m_sock.accept(); true; s = m_sock.accept()) new 
ClientHandler(this, s) ; 


    340 Visual J++: основы программирования 


    ) catch(lOException ioe) { 
    System.err.println ("Error:\n 
    Opening server or listening:" + ioe); System.exit(-1); } ) } 
    // Экземпляр класса Client создается для каждого соединения. 
    //Он реализует интерфейс Observer и ожидает поступления объектов 
    // типа String, которые передаются методу update(). Их надо послать 
    // через сокет, переданный конструктору. 
    class ClientHandler implements Observer { 
    //К переменным состояния относится переменная типа DataOutputStream, // 
связанная с сокетом (в который записываются строки). private DataOutputStream m_out; 
    // Конструктор, которому передан объект Observable и сокет. public 
ClientHandler(Observable о, Socket s) ( 
    // Создание объекта DataOutputStream из объекта Socket, try { 
    m_out = new DataOutputStream(s.getOutputStream ()); } catch(lOException 
ioe) { 
    System.err.println ("Error:\n 
    trying to create new socket stream."); } 
    // Регистрация объекта Observer объектом Observable // для отправления 
уведомлений об обновлении, о.addObserver(this); ) 
    // Метод update() вызывается объектом UserlnputServer, // когда 
пользователь вводит новую строку текста. // Этот текст следует отправить через 
сокет. public void update(Observable o, Object objText) { try { 
    String strText = (String)objText; m_out.writeUTF(strText); 
    } catch (ClassCastException cce) { // Аргумент не должен иметь тип String. 
} catch (lOException ioe) { 
    System.err.println ("Error:\n 
    wtiting out to a socket: " + ioe); o.deleteObserver(this) ; } i 
    ; Co стороны клиента в этом приложении находится апплет. Апплет 
связывается с портом сервера. Порт, с которым необходимо связаться, обозначен 
параметром "port" апплета. Внутренний объект типа Observable 

    Глава 11. Пакеты lava.util n lava.lang	341 

    апплета хранит последние 10 строк, посланные сервером через сетевое 
соединение. Апплет является для этого объекта Observable объектом типа 
Observer. Каждые полсекунды фоновый поток вызывает метод notifyObser-vers() 
объекта Observable, который заставляет апплет обновлять отображаемые данные, 
если установлен бит изменения объекта Observable. 

    import Java.applet.Applet; import java.awt.*; import java.net.*; import 
java.io.*; import java.util.*; 
    public class UserlnputClient extends Applet implements Observer, Runnable 
{ // К переменным состояния относятся список, используемый //в окне апплета, 
программный поток, используемый для // периодических запросов объекта 
UserlnputClientObservable // о посылке уведомления об изменении, а также сам 
объект // UserlnputClientObservable. private List m_list; private Thread m_threadTimer; 
    private UserlnputClientObservable m_observable; private int m_nPort = -1; 
    // Метод init() создает окно со списком и определяет номер // порта 
сервера, с которым в методе start () будет // производиться соединение, public 
void init() { 
    setLayout(new BorderLayout()); 
    try { 
    m_nPort = Integer.parselnt(getParameter("port")); } 
catch(NumberFormatException nfe) { 
    add("Center", new LabelC'Need a valid 'port' parameter")); 
    return; } 
    m_list = new LisstO; 
    addC'Center",   m_list) ; 
    add("North", new Label("Last 10 from the server.")); 
    // Метод start () запускает новый поток-таймер и создает // новый объект 
UserlnputClientObservable, добавляя Applet в // качестве объекта типа 
Observer, public void start () { try ( 
    m_observable = new UserlnputClientObservable( InetAddress.getByName(getDocumentBase().getHost()), 
    m_nPort); 
    m_observable.addObserver(this) ; } catch (UnknownHostException uhe) { 


    342 Visual J++: основы программирования 


    m_observable = null; return; } 
    m_threadTimer = new Thread(this); m__threadTimer.start(); } 
    // Метод stopO останавливает работу объекта // UserlnputClientObservable и 
потока-таймера, public void stop() { 
    m_observable.stop(); 
    m_threadTimer = null; } 
    // Метод run() запускает поток-таймер. Каждые полсекунды он // заставляет 
объект UserlnputClientObservable отправлять // уведомление об обновлении, 
public void run () { try { 
    while(m_threadTimer == Thread.currentThreadO) { 
Thread.currentThread().sleep(500); if(null != m_observable) 
    m_observable.notifyObservers(); } } catch (InterruptedException ie) { 
    return; } } 
    // Метод update() вызывается объектом 
    // UserlnputClientObservable, когда с сервера поступают // новые данные. 
Applet обновляет свой экран, очищая список // m_list и заново заполняя его 
строками, хранящимися в объекте // Observer. 
    public void update(Observable o, Object arg) { if(o != m_observable) return; 
    String[] astr = m_observable.getLatestStrings() ; 
    m_list.clear() ; 
    for(int ii=0; ii<astr.length && null != astr[ii); 
    ii++) 
    m_list.addltern(astr[ii] ) ; } } 
    // Объект UserlnputClientObservable создает фоновый поток, 
    // который принимает поступающие от сервера строки. Он соединяется 
    // с сервером и запускает фоновый поток в конструкторе. 
    // Устанавливает бит изменения, когда приходит новая строка. 
    class UserlnputClientObservable extends Observable implements Runnable 


    Глава 11. Пакеты lava.util n iava.lang	343 


    // К переменным состояния относятся список, состоящий из 10 
    // последних строк (необязательно расположенных по порядку) 
    // фонового потока, а также номер порта, с которым 
    // поддерживается связь. 
    private String!] m_astr = new String[10]; 
    private Socket m_sock; 
    private Thread m_thread; 
    // Конструктор принимает номер порта, создает фоновый поток, public 
UserlnputClientObservable(InetAddress addrHost,int nPort) try { 
    m_sock = new Soxket(addrHost, nPort); } catch(lOException ioe) { 
    System.err.println("Error:\n connecting to server"); m_sock = null; 
return; } 
    m_thread = new Thread(this); m_thread.start(); } 
    // Applet вызывает этот метод stop(), когда вызывается его // собственный 
метод stop(). public void stop() { m_thread = null; if (null != m_sock) try { 
    m_sock.close() ; } catch (lOException ioe) {) 
    // Applet вызывает метод getLatestStrings() для получения 
    // 10 последних строк. 
    public String!] getLatestStrings() { 
    clearChanged(); 
    return m_astr; } 
    // Метод run() выполняется фоновым потоком. Он создает // DatalnputStrem и 
читает строки с сервера, помещая их в // m_astr. public void run () { int 
m_nStr = 0; 
    try   { 
    DatalnputStream in = new DataInputStream(m_sock.getInputStream()), 
while(m_thread == Thread.currentThread())    { 
    String s  =  in.readUTFO ; 
    m_astr[m_nStr]   =  s; 
    m_nStr  =   (m_nstr  +   1)   %  m_astr.length; 
    setChanged(); ) 


    З44 Visual J++: основы программирования 


    m__sock. close () ; } catch (Exception e) { 
    System.err.println("Error:\n reading from server."); 
    } 
    
    
    Прочие классы пакета java.iitil 

    В пакете java.util есть еще три класса. Эти классы не менее полезны, чем 
классы, описанные ранее. Хотя, их,трудно дтнещг к^какщЬлибо категории, 
объяснить особенности их применения не составит труда. Об одном из этих 
классов, классе StreamTokenizer, рассказывалось в главе 9, поскольку объекты 
этого класса служат для анализа потоков данных. 

    Класс Date 

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

    public Date(); 
    public Date(int year, int month, int date); 
    public Date(int year, int month, int date, int hours, int mins); 
    public Date(int year, int month, int date, int hours, int mins,int sees); 
    public Date(long time); 
    public Date(String strTimeDate); 

    Первый конструктор (не принимающий аргументов) создает объект класса Date, 
представляющий текущие значения даты и времени. Второй создает объект класса 
Date, представляя 0 часов указанного дня, месяца и года. Третий конструктор 
позволяет создать объект класса Date с точностью до минуты. В четвертом 
указывается время с точностью до секунды. Пятый принимает целое значение типа 
long, которое является числом миллисекунд, прошедших от полночи 1 января 1900 
года. (Это значение можно получить с помощью метода System.currentMillis()). 
Шестой конструктор принимает значение даты в формате системы UNIX. Эта строка 
выглядит следующим образом: 

    Sat,    12  Аид   1995   13:30:00  GMT 

    Он также "понимает" аббревиатуры временных зон США и разницу между местным 
временем и GMT (Greenvich Mean Time — среднее время по Гринвичу): 

    Sat, 12 Аид 1995 13:30:00 GMT+0500 


    Глава 11. Пакеты iava.uNI n lava.lan*	345 

    Эта строка имеет тот же формат, что и строка, возвращаемая методом 
GMTStringQ данного класса, а также соответствует стандарту, используемому в 
Internet (например, для поля Date заголовков писем электронной почты). 
    В классе Date имеются методы чтения-записи для каждого из параметров: года 
(getYearQ, setYearQ), месяца (getMonthQ, setMonthQ), дня, часа, минуты и 
секунды. Имеются также аналогичные методы для указания дня недели, в которых 
воскресенью приписывается значение 0, а субботе — значение 6. 
    Методы getGMTStringQ и getLocaleStringQ этого класса возвращают значение 
времени, представленное в виде строки. Методы before() и afterQ являются 
методами логического сравнения. Метод before() возвращает значение true, если 
передаваемый в качестве его единственного параметра объект Date указывает 
время более раннее, чем время, представленное данным объектом Date. Метод 
after() возвращает значение true, если передаваемый в качестве его 
единственного параметра объект Date представляет время более позднее, чем 
время, указываемое данным объектом Date. Реализация метода equalsQ возвращает 
значение true для двух различных объектов типа Date в том случае, если они 
представляют одинаковое время. 
    Наконец, метод UTCQ данного класса возвращает значение типа long, которое 
является точным числом миллисекунд, прошедших с полночи 1 января 1900 года до 
времени, представляемого объектом типа Date. 

    Класс Random 

    Для автоматического создания последовательностей псевдослучайных чисел 
создатели языка Java включили в пакет java.util класс Random. Из случайного 
начального значения в виде произвольного заданного числа типа long объект 
Random генерирует псевдослучайные последовательности чисел типов int, long, 
double и float. 
    Первый конструктор без аргументов инициализируется с использованием 
значения типа long, возвращаемого методом System.currentTimeMillisQ. Второй 
конструктор принимает один параметр типа long, который является начальным 
значением, используемым при генерации случайных чисел. Применяя метод setSeedQ 
класса Random, можно в любой момент заменить начальное значение генератора 
случайных чисел объекта Random. Метод setSeedQ принимает число типа long. 
    Методы nextlntQ, nextLongQ, nextFloat() и nestDoubleQ возвращают следующее 
псевдослучайное число последовательности. Методы nextlnt() и nextLong() 
возвращают значения целочисленного типа. Методы nextFloat() и nextDouble() 
возвращают значения в диапазоне от 0.0 до 1.0. 

    Числовые классы пакета java.lang 

    При программировании на Java может оказаться необходимым сохранить 
какое-нибудь числовое значение для его дальнейшего использования. Но нельзя 
содержать это значение в классах хранения данных пакета java.util, 

    346	Visual J++: основы программирования 

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

    public void storeVal(Vector vect, int n) { int [ ] an = new int[l]; an[0] 
= n; vect.addElement(an); 
    } 

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

    public void storeVal(Vector vect, int n) { vect.addElement(new Integer(n)); 

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

    Класс	Что представляет 

    Boolean	Булевы значения 
    Character	Символьные значения 
    Double	Значения с плавающей запятой двойной точности 
    Float	Значения с плавающей запятой 
    Integer	Целые значения 
    Long	Длинные целые значения 
    Родительский класс значений числовых типов (Float, Double, Integer и Long) 
— класс Number. В нем определены четыре абстрактных метода: 
    public double doubleValue(); public float floatValue(); 


    Глава 11. Пакеты lava.util n iava.lan*	347 


    public int intValueO; public void longValuef); 

    Они представляют собой методы преобразования, позволяющие приводить любое 
число к одной из четырех основных форм. (В версии Java 1.1 в пакете java.lang 
имеется класс для типа Short. В этой книге описывается главным образом версия 
Java 1.0, на которой основывается Visual J++. В последней главе обсуждаются 
некоторые преимущества, предоставляемые следующей версией Java, и расширения, 
которые будут созданы для Visual J++ в будущем.) 
    Помимо упомянутых методов приведения в числовых классах присутствует 
группа методов, предназначенных для преобразования чисел в строки и, наоборот, 
строк в числа. Например, в классе Integer имеется метод getlntegerQ, 
выполняющий преобразование строки в новый объект типа Integer. Метод parselntQ 
этого класса аналогичным образом преобразует строку в целое значение. 
    Для преобразования целого числа в строку используются также методы 
toBinaryStringO, toOctalStringQ, toHexStringQ и toStringQ, каждый из которых 
интерпретирует переданное ему значение как представленное в соответствующей 
системе счисления (последний метод преобразует в строку любое число). 
    Следует отметить, что методы parseXXXQ и toA%?String() 
яш1яются_стати-ческими и присутствуют в различных числовых классах. 
Метод~раг8еА1Щ) преобразует переданную строку в целое или в значение Integer. 
Статический метод toXXXStrmgQ создаст и вернет новую строку, получив целое 
значение или значение Integer. (Помимо этого существует нестатический метод 
toStringO, который позволяет представить объект Integer в десятичной форме.) 
    Итак, эти числовые классы применяются там^где^нужно использовать числовое 
значение в методе, требующем передачи ём^о^ъёктав'ъ качестве аргументов. Ниже 
приведен пример программы, использующей числовые классы для хранения числовых 
объектов в объекте типа Stack пакета java.util.Stack. В хорошо известной 
нотации "обратная польская запись" вместо привычной записи операций 

    <операнд>  <оператор>  <операнд> 
    например 
    5   +   2 
    производится запись следующего типа: 
    <операнд>   [<операнд>   [<операнд>  ...] ]   <оператор> 
    Следовательно, получаем 
    52   + 


    348 Visual J++: основы программирования 


    Эта запись облегчает вычисление выражений. Например, сложное выражение 
    ((5*4)   +   10   +  20   /   (2   +   1) )   %   7 
    будет выглядеть следующим образом: 
    5 
    4 
    *	// (5 * 4) 
    10 
    +	//(5*4)+ 10 
    20 
    2 
    1 
    + 
    /	// 20 / (2 + 1) 
    +	// 5 * 4) + 10 + 20 / (2 + 1) 
    7 
    %	// ((5 * 4) + 10 + 20 / (2 + 1) ) % 7 

    Разумеется, при этом облегчается не запись выражения, а именно его 
вычисление. В приводимой ниже программе с помощью стека реализован калькулятор 
с представлением операндов в "обратной польской записи". Последовательно 
расположенные числовые элементы польской записи помещаются во внутренний стек. 
Для представления числовых значений используются объекты типа Double. При 
вводе оператора, такого как '+' или '*', из стека извлекается последний 
операнд, производится действие, и результат снова помещается в стек. Можно 
рассматривать эту программу просто как задачу по программированию. Вот как она 
реализуется на Java: 

    import java.util.Stack; import java.io.*; 
    public class RPCalculator { 
    public static void main(String astrArgs[]) { 
    // Аргументы командной строки игнорируются. 
    // Каждая прочитанная из System.in строка 
    // является или операндом (числом), или оператором. 
    // Операнд помещается в стек. При появлении оператора 
    // производится вычисление с использованием 
    // операндов из стека. 
    Stack stack = new Stack (); 
    try { 
    String str; 
    DatalnputStream in = new DatalnputStream(System.in); while(true) { 
    str = in.readLine(); 
    // Если это операнд, вызывается соответствующий // метод-обработчик, if("+".equals(str)) 


    Глава 11. Пакеты iava.util и iava.lan*	349 


    doAdd(stack); else if ("-".equals(str)) 
    doSub (stack); else if ("*".equals(str)) 
    doMult(stack); else if("/".equals(str)) 
    doDiv(stack); else if("%".equals(str)) 
    doMod(stack); 
    else if("quit".equals(str)) 
    break; else try {     // Прмещение^операнда в стек. 
    Double d = Double. valueOf (str) ;"'""" 
    stack.push(d); } catch (NumberFormatException nfe) { 
    System.err.println("\tWhat?"); 
    // Отображение текущего состояния стека. doShowResult(stack); } } catch 
(EOFException eof) { 
    // Конец программы, вывод результата на экран. doShowResult(stack); } 
catch (JOException ioe) { 
    System.err.println("10 error: " + ioe); System.exit (-1); 
    } 
    public static void doAdd(Stack s) { Double opl, op2; try { 
    opl = (Double)s.pop(); 
    op2 = (Double)s.pop(); 
    s.push(new Double(opl.doubleValue() + 
    op2.doubleValue())); ) catch (ClassCastException cce) { 
    System.err.println("Error:\n improper args on the stack.") 
    System.exit(-1); } 
    public static void doSub(Stack s) { Double opl, op2; try { 
    opl = (Double)s.pop (); 
    op2 = (Double)s.pop(); 
    s.push(new Double(op2.doubleValue() - opl.doubleValue())). } catch 
(ClassCastException cce) { 
    System.err.println("Error:\n improper args on the stack."); 


    350 Visual J++: основы программирования 


    System.exit(-1); } 
    } 
    public static void doMult(Stack s) { Double opl, op2; try { 
    opl = (Double)s.pop(); 
    op2 = (Double)s.pop(); 
    s.push(new Double(opl.doubleValue() *op2.doubleValue())) } catch 
(ClassCastException cce) { 
    System.err.println("Error:\n improper args on the stack.") 
    System.exit(-1) ; } ) 
    public static void doDiv(Stack s) { Double opl, op2; try { 
    opl = (Double)s.pop(); 
    op2 = (Double)s.pop() ; 
    Double d = new Double(op2.doubleValue() / opl.doubleValue()); 
    if(d.isNaN() II d.islnfinite()) { 
    System.err.println("Illegal arithmetic operation."); System.exit(-1); ) 
    s.push(d); ) catch (ClassCastException cce) { 
    System.err.println("Error:\n improper args on the stack."); System.exit 
(-1); } } 
    public static void doMod(Stack s) { Double opl, op2; try { 
    opl = (Double)s.pop(); :    op2 = (Double)s.pop(); 
    Double d = new Double(op2.doubleValue() % opl.doubleValue()); if (d.isNaNO 
II d.islnf inite ()) { 
    System.err.println("Illegal arithmetic operation."); System.exit(-1); } 
    s.push(d); } catch (ClassCastException cce) { 
    System.err.println("Error:\n improper args on the stack."); System.exit(-1); 
    // Метод doShowResult() выводит на экран состояние стека // Использует 
методы класса Stack, унаследованные от // класса Vector. 


    Глава 11. Пакеты lava.util n java.lan*	351 


    public static void doShowResult(Stack s); 
    System.out.println("\tStack's current state:"); for(int ii=0; ii<s.size(); 
ii + + ) 
    System.out.println("\t" + s.elementAt(ii)); } } 


    Объекты типа Class и загрузчики классов 

    Как уже говорилось в главе 1, каждый класс Дщ..хранится в отдельном 
класс-файле. Создавать экземпляр класса можно только после того, как класс 
загружен в среду выполнения Java. Например, когда броузер загружает 
Java-апплет из HTML-файла, ВМ в первую очередь загружает класс-файлы апплета 
из соответствующего URL. Аналогичным образом при загрузке этого апплета в ВМ 
все классы, на которые ссылается апплет, загружаются либо из локальной 
файловой системы (с помощью переменной среды CLASSPATH, которая сообщает ВМ, 
где искать класс-файлы), либо из URL. 
    Каждый загруженный в ВМ класс представлен в виде объекта типа Class. 
Непосредственно используя такой объект, можно получить имя класса, создать 
новый экземпляр класса и даже получить список всех интерфейсов, реализуемых 
классом. Все это выполняется с помощью методов класса Class. 
    В этой главе описан класс Class, его методы, способ получения объектов 
этого класса и способ загрузки объектов в среду выполнения. Кроме того, здесь 
показано, как можно самостоятельно создавать загрузчики классов. Они являются 
мощным средством, позволяющим расширять список доступных классов, однако 
создавать с их помощью новые классы в апплете нельзя. 

    Класс Class 

    Наследуемый метод getClassQ класса Object, принимая в качестве аргумента 
имя объекта, возвращает ссылку на объект типа Class, описывающий класс 
заданного объекта. Например, при создании объекта типа Vector можно получить 
ссылку на объект типа Class, описывающий класс Vector, с помощью метода 
getClassQ класса Vector. Это делается следующим образом: 

    Vector vect = new Vector(); 
    Class classVector = vect.getClass(); 

    Метод getClassQ возвращает объект типа Class для любого объекта. 
    Существует способ получения объекта типа Class с использованием имени 
класса. Статический метод forName() класса Class возвращает объект типа Class 
для любого класса, чье имя содержится в строке параметра. Например, в 
следующем коде будет получен объект типа Class для класса "Object": 

    Class classObject = Class.forName("Object"); 

    352	Visual J++: основы программирования 

    Метод forNameQ работает даже тогда,, когда,указанный 'класс еще не 
загружен в ВМ. Метод forNameQ обращается для поиска указанного класса 
непосредственно к загрузчику классов ВМ. Если класс еще не загружен, то 
загрузчик классов попытается загрузить его А среду выполнения. Если класс-файл 
находится в одном из каталогов локальной файловой системы, указанном 
переменной CLASSPATH, то загрузчик файлов загрузит его из локальной системы и 
затем вернет связанный с ним объект типа Class. Этот способ загрузки классов 
подходит и для апплетов, и для приложений Java. 
    Что можно сделать со ссылкой на объект Tjma_Class? Например, можно создать 
с ее помощью новый экземпляр класса. Метод newInstanceQ класса Class 
возвращает новый экземпляр класса. Это похоже на использование для создания 
нового объекта оператора new илй~констщкгора без аргументов. Единственное 
различие заключается в том, что знать имя класса во время компиляции 
необязательно. Вместо этого можно с помощью метода for-NameQ класса Class 
создавать экземпляры объектов, чьи классы даже еще не существовали при 
создании апплета или приложения. 
    В следующем примере в командной строке можно указывать любое количество 
имен классов. Приложение попытается ^загрузить указанный класс в среду 
выполнения и создать новый экземпляр с помощью метода newInstanceO класса 
Class. Далее приведен пример апплета HelioWorldFrame, который отображает текст 
"Hello, World!" в некотором окне. При выполнении программы InstantiateClass на 
экране появится окно, внешний вид которого показан на рис. 11.4. 

    public class InstantiateClass { 
    public static void main(String[] astrArgs) { 
    // Попытка загрузить класс для каждого объекта, // определяемого 
аргументами командной строки, // и создать его экземпляр. for(int ii=0; 
ii<astrArgs.length; ii++) { try { 
    Class els = Class.forName(astrArgs[ii]); Object о = els .n?wI_nstance () ; 
} catch (ClassNotFouTTdException cnfe) { 
    System.err.println("Class not found: " + astrArgs[ii]); } catch 
(InstantiationException ie) { 
    System.err.println("Instantiation error with class " + astrArgs[ii]); } 
catch (IllegalAccessException iae) { 
    System.err.println("No rights to constructor for 
    class" + astrArgs[ii]); } catch (NoSuchMethodException nsme) { 
    System.err.println("Class doesn't have no-arg constructor" + 
astrArgs[ii]); } } } 1 


    Глава 11. Пакеты iava.util и java.lang	353 


    HelloWoildFiame - Miciosol! Developei Studio 
    jj He :?tlit; Ида :|гая(. firojecl   Juld lools  ^refew   Help 
    FTT7^||§ ; arft.g. | i, • ^ P i 3 
    Ji-*'® ЁШ'*» 
    4? «a i>3-; "i:! 
    ® Workspace ;Не1^9[№ате': 1 pro ffl-S HeiioWoildFrame fiies       "" 
    ^С1а^:|Ц,Г|ЗДот>|, g'jjWjwj 
    public HelloWorldFrame()   { 
    sup<sr( 'Hello,   World!!!"); resize(200,   150): show()• 
    } 
    public boolean handleEvent(Event evt) { if(Event.?IHDOtf_DESTROY « evt.id) 
{ dispose(); return true; 
    } 
    return false; } 
    ———————————————Configuration: HelloWorldFrane - Javt. Compiling... 
    Microsoft (R) Visual J++ Compiler Version 1.01.7022 Copyright (C) 
Microsoft Corp 1996. All rights reserved. 
    HelloWorldFrame. class - 0 error(s). 0 warning(s) 
    •Г 

    Puc. 11.4. Окно HelloWorldFrame, появляющееся при выполнении приложения InstantiateClass 

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

    Листинг класса HelloWorldFrame выглядит следующим образом: 

    import java.awt.*; 
    public class HelloWorldFrame extends Frame { public HelloWorldFrame() { 
    super("Hello, World!!!"); resize (200, 150); show(); } 
    public boolean handleEvent(Event evt) ( if(Event.WINDOW_DESTROY == evt.id) 
{ dispose (); return true; } 
    return false; } } 


    354 Visual J++: основы программирования 

    Первое, что бросается в глаза в приложении InstantiateClass,— это большое 
количество блоков catch, заключающих вызовы методов Class.forName() и 
Class.newlnstance(). Каждый из блоков служит для обработки определенной 
ошибки, которая может возникнуть при попытке загрузить класс или создать новый 
экземпляр класса. Ниже объяснено назначения всех этих блоков. 
    Если загрузчик классов ВМ не может обнаружить необходимый класс-файл, то 
вызов метода Class.forNameQ приведет к выдаче исключения 
ClassNotFoundException. Если класс уже загружен, то это исключение получено не 
будет. Действительно, загрузчику классов не понадобится загружать классы, 
поскольку они уже загружены. Исключение ClassNotFoundException может быть 
получено только в том случае, если ВМ не обнаружит класс-файл. Все, что 
касается загрузчиков классов, рассмотрено в следующем параграфе. 
    Выдача исключения InstantiationException происходит в том случае, когда 
пытаются создать новый объект абстрактного класса или интерфейса. Например, 
если приложению InstantiateClass передали в качестве аргумента строку 
"java.lang.Observer", то приложение сообщит о возникновении ошибки при попытке 
создания экземпляра. Это происходит вследствие того, что имя класса 
"java.lang.Observer" на самом деле является именем интерфейса (интерфейс 
Observer принадлежит пакету java.lang). Нельзя создать экземпляр абстрактного 
класса или интерфейса. При попытке сделать это всегда происходит выдача 
исключения InstantiationException. 
    Выдача исключения Illegal Access Exception происходит в том случае, если 
метод newInstanceQ применяется к классу, чей конструктор без параметров не 
объявлен как public. По определению метода newlnstance(), создание нового 
экземпляра происходит при вызове конструктора данного класса без аргументов. 
Если такой конструктор не объявлен как public, то фактически вызывается метод, 
к которому вы не имеете доступа. При этом выдается 'исключение IllegalAccessException. 
    Наконец, выдача исключения NoSuchMetodException осуществляется методом 
newInstanceQ, если в классе вообще нет конструктора без параметров. Это очень 
важно, поскольку метод newInstanceQ создает экземпляры только посредством 
вызова такого конструктора. Если конструктора без параметров нет, то среда 
выполнения осуществляет выдачу исключения NoSuchMetodException. 
    Итак, хотя комбинация методов newlnstance() и forNameQ позволяет вводить 
дополнительные классы во время выполнения приложения или апплета, при ее 
использовании надо избегать потенциально возможных ловушек в виде исключений 
ClassNotFoundException, InstantiationException, IllegalAccessException и NoSuchMetodException. 

    Прочие методы класса Class 

    Метод isInterfaceQ класса Class возвращает значение true, если объект типа 
Class описывает не класс, а интерфейс. В противном случае он возвращает 
значение false. Данный метод можно использовать для того, чтобы 

    Глава 11. Пакеты iava.util и java.lang	355 

    избежать выдачи исключения InstantiationException, но следует помнить, что 
это исключение выдается и тогда, когда класс является абстрактным. 
    Как показано в следующем примере, метод getNameQ класса Class возвращает 
строку с именем загружаемого класса. 
    Метод getlnterfacesQ возвращает массив объектов типа Class, каждый из 
которых описывает один из интерфейсов, реализованных классом. Аргументом 
командной строки в нижеприведенном примере служит имя класса, который следует 
загрузить. Затем программа перечисляет все интерфейсы, реализованные этим классом. 

    public class Listlnterfaces { 
    public static void main(String[] astrArgs) { for(int ii=0; 
ii<astrArgs.length; ii++) { try { 
    Class els = Class.forName(astrArgs[ii]); Class[] aclslnterfaces = 
els.getlnterfaces (); System.out.println("Class " + els.getName() + ":"); 
for(int jj=0; jj<aclslnterfaces.length; jj++) 
    System.out.println("\t" +aclslnteerfaces[jj].getName(}); } catch 
(ClassNotFoundException cnfe) ( 
    System.err.println("Error:\n class not found " + 
    astrArgs[ii]); } j ) } 

    Метод getSuperClassQ возвращает ссылку на объект типа Class, описывающий 
родительский класс данного класса. Например, при получении ссылки на класс 
типа java.awt.Container и вызове его метода getSuperclassQ возвращается ссылка 
на объект типа Class, описывающий класс Component, поскольку класс Container 
порожден непосредственно от класса Component. 
    Наконец, метод getClassLoaderQ класса Class возвращает ссылку на загрузчик 
классов, используемый при загрузке классов в среду выполнения. До сих пор мы 
ничего не говорили об объектах типа ClassLoader, так что давайте сначала 
познакомимся с ними, а заодно рассмотрим и то, как они связаны с объектами 
типа Class. 

    Загрузчпкп классов 

    Итак, каким образом ВМ загружает новые классы? Откуда она "знает", как 
читать класс-файлы из локальной файловой системы или из Internet (при загрузке 
их из апплетов)? Откуда она знает, как преобразовать эти файлы в исполняемые 
байт-коды? Все это выполняется загрузчиком классов (class loader). Класс 
ClassLoader осуществляет преобразование массива байтов, соответствующего 
формату класс-файла, в исполняемый код для ВМ Java. Реализовав собственный 
загрузчик классов, пользователь может загружать классы из файлов, по заданному 
URL или откуда-нибудь еще. 

    356	Visual J++: основы программирования 

    При указании загрузить класс с именем "Foo" загрузчик классов ВМ Java по 
умолчанию ищет файл с именем "Foo.class" в каталогах (и ZIP-файлах), указанных 
в переменной CLASSPATH. Найдя файл с таким именем, он читает весь файл в 
буферный массив..байтов и вызывает метод defmeClassQ, объявленный как private, 
передавая ему_этот _ массив байтов в качестве аргумента. Мехрд definejClassQ 
sarpj^a^eTJataccj^cpeflyjbmOTHeH™ и возвращает для вновь загруженного класса 
объект типа Class. 
    Такие загрузчики предусмотрены для всех несистемных классов. Метод 
getClassLoaderQ класса Class возвращает ссылку на подобный загрузчик классов. 
Продемонстрируем это на следующем примере. 

    public class GetThisClassLoader { 
    public static void main(String[] astrArgs) { 
    new GetThisClassLoader(); } 
    public  GetThisClassLoader()    { Class   els   =   this.getClass(); 
ClassLoader  cl  =  els.getClassLoader(); System.out.printlnt"The " + 
cls.getNameO   + " class ClassLoader:   " + cl); 
    } i 

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

    The GetThisClassLoader class ClassLoader: null 

    Почему появилось значение null? Это произошло вследствие того, что 
нельзя-получить ссылку Ha^3jrjpjf3j4jjKjgraccoB, который используется ВМ^по 
умолчанию. Он объявлен как jprotected, и его нельзя непосредственно 
использовать илигюлучать на него ссылку. Классы, загружаемые ВМ 
непосредственно из локальной файловой системы, по умолчанию загружаются 
загрузчиком классов, ссылку на который получить нельзя. 
    Но все же существует метод, позволяющий загрузить класс с помощью этого 
загрузчика. Метод getSystemClassQ объекта ClassLoader загружает классы с 
использованием стандартного загрузчика классов. 
    Итак, когда загрузчик классов по умолчанию_«е используется при загрузке 
класса? Он не используется, когда класс, пытающийся загрузить другие классы, 
сам загружен другим загрузчиком классов. Если класс загружался 
пользовательским загрузчиком, то этот же загрузчик будет применен при 
загру_зке_и дру^х^клаххов^ необходимых этому"клаюсуТВ противном случае 
используется стандартный загрузчик классов. 

    Созаанпе пользовательского загрузчика классов 

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

    Глава 11. Пакеты iava.util и java.lang	357 

    был загружен класс-файл данного апплета. Например, если класс-файл апплета 
загружен из URL "http://www.whatever.com/MyApplet.class", а этот апплет явно 
ссылается на класс с именем "MyHelperClass", то загрузчик классов броузера 
будет искать URL "http://www.whatever.com/MyHelper-Class.class". 
    Для создания пользовательского загрузчика классов надо породить от класса 
java.lang.ClassLoader новый класс и обеспечить реализацию метода loadClassQ. 
Этот метод должен получить класс-файл (в данном случае — по Internet) и 
вызвать метод defineClass() класса ClassLoader, объявленный как protected, 
передав ему данные, полученные из соответствующего URL. Это все. Ниже приведен 
пример реализации класса URLClassLoader: 

    import java.net.*; import java.io.*; 
    class URLClassLoader extends ClassLoader { private URL m_urlBase; public 
URLClassLoader(URL urlBase) { 
    m_urlBase = urlBase; } 
    public Class loadClass(String strClassName, boolean resolve) 
    throws ClassNotFoundException { Class els; • try { 
    URL urlClassFile = new URL(m_urlBase, StrClassName +".class"); InputStream 
is = urlClassFile.openStream(); 
    ByteArrayOutputStream out = new ByteArrayOutputStream(); int n; 
    while (-1 != (n = is.readO)) out.write(n); 
    els = defineClass(out.toByteArray() , 0, out.size()); } catch (lOException 
ioe) { 
    throw new ClassNotFoundException(strClassName); ) 
    if (resolve) 
    resolveClass(els); 
    return els; 
    } 
    1 

    Используя загрузчик URLClassLoader, можно создать программу, аналогичную 
приложению InstantiateClass, которая сможет загрузить класс из URL. Это 
приложение, называемое InstantiateURLClass, принимает, по крайней мере, два 
аргумента командной строки. Первый аргумент является базовым URL, который 
используется для создания загрузчика классов. Каждый из последующих аргументов 
представляет собой имя класса, который надо 

    358	Visual J++: основы программирования 

    загрузить и экземпляр которого надо создать. Ниже приведен код приложения 
InstantiateURLClass, которое использует приведенный выше загрузчик URLClass Loader: 

    import java.net.*; 
    public class InstantiateURLClass { 
    public static void main(String[] astrArgs) { 
    // Первый аргумент является базовым URL, используемым // при создании 
загрузчика URLClassLoader. URLClassLoader url_cl; try { 
    URL urlBase = new URL(astrArgs[0]}; 
    url_cl = new URLClassLoader(urlBase); } catch (MalformedURLException mue) { 
    System.err.println("Usage Error:\n InstantiateURLClass "} ; 
    System.err .println ("<url-base> <classname> [<classname> ...]") ; 
    System.exit(-1); } 
    // Попытка загрузить класс и создать его экземпляр // для каждого 
аргумента командной строки. for(int ii=l; iiOstrArgs. length; ii + + ) { try { 
    Class els = url_cl.loadClass(astrArgs[ii], false); Object о = 
els.newlnstance(); } catch (ClassNotFoundException cnfe) { 
    System.err.println("Class not found: " + astrArgs[ii]); } catch 
(InstantiationException ie) { 
    System.err.println("Instantiation error with 
    class " + astrArgs[ii]); 
    System.err.println("\texception is: " + ie) ; } catch 
(IllegalAccessException iae) { 
    System.err.println("No rights to constructor for 
    class " + astrArgs[ii]}; } catch (NoSuchMethodException nsme) { 
    System.err.println("Class doesn't have no-arg 
    constructor" + astrArgs[ii]); } } i ) 


    Класс Math 

    Все тригонометрические и математические функции, необходимые для 
проведения сложных вычислений, включены в класс, имеющий единственный 
экземпляр, — класс java.lang.Math. В классе Math имеются только статические 
методы, объявленные как public, поэтому доступ ко всем математическим функциям 
осуществляется по имени "Math" следующим образом: 

    int nMin = Math.min(10, 20); 


    Глава 11. Пакеты iava.util n java.lang	359 

    Углы, используемые в тригонометрических методах класса Math, измеряются в 
радианах. Для облегчения работы с радианами в класс Math введена статическая 
константа типа double, называемая PI, значение которой является достаточно 
точным приближением числа к. Поскольку большинство инженеров привыкли иметь 
дело с градусами, а не с радианами, ниже приведены функции преобразования 
градусов в радианы и обратно: 

    public double degreesToRads(double dDegrees) { 
    return (Math.PI * dDegrees / 180.0); } 
    public double radsToDegrees(double dRads) { return (180.0 * dRads / Math.PI); 
    } 

    В классе Math имеется метод random(), который при многократном вызове 
возвращает последовательность псевдослучайных чисел (типа double). Однако 
гораздо легче управлять объектами типа Random пакета java.util, чем методами 
randomQ класса Math, поскольку в процессе отладки можно задавать начальное 
значение для генератора псевдослучайных чисел, используемого объектами типа 
Random (при задании одинакового начального значения генератор будет всегда 
выдавать одну и ту же последовательность), чего нельзя сделать при 
использовании метода random() класса Math. Поэтому генерируемая этим методом 
последовательность не может быть воспроизведена повторно. 
    Все методы класса Math известны: это тригонометрические функции, 
экспоненциальная функция, функции округления, функции сравнения и прочие. 
Электронная документация класса Math доступна в Info Viewer Visual J++. Чтобы 
не дать вам заснуть во время чтения этого параграфа, мы не приводим общий 
список всех математических методов. Для получения подробной информации 
обратитесь к документации по Java. 

    Системные классы периода выполнения 

    Среда выполнения приложений и апплетов Java представлена группой из 
четырех классов пакета java.lang. Классы Runtime и System, чьи функциональные 
возможности несколько перекрываются, позволяют пользователю управлять 
параметрами среды выполнения и получать информацию о среде, в которой запущены 
выполняемые Java-коды. 
    В Java предусмотрена возможность охранять определенные локальные ресурсы 
от доступа неавторизованных (т.е. не имеющих на это прав) классов. Класс 
SecurityManager является классом с единственным экземпляром, посредством 
которого передаются все запросы на доступ к локальной файловой системе, 
осуществляются соединения через Internet, запускаются новые процессы, 
происходит обращение к библиотекам операционной системы 

    360 Visual J++: основы программирования 

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

    Классы Runtime и System 

    Классы Runtime и System пакета java.lang обеспечивают доступ к параметрам 
среды выполнения, в которой запускаются приложения и апплеты Java. Оба эти 
класса являются классами с одним экземпляром, а их функциональные возможности 
несколько перекрываются, поэтому их удобнее рассматривать как комплементарные 
классы, обеспечивающие различные способы решения одних и тех же задач. 
    Класс Runtime используется для обеспечения контроля над процессами и 
управления свободной памятью системы. Именно этот класс отвечает за выполнение 
внешних процессов и распределение памяти, используемой ВМ. 

    Запуск внешних процессов 

    Для запуска внешних процессов в Java-программе используется метод ехес() 
класса Runtime. Имеются четыре перегруженные версии этого метода со следующими объявлениями: 

    public Process exec (String	command); 
    public Process exec(String	command, String envp[]); 
    public Process exec(String	cmdarray[]); 
    public Process exec(String	cmdarrayt], String envp[]); 

    Первая версия метода просто принимает командную строку, которую следует 
выполнить. Формат командной строки зависит от системы, поэтому ее синтаксис 
будет различным для разных платформ. 
    Вторая версия позволяет определять переменные и параметры среды 
выполнения, используемые внешней программой. Командная строка выглядит так же, 
как и в первом методе. Массив строк envp[] является массивом определений 
переменных среды. Формат каждой СТРОКИ массива таков: "имя переменной = значение". 
    Третья перегруженная версия этого метода позволяет определять командную 
строку в виде строки лексем. Первая лексема является именем программы, которую 
следует выполнить, а последующие лексемы представляют собой аргументы 
командной строки, которые будут переданы выполняемой программе. Вот, например, 
строка кода, использующая первую версию метода ехес(): 

    Process proc = Runtime.exec("Java AnotherClass argl arg2"); 


    Глава 11. Пакеты java.util и Java.lang 361 

    Ее можно выполнить, используя третью перегруженную версию метода ехес(): 

    String[] astr_cmndline = {"Java", "AnotherClass", "argl", "arg2" }; 
Process proc = Runtime.exec(astr_cmdline); 

    Четвертая версия отличается от третьей тем, что ей можно передавать ряд 
переменных среды, используемых выполняемым процессом. Формат строк 
переменная/значение, которые составляют массив envp[], такой же, как формат 
параметра envp[] второй версии этого метода. 

    Класс Process 

    Различные версии метода ехес() класса Runtime возвращают объект Process. 
Объект Process предназначен для представления внешних процессов в среде 
выполнения Java. Используя объект типа Process, можно контролировать 
совместное выполнение Java-программы и внешнего процесса. 
    В классе Process имеются методы, предоставляющие доступ к потокам 
ввода-вывода данных и потокам ошибок внешнего процесса. Метод getln-putStreamO 
возвращает поток ввода. Данные, читаемые из этого потока, являются данными, 
записанными в поток вывода внешнего процесса. Метод getOutputStream() класса 
Process возвращает доток вывода. Данные, записываемые в этот поток, передаются 
внешнему процессу в его поток ввода. 
    Метод getErrorStream() возвращает поток ввода. Данные, читаемые из этого 
потока, являются данными, которые внешний процесс, записывает в поток ошибок. 
    Например, если выполняется внешняя Java-программа (запущенная в новом 
экземпляре ВМ), то данные, записанные в поток System.outPrintStream этой 
внешней программой, ваша программа может прочитать из потока ввода, 
возвращаемого методом getlnputStreamQ класса Process. Если внешний процесс 
пытается читать из потока System.in, то он прочитает данные, записанные вашей 
программой в поток вывода, возвращенный методом Process.getOutputStream(). 
    Метод exitValue() класса Process возвращает код завершения, которым 
оканчивается внешний процесс. Если внешний процесс является Java-процессом, то 
код завершения служит значением, передаваемым методу System. exitQ. Если 
внешний процесс в момент вызова метода еще не завершен, то произойдет выдача 
исключения IllegalThreadStateException. 
    Для того чтобы приостановить родительский Java-процесс на время работы 
внешнего процесса, запущенного методом Runtime.execQ, используется метод 
waitForQ класса Process. Этот метод приостанавливает вызывающий поток 
выполнения на период завершения внешнего процесса. Если на момент вызова 
данного метода внешний процесс уже завершен, метод waitForQ сразу же завершается. 
    Метод destroyQ класса Process обеспечивает немедленное завершение 
запущенного процесса. 
    С помощью методов классов Process и Runtime можно с легкостью создавать 
программные оболочки целиком на Java. 

    362 Visual J++: основы программирования 


    Память, сбор мусора и управление завершением 

    Методы freeMemoryQ и totalMemoryQ класса- Runtime сообщают информацию о 
состоянии памяти локальной системы. Метод freeMemory() показывает, какой объем 
памяти свободен, а метод totalMemory() определяет общий объем памяти локальной 
системы. С учетом того, что ВМ Java не позволяет запрашивать просто блоки 
памяти (а разрешает запрашивать только объекты), полезность этих методов 
невелика. Конечно, можно обнаружить ситуацию нехватки памяти и иным способом — 
получив при вызове метода Runtime.free.Memory() исключение Memory.Exception. 
    При подозрении о нехватке памяти или при желании освободить как можно 
больший ее объем во время выполнения программы следует использовать методы 
Runtime.gcQ и Runtime.runFinalizationQ. Метод gc() сразу же запускает сеанс 
работы сборщика мусора. Метод runFinalizationQ завершает процесс освобождения 
памяти, уничтожая все объекты, которые еще не удалены. 

    Класс System 

    Класс с единственным экземпляром System предоставляет доступ к переменным 
среды, в которой выполняются приложения и апплеты Java. С помощью объекта 
класса System можно получить доступ к переменным среды, потокам ввода-вывода и 
потокам ошибок, "а" также к текущему времени системы. Некоторые методы класса 
Runtime реализованы в классе System (например, метод exit() класса RuntimeQ 
повторно реализован в классе System).	"""——""*""" 

    Доступ к свойствам срепы 

    Посредством свойств среды сообщается различная информация о платформе, на 
которой выполняется апплет или приложение Java, а также о ВМ. Объект типа 
Properties является хеш-таблицей, состоящей из строковых ключей и значений. 
Электронная версия документации, доступная в Info Viewer, перечисляет 
доступные свойства и описывает их. 
    Например, системное свойство, называемое "user.dir", указывает на 
расположение текущего рабочего каталога на момент запуска ВМ. Свойство 
"user.home" хранит список начальных каталогов пользователя. Свойство 
"user.name" хранит имя, под которым зарегистрирован пользователь в текущем 
сеансе. Описание метода getPropertiesQ класса System приводится в 
соответствующем параграфе модуля Info Viewer. Там подробно описаны все 
свойства, к которым можно получить доступ с помощью объекта Properties. 

    Потоки ввопа-вывоаа и потоки ошибок 

    В приводимых в этой книге примерах приложений и апплетов часто 
используются потоки System.in, System.out и System.err. Эти потоки являются 
основными потоками ввода, вывода и ошибок для всех процессов Java. 

    Глава 11. Пакеты java.util n iava.lang 363 

    Для апплетов потоки System.in и System.out применять нельзя, т.е. 
пользователь не может вводить данные в командной строке. Он взаимодействует с 
апплетом исключительно посредством элементов управления пакета AWT. Поток 
System.err направляется к различным обработчикам ошибок, в зависимости от 
используемого броузера. В броузере Netscape Navigator вывод потока System.err 
направляется в окно Java Console, которое можно открыть, выбрав в меню Options 
команду Java Console. 
    Приложен^J^ya могут читать вводимые данные из потока System.in и 
записывать их в поток System.out. Вводимые данные поступают с клавиатуры или 
из входного файла. Выводиться данные могут на экран или в файл, если 
соответствующим образом указать_перенаправление вывода в командной строке. 

    Метоп аггаусору() 

    Метод System.arraycopy() копирует все элементы или их часть из одного 
массива в другой. Этот метод, с целью ускорения копирования массивов в Java, 
оптимизирован с помощью платформно-зависимого кода. Без этого метода пришлось 
бы копировать элементы массива по отдельности. 
    Например, для тогсГчтобы заново разместить массив и увеличить его размер, 
можно использовать следующий код: 

    public int[] doublelntArraySize(int[] an) { int[] anRet = new 
int[an.length * 2]; 
    System.arraycopy(an, 0, anRet, 0, an.length); 
    } 

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

    public void arraycopy(Object  src,   int  srcOffset,   Object  dst, 
    int  dstOffset,   int  length); 

    Метод arraycopyQ осуществляет выдачу исключения Array Store Exception JB 
том случае, когда типы исходного массива и результирующего массивов не 
совпадают. Если"новый массив недостаточно велик, чтобы вместить исходный 
массив, то происходит выдача исключения ArrayOutOfBoundsException. То же самое 
происходит и в том случае, если индекс элемента в массиве представляет собой 
отрицательное число или оно положительно, но слишком велико для размера массивов. 
    Наконец, метод currentTimeMillis() класса System возвращает целое типа 
long, соответствующее текущему времени системы. Возвращаемое значение является 
числом миллисекунд, прошедших начиная с полночи 1 января 1900 года. Это 
значение можно использовать, чтобы создать экземпляр объекта Date для указания 
текущей даты и текущего времени. 

    Менеджер Безопасности 

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

    364 Visual J++: основы программирования 

    опасных действиях сервера менеджеру безопасности направляется запрос о 
правомочности таких действий. 
    Например, когда апплет или приложение пытаются открыть файл в лекальной 
файловой системе, менеджеру безопасности делается запрос относительно 
допустимости такой операции. Если какая-либо программа попытается установить 
сетевое соединение с удаленным объектом, то менеджер безопасности получит 
запрос о возможности такого действия? 
    Менеджер безопасности особенно необходим в броузерах, выполняющих апплеты. 
Как известно, у объектов типа Applet имеется множество ограничений по 
отношению к потенциально уязвимым ресурсам системы. Апплеты не имеют права 
открывать файлы в локальной файловой системе, создавать сетевые соединения, 
загружать классы с помощью собственных загрузчиков классов или создавать 
экземпляры СОМ-объектов. Выполнение всех этих ограничений контролируется 
менеджером безопасности. 
    В интерфейсе SecurityManager представлен ряд public-методов типа 
checkXXXQ. Все они имеют одинаковый синтаксис: 

    public void checkXXX(<args>) ; 

    Если операция разрешена менеджером безопасности, то соответствующий метод 
checkXXXQ просто завершается. Если операция не разрешена, то объект 
8есип1уМапа§ег"осуществляет выдачу исключения Security Exception. 
    Так, если апплет пытается открыть файл для чтения, система вызывает метод 
checkReadQ класса SecurityManager. Для апплетов этот метод всегда выдает 
исключение SecurityException, которое запрещает им открывать файл для чтения. 
Аналогично, при пытке установить сетевое соединение вызывается метод 
checkConnectQ. Если это сетевое соединение устанавливается с тем же самым 
сервером, с которого получен апплет, то данный метод благополучно завершается. 
В противном случае происходит выдача исключения SecurityException, 
запрещающего апплету устанавливать сетевое соединение. 
    В электронной версии документации по Visual J++ перечислены все методы 
checkA%Y() класса SecurityManager. Следует помнить, что эти методы объявлены 
как возвращающие void. Вместо того чтобы вернуть значение false или true, 
SecurityManager при попытке выполнить запрещенные действия выдает исключение SecurityException. 

    Глава 11. Пакеты java.util и iava.lang 365 

