Глава 1 
ПЕРВОЕ ЗНАКОМСТВО С JAVA 
Посмотрите Европу! Десять стран за семнадцать дней! 
Реклама в туристическом агентстве 
В этой главе представлен краткий обзор языка программирования Java. После его
прочтения вы сможете написать свое первое Java-приложение. Здесь мы рассмотрим
только основные возможности языка, не задерживаясь на деталях. Конкретные свойства
Java подробно изучаются в последующих главах. 
1.1. С самого начала
Программы на языке Java строятся на основе классов. Руководствуясь определением
класса, разработчик создает произвольное количество объектов, или экземпляров, 
данного класса. Класс и его объекты можно сравнить, соответственно, с чертежом и
деталями — имея чертеж, не составляет труда произвести необходимое количество
деталей. 
converted to PDF by BoJIocКласс содержит в себе члены двух видов: поля и методы. Полями называются данные, 
принадлежащие либо самому классу, либо его объектам; значения полей определяют
состояние объекта или класса. Методами называются последовательности операторов, 
выполняющих какие-либо действия с полями для изменения состояния объекта. 
По сложившейся традиции первая программа на изучаемом языке программирования
должна выводить строку Hello, world. Текст такой программы на Java выглядит
следующим образом: 
class HelloWorld { 
public static void main(String[] args) { 
 System.out.println(“Hello, world”); 
} 
} 
Воспользуйтесь своим любимым редактором и введите исходный текст программы в файл. 
Затем запустите компилятор Java, чтобы преобразовать исходный текст в байт-код Java, 
“машинный язык” виртуальной абстрактной машины Java. Набор текста программы и ее
компиляция в разных системах могут производиться по-разному и потому здесь не
описываются — за информацией следует обратиться к соответствующей документации. 
Если запустить программу, на экране появится: 
Hello, world 
Наше маленькое приложение на языке Java что-то делает — но, собственно, как это
происходит? 
В приведенной выше программе объявляется класс с именем HelloWorld, который
содержит всего один метод main. Члены класса перечисляются внутри фигурных скобок { 
и }, следующих за именем класса. HelloWorld содержит один метод и не имеет полей. 
Единственным параметром метода main является массив объектов String, которые
представляют собой аргументы программы из командной строки, использованной для
запуска. Массивы и строки, а также значение args для метода main рассматриваются
ниже. 
Метод main объявлен с ключевым словом void, поскольку он не возвращает никакого
значения. В Java этот метод имеет особое значение; метод main класса, объявленный так, 
как показано выше, выполняется, если запустить класс как приложение. При запуске
метод main может создавать объекты, вычислять значения выражений, вызывать другие
методы и делать все то, что заложил в него программист. 
В приведенном выше примере main содержит всего один оператор, вызывающий метод
println объекта out класса System. Для вызова метода необходимо указать объект и
название метода, разделив их точкой (.). Метод println объекта out выводит в
стандартный выходной поток строку текста и символ перехода на новую строку. 
Упражнение 1.1 
Наберите, откомпилируйте и запустите программу HelloWorld на вашем компьютере. 
Упражнение 1.2 
Попробуйте изменить различные части программы HelloWorld и ознакомьтесь с
полученными сообщениями об ошибках. 
converted to PDF by BoJIoc1.2. Переменные
Следующий пример выводит числа Фибоначчи — бесконечную последовательность, 
первые члены которой таковы: 
1 
1 
2 
3 
5 
8 
13 
21 
34 
Ряд чисел Фибоначчи начинается с 1 и 1, а каждый последующий его элемент
представляет собой сумму двух предыдущих. Программа для печати чисел Фибоначчи
несложна, но она демонстрирует объявление переменных, работу простейшего цикла и
выполнение арифметических операций: 
class Fibonacci { 
/** Вывод чисел Фибоначчи < 50 */ 
public static void main(String[] args) { 
 int lo = 1; 
 int hi = 1; 
 System.out.println(lo); 
 while (hi < 50) { 
 System.out.println(hi); 
 hi = lo + hi; // Изменение значения hi 
 lo = hi — lo; /* Новое значение lo равно
 старому hi, то есть сумме
 за вычетом старого lo */ 
 } 
} 
} 
В этом примере объявляется класс Fibonacci, который, как и Hello World, содержит метод
main. В первых строках метода main объявляются и инициализируются две переменные, 
hi и lo. Перед именем переменной должен быть указан ее тип. Переменные hi и lo 
относятся к типу int — то есть являются 32-разрядными целыми числами со знаком, 
лежащими в диапазоне от –232 до 232–1. 
В языке Java имеется несколько встроенных, “примитивных” типов данных для работы с
целыми, вещественными, логическими и символьными значениями. Java может
непосредственно оперировать со значениями, относящимися к примитивным типам, — в
отличие от объектов, определяемых программистом. Типы, принимаемые “по умолчанию”, 
в Java отсутствуют; тип каждой переменной должен быть указан в программе. В Java 
имеются следующие примитивные типы данных: 
boolean одно из двух значений: true или false 
char 16-разрядный символ в кодировке Unicode 1.1 
byte 8-разрядное целое (со знаком) 
converted to PDF by BoJIocshort 16-разрядное целое (со знаком) 
int 32-разрядное целое (со знаком) 
long 64-разрядное целое (со знаком) 
float 32-разрядное с плавающей точкой (IEEE 754-1985) 
double 64-разрядное с плавающей точкой (IEEE 754-1985) 
В программе для вывода чисел Фибоначчи переменным hi и lo было присвоено значение
1. Начальные значения переменных можно задавать при их объявлении с помощью
оператора = (это называется инициализацией). Переменной, находящейся слева от
оператора =, присваивается значение выражения справа от него. В нашей программе
переменная hi содержит последнее число ряда, а lo — предыдущее число. 
До инициализации переменная имеет неопределенное значение. Если вы попробуете
воспользоваться переменной до того, как ей было присвоено значение, компилятор Java 
откажется компилировать программу до тех пор, пока ошибка не будет исправлена. 
Оператор while в предыдущем примере демонстрирует один из вариантов циклов в Java. 
Программа вычисляет выражение, находящееся в скобках после while, — если оно
истинно, то выполняется тело цикла, после чего выражение проверяется снова. Цикл
while выполняется до тех пор, пока выражение не станет ложным. Если оно всегда
остается истинным, программа будет работать бесконечно, пока какое-либо
обстоятельство не приведет к выходу из цикла — скажем, встретится оператор break или
возникнет исключение. 
Условие, проверяемое в цикле while, является логическим выражением, принимающим
значение true или false. Логическое выражение, приведенное в тексте программы, 
проверяет, не превысило ли текущее число ряда значение 50. Если большее число ряда
(hi) меньше 50, то оно выводится, а программа вычисляет следующее число Фибоначчи. 
Если же оно больше или равно 50, то управление передается в строку программы, 
находящуюся после тела цикла while. В нашем примере такой строкой оказывается конец
метода main, так что работа программы на этом завершается. 
Обратите внимание на то, что в приведенном выше примере методу println передается
целочисленный аргумент, тогда как в HelloWorld его аргументом была строка. Метод
println является одним из многих методов, которые перегружаются (overloaded), чтобы их
можно было вызывать с аргументами различных типов. 
Упражнение 1.3 
Выведите заголовок перед списком чисел Фибоначчи. 
Упражнение 1.4 
Напишите программу, которая генерирует другой числовой ряд, — например, таблицу
квадратов (умножение выполняется с помощью оператора * — например, i * i). 
1.3. Комментарии
Текст на русском языке в нашей программе представляет собой комментарий. В Java 
предусмотрены комментарии трех видов — все они встречаются в нашем примере. 
Текст, следующий за символами // вплоть до конца строки, игнорируется компилятором; 
то же самое относится и к тексту, заключенному между символами /* и */. 
converted to PDF by BoJIocКомментарии позволяют добавлять описания и пояснения для тех программистов, 
которым в будущем придется разбираться в вашей программе. Вполне возможно, что на
их месте окажетесь вы сами через несколько месяцев или даже лет. Комментируя свою
программу, вы экономите собственное время. Кроме того, при написании комментариев
нередко обнаруживаются ошибки в программе — когда приходится объяснять кому-то, что
происходит, то поневоле задумываешься над этим сам. 
Комментарий третьего типа встречается в самом начале программы, между символами /** 
и */. Комментарий, начинающийся с двух звездочек, является документирующим. 
Документирующие комментарии используются для описания назначения следующего за
ними фрагмента программы; в нашем примере характеризуется метод main. Специальная
программа, которая называется javadoc, извлекает документирующие комментарии и
генерирует по ним справочный файл в формате HTML. 
1.4. Именованные константы
Константами называются фиксированные значения — например, 12, 17.9 или “String like 
this”. С их помощью можно работать с величинами, которые не вычисляются заново, а
остаются постоянными во всем жизненном цикле программы. 
Программисты предпочитают иметь дело с именованными константами по двум причинам. 
Первая из них заключается в том, что имя константы представляет собой некоторую
форму документации. Оно может (и должно!) описывать, для чего используется то или
иное значение. 
Другая причина в том, что именованная константа определяется всего в одном месте
программы. Когда ее значение понадобится изменить, это достаточно будет сделать в
одном месте, что заметно упрощает модификацию программы. Чтобы создать
именованную константу в Java, следует указать в ее объявлении ключевые слова static и
final и задать начальное значение: 
class CircleStuff { 
static final double p = 3.1416; 
} 
Если вдруг окажется, что точности в четыре цифры после десятичной точки недостаточно, 
значение p легко изменить. Мы объявили p как переменную типа double — 64-разрядное
число с плавающей точкой с двойной точностью, так что p можно задать и поточнее — 
скажем, 3.14159265358979323846. 
Взаимосвязанные константы можно группировать в рамках класса. Например, в карточной
игре могут пригодиться следующие константы: 
class Suit { 
final static int CLUBS = 1; 
final static int DIAMONDS = 2; 
final static int HEARTS = 3; 
final static int SPADES = 4; 
}; 
При такой группировке на масти можно ссылаться как на Suit.HEARTS, Suit.SPADES и
т. д. — все названия мастей сосредоточены в пределах одного класса Suit. 
converted to PDF by BoJIoc1.4.1. Символы Unicode
Давайте ненадолго остановимся и обратим внимание на символ p, использованный в
качестве имени константы в предыдущем примере. В большинстве языков
программирования идентификаторы обычно ограничиваются буквами и цифрами, 
входящими в набор символов ASCII. 
Java вводит вас в современный мир интернационального программного обеспечения; в
Java используется кодировка Unicode — международный стандарт для набора символов. 
Символы Unicode состоят из 16 бит, благодаря чему обеспечивается поддержка букв, 
входящих в большинство языков мира. По-этому мы и смогли использовать p как имя
константы в приведенном выше примере. Символ p входит в греческую секцию Unicode и, 
следовательно, может присутствовать в исходном тексте программы. В настоящее время
при программировании, как правило, используется набор символов ASCII, 7-битная
кодировка или ISO-Latin-1 — 8-битный стандарт, который обычно называется Latin-1. 
Однако перед обработкой эти символы переводятся в Unicode, так что в Java всегда
используется кодировка Unicode. 
Упражнение 1.5 
Измените приложение HelloWorld так, чтобы выводимая строка была представлена
именованной константой. 
Упражнение 1.6 
Измените приложение Fibonacci так, чтобы вместо константы-литерала (50) в условии
цикла фигурировала именованная константа. 
1.5. Порядок выполнения
“Порядком выполнения” называется очередность, в которой выполняются операторы
программы. Один из примеров воздействия на порядок выполнения — цикл while в
программе для вывода чисел Фибоначчи. К числу других операторов, изменяющих
порядок выполнения, относятся конструкции if/else, for, switch, do/while и блоки — 
несколько операторов, сгруппированных внутри фигурных скобок { и }. Давайте
усовершенствуем программу для вывода чисел Фибоначчи — перенумеруем все члены
последовательности и пометим нечетные числа звездочкой: 
/** Вывести несколько первых чисел Фибоначчи, 
помечая нечетные числа символом ‘*’ */ 
static final int MAX_INDEX = 10; 
public static void main (String[] args) { 
 int lo = 1; 
 int hi = 1; 
 String mark; 
 System.out.println(“1: ” + lo); 
 for (int i = 2; i < MAX_INDEX; i++) { 
 if (hi % 2) == 0) 
 mark = “ *”; 
 else 
mark = “”; 
 System.out.println(i + “: ” + hi + mark); 
 hi = lo + hi; // Изменение значения hi 
 lo = hi — lo; /* Новое значение lo равно
 старому hi, то есть сумме
 за вычетом старого lo */ 
converted to PDF by BoJIoc } 
} 
} 
Вот как выглядит результат работы программы: 
1: 1 
2: 1 
3: 2 * 
4: 3 
5: 5 
6: 8 * 
7: 13 
8: 21 
9: 34 * 
Для упрощения нумерации ряда вместо while используется цикл for. Цикл for является
частным случаем while с добавлением инициализации и приращения переменной цикла. 
Приведенный выше цикл for эквивалентен следующему циклу while: 
{ 
int i = 2; 
while (i < MAX_INDEX) { 
 // .. вывод
 i++; 
} 
} 
Оператор ++ в этом фрагменте может показаться непонятным тому, кто не знаком с
языками программирования, восходящими к C. Этот оператор увеличивает на единицу
значение переменной, к которой он применяется, — в данном случае, i. Оператор ++ 
является префиксным, если он стоит перед операндом, и постфиксным, если он стоит
после него. Аналогично, оператор — уменьшает на единицу значение переменной, к
которой он применяется, и также может быть префиксным или постфиксным. Операторы
++ и — ведут свое происхождение из языка программирования C. В приведенном выше
примере оператор
i++; 
может быть заменен выражением
i = i + 1; 
Помимо простого присваивания, в Java имеются и другие операторы присваивания, 
которые применяют арифметические действия к значению в их левой части. Например, 
еще одна возможность представить i++ в цикле for может быть такой: 
i += 1; 
Значение в правой части оператора += (то есть 1) прибавляется к значению переменной
в левой части (то есть i), и результат записывается в ту же переменную. Большинство
бинарных операторов в Java (другими словами, операторов с двумя операндами) может
аналогичным образом объединяться с оператором =. 
converted to PDF by BoJIocВнутри цикла for используется конструкция if/else, проверяющая текущее значение hi на
четность. Оператор if анализирует значение выражения в скобках. Если оно равно true, то
выполняется первый оператор или блок внутри оператора if. Если же значение равно
false, то выполняется оператор или блок, следующий за ключевым словом else. Наличие
else не требуется; если else отсутствует и условие равно false, то блок if пропускается. 
После выполнения одной из двух возможных ветвей конструкции if/else, управление
передается оператору, следующему за оператором if. 
В нашем примере проверка hi на четность осуществляется с помощью оператора %. Он
вычисляет остаток от деления левого операнда на правый. Если значение слева четно, то
остаток будет равен 0, и следующий оператор присвоит переменной marker звездочку — 
индикатор для пометки четного числа. Для нечетных чисел выполняется условие else, 
присваивающее marker пустую строку. 
Метод println выполняется несколько сложнее — оператор + используется для
конкатенации следующих строк: i, разделитель, строка для значения hi и строка-
индикатор. В случае применения оператора + к строкам он выполняет их конкатенацию, 
тогда как в арифметических выражениях он занимается сложением. 
Упражнение 1.7 
Модифицируйте цикл так, чтобы значение переменной i изменялось не в прямом, а в
обратном направлении. 
1.6. Классы и объекты
Java, как и любой другой объектно-ориентированный язык программирования, 
располагает средствами построения классов и объектов. Каждый объект в Java имеет тип; 
им является тот класс, к которому принадлежит данный объект. В каждом классе есть
члены двух видов: поля и методы. 
· Полями называются переменные, содержащие данные класса и его объектов. В
них хранятся результаты вычислений, выполняемых методами данного класса. 
· Методы содержат исполняемый код класса. Методы состоят из операторов; эти
операторы, а также способ вызова методов в конечном счете определяют процесс
выполнения программы. 
Так может выглядеть объявление простого класса, представляющего точку на плоскости: 
class Point { 
public double x, y; 
} 
Класс Point содержит два поля с координатами x и y точки, и в нем нет ни одного метода
(конечно же, в текущей реализации). Подобное объявление класса определяет, как будут
выглядеть объекты, созданные на его основе, а также задает поведение объектов с
помощью ряда инструкций. Чертеж приобретает наибольшую ценность после того, как к
нему добавляются технические задания и инструкции. 
Члены класса могут обладать различными правами доступа. Объявление полей x и y 
класса Point с ключевым словом public означает, что любой метод программы, получивший
доступ к объекту Point, сможет прочитать или изменить эти поля. Разрешается ограничить
доступ к данным и предоставлять его лишь методам самого класса или связанных с ним
классов. 
converted to PDF by BoJIoc1.6.1. Создание объектов
Объекты создаются посредством выражений, в которых используется ключевое слово
new. Созданные на основе определения класса объекты часто называют экземплярами
данного класса. 
В языке Java создаваемые объекты размещаются в области системной памяти, которая
называется кучей (heap). Доступ к любому объекту осуществляется с помощью ссылки на
объект — вместо самого объекта в переменных содержится лишь ссылка на него. Когда
ссылка не относится ни к какому объекту, она равна null. 
Обычно между самим объектом и ссылкой на него не делается особых различий — можно
сказать “передать методу объект”, на самом деле имея в виду “передать методу ссылку на
объект”. В книге мы будем различать эти два понятия лишь там, где необходимо, но чаще
всего термины “объект” и “ссылка на объект” будут употребляться как эквивалентные. 
Возвращаясь к определенному выше классу Point, давайте предположим, что мы
разрабатываем графическое приложение, в котором приходится следить за множеством
точек. Каждая точка представляется отдельным объектом Point. Вот как может выглядеть
создание и инициализация объектов Point: 
Point lowerLeft = new Point(); 
Point upperRight = new Point(); 
Point middlePoint = new Point(); 
lowerLeft.x = 0.0; 
lowerLeft.y = 0.0; 
upperRight.x = 1280.0; 
upperRight.y = 1024.0; 
middlePoint.x = 640.0; 
middlePoint.y = 512.0; 
Каждый объект класса Point обладает собственной копией полей x и y. Например, 
изменение поля x объекта lowerLeft никак не влияет на значение x объекта upperRight. 
Поля объектов иногда называют переменными экземпляра (instance variables), поскольку
в каждом объекте (экземпляре) класса содержится отдельная копия этих полей. 
1.6.2. Статические поля
Чаще всего бывает нужно, чтобы значение поля одного объекта отличалось от значений
одноименных полей во всех остальных объектах того же класса. 
Тем не менее иногда возникает необходимость совместного использования поля всеми
объектами класса. Такие совместные поля также называются переменными класса — то
есть переменными, относящимися ко всему классу, в отличие от переменных, 
относящихся к его отдельным объектам. 
Для чего нужны переменные класса? Давайте представим себе фабрику, производящую
плееры Sony (Sony Walkman). Каждому плееру присваивается уникальный серийный
номер. В наших терминах это означает, что в каждом объекте имеется уникальное поле, в
котором хранится значение номера. Однако фабрика должна знать значение номера, 
который должен быть присвоен следующему плееру. Дублировать эту информацию в
каждом объекте-плеере было бы неразумно — нужна всего одна копия номера, которая
хранится на самой фабрике, другими словами — в переменной класса. 
converted to PDF by BoJIocЧтобы использовать поле для хранения информации, относящейся ко всему классу, 
следует объявить его с ключевым словом static, поэтому такие поля иногда называют
статическими. Например, объект Point, представляющий начало координат, может
встречаться достаточно часто, поэтому имеет смысл выделить ему отдельное статическое
поле в классе Point:
public static Point origin = new Point(); 
Если это объявление встретится внутри объявления класса Point, то появится ровно один
экземпляр данных с именем Point.origin, который всегда будет ссылаться на объект (0,0). 
Поле static будет присутствовать всегда, независимо от того, сколько существует объектов
Point (даже если не было создано ни одного объекта). Значения x и y равны нулю, потому
что числовые поля, которым не было присвоено начального значения, по умолчанию
инициализируются нулями. 
Мы уже встречались со статическим объектом в нашей первой программе. Класс System — 
стандартный класс Java, в котором имеется статическое поле с именем out, 
предназначенное для направления вывода программы в стандартный выходной поток. 
Когда в этой книге встречается термин “поле”, обычно имеется в виду поле, уникальное
для каждого объекта, хотя в отдельных случаях для большей ясности может
использоваться термин “нестатическое поле”. 
1.6.3. Сборщик мусора
Предположим, вы создали объект с помощью вызова new; но как избавиться от этого
объекта, когда он окажется ненужным? Ответ простой — никак. Неиспользуемые объекты
Java автоматически уничтожаются сборщиком мусора. Сборщик мусора работает в
фоновом режиме и следит за ссылками на объекты. Когда ссылок на объект больше не
остается, появляется возможность убрать его из кучи, где он временно хранился, хотя
само удаление может быть отложено до более подходящего момента. 
1.7. Методы и параметры
Объекты определенного выше класса Point могут быть изменены в любом фрагменте
программы, в котором имеется ссылка на объект Point, поскольку поля этого класса
объявлены с ключевым словом public. Класс Point представляет собой простейший пример
класса. На самом деле иногда можно обойтись и простыми классами — например, при
выполнении чисто внутренних задач пакета или когда для ваших целей хватает
простейших типов данных. 
Тем не менее настоящие преимущества объектно-ориентированного программирования
проявляются в возможности спрятать реализацию класса. В языке Java операции над
классом осуществляются с помощью методов класса — инструкций, которые выполняются
над данными объекта, чтобы получить нужный результат. В методах часто используются
такие детали реализации класса, которые должны быть скрыты от всех остальных
объектов. Данные скрываются в методах и становятся недоступными для всех прочих
объектов — в этом заключается основной смысл инкапсуляции данных. 
Каждый метод имеет ноль или более параметров. Метод может возвращать значение или
объявляться с ключевым словом void, которое означает, что метод ничего не возвращает. 
Операторы метода содержатся в блоке между фигурными скобками { и }, которые
следуют за именем метода и объявлением его сигнатуры. Сигнатурой называется имя
метода, сопровождаемое числом и типом его параметров. Можно усовершенствовать класс
Point и добавить в него простой метод clear, который выглядит так: 
public void clear() { 
x = 0; 
converted to PDF by BoJIocy = 0; 
} 
Метод clear не имеет параметров, поскольку в скобках ( и ) после его имени ничего нет; 
кроме того, этот метод объявляется с ключевым словом void, поскольку он не возвращает
никакого значения. Внутри метода разрешается прямое именование полей и методов
класса — можно просто написать x и y, без ссылки на конкретный объект. 
1.7.1. Вызов метода
Объекты обычно не работают непосредственно с данными других объектов, хотя, как мы
видели на примере класса Point, класс может сделать свои поля общедоступными. И все
же в хорошо спроектированном классе данные обычно скрываются, чтобы они могли
изменяться только методами этого класса. Чтобы вызвать метод, необходимо указать имя
объекта и имя метода и разделить их точкой (.). Параметры передаются методу в виде
заключенного в скобки списка значений, разделяемых запятыми. Даже если метод
вызывается без параметров, все равно необходимо указать пустые скобки. Объект, для
которого вызывается метод (объект, получающий запрос на вызов метода) носит название
объекта-получателя, или просто получателя. 
В качестве результата работы метода может возвращаться только одно значение. Чтобы
метод возвращал несколько значений, следует создать специальный объект, 
единственное назначение которого — хранение возвращаемых значений, и вернуть этот
объект. 
Ниже приводится метод с именем distance, который входит в класс Point, использованный
в предыдущих примерах. Метод distance принимает в качестве параметра еще один
объект Point, вычисляет евклидово расстояние между двумя точками и возвращает
результат в виде вещественного значения с двойной точностью: 
public double distance(Point that) { 
double xdiff, ydiff; 
xdiff = x — that.x; 
ydiff = y — that.y; 
return Math.sqrt(xdiff * xdiff + ydiff * ydiff); 
} 
Для объектов lowerLeft и upperRight, которые были определены в разделе, посвященном
созданию экземпляров объектов, вызов метода distance может выглядеть так: 
double d = lowerLeft.distance(upperRight); 
После выполнения этого оператора переменная d будет содержать евклидово расстояние
между точками lowerLeft и upperRight. 
1.7.2. Ссылка this 
Иногда объекту-получателю бывает необходимо знать ссылку на самого себя. Например, 
объект-получатель может захотеть внести себя в какой-нибудь список объектов. В каждом
методе может использоваться this — ссылка на текущий объект (объект-получатель). 
Следующее определение clear эквивалентно приведенному выше: 
public void clear() { 
this.x = 0; 
this.y = 0; 
converted to PDF by BoJIocуниверсальным языком, и именно он интерпретируется виртуальной машиной в каждой
системе, поддерживающей Java. /Разумеется, виртуальная машина Java может быть
реализована и на аппаратном уровне - то есть с использованием специализированных
микросхем. Это никак не влияет на переносимость байт-кода и является всего лишь
одним из видов реализации виртуальной машины. - Примеч. перев/ 
Виртуальная машина присваивает каждому приложению собственный контекст времени
выполнения (runtime), который одновременно изолирует приложения друг от друга и
обеспечивает безопасность работы. Менеджер безопасности каждого контекста времени
выполнения определяет, какие возможности доступны данному приложению. Например, 
менеджер безопасности может запретить приложению чтение или запись на локальный
диск или ограничить сетевые соединения строго определенными компьютерами. 
В совокупности все эти средства делают язык Java полностью платформонезависимым и
предоставляют схему безопасности для выполнения переданного по сети кода на
различных уровнях доверия (trust levels). Исходный текст Java, скомпилированный в
байт-код Java, может выполняться на любом компьютере, где имеется виртуальная
машина Java. Код может выполняться на соответствующем уровне защиты, чтобы
предотвратить случайное или злонамеренное повреждение системы. Уровень доверия
регулируется в зависимости от источника байт-кода — байт-код на локальном диске или в
защищенной сети пользуется большим доверием, чем байт-код, полученный с удаленного
компьютера, неизвестно даже где расположенного. 
1.15. Прочее
Java содержит ряд других возможностей, которые кратко упоминаются здесь и
рассматриваются в следующих главах: 
· Потоки: Java обладает встроенной поддержкой потоков для создания
многопоточных приложений. Для синхронизации параллельного доступа к
объектам и данным класса используется блокировка на уровне объектов и на
уровне классов. Подробности приведены в главе 9. 
· Ввод/вывод: Java содержит пакет java.io, предназначенный для выполнения
разнообразных операций ввода/вывода. Конкретные возможности ввода/вывода
описаны в главе 11. 
· Классы общего назначения: в состав Java входят классы, представляющие многие
примитивные типы данных (такие, как Integer, Double и Boolean), а также класс
Class для работы с различными типами классов. Программирование с
использованием типов рассматривается в главе 13. 
· Вспомогательные классы и интерфейсы: Java содержит пакет java.util со
множеством полезных классов — таких, как BitSet, Vector, Stack и Date. Более
подробно о вспомогательных классах рассказывается в главе 12. 
converted to PDF by BoJIocГлава 2 
КЛАССЫ И ОБЪЕКТЫ
Начнем с самого начала, хотя порядок может быть и другим. 
Доктор Who, Meglos 
Фундаментальной единицей программирования в Java является класс. В состав классов
входят методы — фрагменты выполняемого кода, в которых происходят все вычисления. 
Классы также определяют структуру объектов и обеспечивают механизмы для их
создания на основе определения класса. Вы можете ограничиваться в своих программах
одними лишь примитивными типами (целыми, вещественными и т. д.), но практически
любая нетривиальная программа на Java создает объекты и работает с ними. 
В объектно-ориентированном программировании между тем, что делается, и тем, как это
делается, существуют четкие различия. “Что” описывается в виде набора методов (а
иногда и общедоступных данных) и связанной с ними семантики. Такое
сочетание (методы, данные и семантика) часто называется контрактом между
разработчиком класса и программистом, использующим этот класс, так как оно
определяет, что происходит при вызове конкретных методов объекта. 
Обычно считается, что объявленные в классе методы составляют все содержание его
контракта. Кроме того, в понятие контракта входит и семантика этих операций, даже
несмотря на то, что она может быть описана лишь в документации. Два метода могут
иметь одинаковые имена и сигнатуры, но они не будут эквивалентными, если обладают
различной семантикой. Например, нельзя предположить, что каждый метод с именем print 
предназначен для вывода копии объекта. Кто-нибудь может создать метод print и вложить
в его название иную семантику — скажем, термин может быть сокращением от выражений
“process interval” или “prioritize nonterminals”. Контракт (то есть совокупность сигнатуры и
семантики) определяет сущность метода. 
На вопрос “как” отвечает класс, на основе которого создавался данный объект. Класс
определяет реализацию методов, поддерживаемых объектом. Каждый объект
представляет собой экземпляр класса. При вызове метода объекта выполняемый код
ищется в классе. Объект может использовать другие объекты, однако мы начнем изучение
с простых классов, в которых все методы реализуются непосредственно. 
2.1. Простой класс
Основными компонентами класса являются поля (данные) и методы (код для работы с
ними). Приведем простой класс Body, предназначенный для хранения сведений о
небесных телах: 
class Body { 
public long idNum; 
public String nameFor; 
public Body orbits; 
public static long nextID = 0; 
} 
Прежде всего объявляется имя класса. Объявление класса в языке Java создает имя типа, 
так что в дальнейшем ссылки на объекты этого типа могут объявляться следующим
образом: 
Body mercury; 
converted to PDF by BoJIocПодобное объявление указывает на то, что mercury является ссылкой на объект класса
Body. Оно не создает сам объект, а лишь объявляет ссылку на него. Первоначально
ссылка имеет значение null, и объект, на который ссылается mercury, не существует до
тех пор, пока вы не создадите его явным образом; в этом отношении Java отличается от
других языков программирования, в которых объекты создаются при объявлении
переменных.
Первая версия класса Body спроектирована неудачно. Это было сделано намеренно: по
мере усовершенствования класса в данной главе мы сможем сосредоточить свое внимание
на некоторых возможностях языка. 
Упражнение 2.1 
Напишите простой класс Vehicle (транспортное средство) для хранения информации об
автомашине, в котором предусмотрены (как минимум) поля для задания текущей
скорости, текущего направления в градусах и имени владельца. 
Упражнение 2.2 
Напишите класс LinkedList (связный список), в котором имеется поле типа Object и ссылка
на следующий по списку элемент LinkedList. 
2.2. Поля
Переменные класса называются полями; примерами могут служить поля nameFor и orbits, 
входящие в класс Body. Каждый объект Body обладает отдельным экземпляром своих
полей: значение типа long отличает данное небесное тело от остальных, переменная типа
String содержит его имя, а ссылка на другой объект Body определяет небесное тело, 
вокруг которого оно обращается. 
Наличие у каждого объекта отдельного экземпляра полей означает, что его состояние
уникально. Изменение поля orbits в одном объекте класса Body никак не отражается на
значениях соответствующего поля в других объектах этого класса. 
Тем не менее иногда бывает необходимо, чтобы один экземпляр поля совместно
использовался всеми объектами класса. Такие поля объявляются с ключевым словом
static и называются статическими полями, или переменными класса. При объявлении в
классе поля static все объекты этого класса разделяют одну и ту же копию статического
поля. 
В нашем случае класс Body содержит одно статическое поле — nextID, в котором
хранится следующий используемый идентификатор небесного тела. При инициализации
класса, которая происходит после его загрузки и компоновки, в поле nextID заносится
нулевое значение. Ниже мы убедимся, что текущее значение nextID присваивается
идентификатору каждого вновь создаваемого объекта класса Body. 
Когда в этой книге используются термины “поле” или “метод”, обычно имеются в виду
нестатические поля и методы. В тех случаях, когда контекст не дает однозначного
толкования, мы будем пользоваться термином “нестатическое поле” или “нестатический
метод”. 
Упражнение 2.3 
Включите в класс Vehicle (транспортное средство) статическое поле для хранения
идентификатора машины, а в класс Car (автомобиль) — нестатическое поле, содержащее
номер машины. 
converted to PDF by BoJIoc2.3. Управление доступом и
наследование
Код класса всегда может обращаться ко всем полям и методам данного класса. Для
управления доступом к ним из других классов, а также для управления наследованием их
в подклассах члены классов могут объявляться с одним из четырех атрибутов доступа: 
· Открытый (Public): к членам класса всегда можно обращаться из любого места, в
котором доступен сам класс; такие члены наследуются в подклассах. 
· Закрытый (Private): доступ к членам класса осуществляется только из самого
класса. 
· Защищенный (Protected): к данным членам разрешается доступ из подклассов и из
функций, входящих в тот же пакет. Такие члены наследуются подклассами. 
Расширение объектов (наследование) подробно рассмотрено в главе 3. 
· Пакетный: доступ к членам, объявленным без указания атрибута доступа, 
осуществляется только из того же пакета. Такие члены наследуются подклассами
пакета. Пакеты рассматриваются в главе 10. 
Поля класса Body были объявлены с атрибутом public, потому что для выполнения
поставленной задачи программистам необходим доступ к этим полям. На примере более
поздних версий класса Body мы убедимся, что подобное решение обычно является
неудачным. 
2.4. Создание объектов
Для первой версии класса Body создание и инициализация объектов, представляющих
небесные тела, происходит следующим образом: 
Body sun = new Body(); 
sun.idNum = Body.nextID++; 
sun.nameFor = “Sol”; 
sun.orbits = null; // Солнце является центром Солнечной
 // системы
Body earth = new Body(); 
earth.idNum = Body.nextID++; 
earth.nameFor = “Earth”; 
earth.orbits = sun; 
Сначала мы объявили две ссылки (sun и earth) на объекты типа Body. Как упоминалось
выше, объекты при этом не создаются — лишь объявляются переменные, которые
ссылаются на объекты. Первоначальное значение ссылок равно null, а соответствующие
им объекты должны явным образом создаваться в программе. 
Объект sun создается посредством оператора new. Конструкция new считается самым
распространенным способом построения объектов (позднее мы рассмотрим и другие
возможности). При создании объекта оператором new следует указать тип
конструируемого объекта и необходимые параметры. Runtime-система выделяет область
памяти, достаточную для хранения полей объекта, и инициализирует ее в соответствии с
рассматриваемыми ниже правилами. После завершения инициализации runtime-система
возвращает ссылку на созданный объект. 
converted to PDF by BoJIocЕсли системе не удается выделить память, достаточную для создания объекта, она может
запустить сборщик мусора, который освобождает неиспользуемую память. Если же и
после этого памяти все равно не хватает, оператор new возбуждает исключение
OutOfMemoryError. 
Создав новый объект Body, мы инициализируем его переменные. Каждый объект класса
Body должен иметь уникальный идентификатор, который он получает из статического
поля nextID. Программа наращивает значение nextID, чтобы идентификатор следующего
объекта Body также был уникальным. 
В нашем примере строится модель Солнечной системы. В ее центре находится Солнце, 
поэтому полю orbits объекта sun присваивается значение null — у Солнца нет объекта, 
вокруг которого оно бы вращалось. При создании и инициализации объекта earth (Земля) 
мы присвоили полю orbits значение sun. Для Луны, вращающейся вокруг Земли, поле
orbits получило бы значение earth. Если бы мы строили модель Галактики, то Солнце бы
также вращалось вокруг “черной дыры”, находящейся где-то в середине Млечного Пути. 
Упражнение 2.4 
Напишите для класса Vehicle метод main, который создает несколько объектов-автомашин
и выводит значения их полей. 
Упражнение 2.5 
Напишите для класса LinkedList метод main, который создает несколько объектов типа
Vehicle и заносит их в список. 
2.5. Конструкторы
Каждый вновь созданный объект обладает некоторым исходным состоянием. Значения
полей могут инициализироваться при их объявлении — иногда этого бывает достаточно. 
/Инициализация данных подробно рассматривается в разделе "Инициализация", однако в
сущности за этим термином скрывается обычное присвоение начального значения. Если в
программе полю не присваивается никакого значения, оно получит значение ноль, 
\u0000, false или null, в зависимости от типа./ Однако довольно часто для определения
исходного состояния простой инициализации данных оказывается недостаточно; 
например, могут понадобиться какие-либо исходные данные, или же выполняемые
операции не могут быть представлены в виде простого присваивания. 
Для тех случаев, когда простой инициализации недостаточно, используются
конструкторы. Имя конструктора совпадает с именем класса, который он инициализирует. 
Конструкторы, подобно методам, могут получать один или несколько параметров, однако
они не являются методами и не могут возвращать никакого значения. Параметры
конструктора (если они есть) указываются в скобках за именем типа при создании
объекта оператором new. Конструктор вызывается после того, как переменным в
экземпляре вновь создаваемого объекта будут присвоены начальные значения по
умолчанию и будет выполнена непосредственная инициализация. 
В усовершенствованной версии класса Body исходное состояние объекта частично
устанавливается посредством инициализации, а частично — в конструкторе: 
class Body { 
public long idNum; 
public String name = “”; 
public Body orbits = null; 
private static long nextID = 0; 
converted to PDF by BoJIocBody() { 
 idNum = nextID++; 
} 
} 
Конструктор класса Body вызывается без аргументов, однако он выполняет важную
функцию, а именно устанавливает во вновь создаваемом объекте правильное значение
поля idNum. Простейшая ошибка, возможная при работе со старой версией класса, — 
скажем, вы забыли присвоить значение полю idNum или не наращивали nextID после его
использования — приводила к тому, что в программе возникали разные объекты класса
Body с одинаковыми значениями поля idNum. В результате возникали проблемы в той
части кода, которая была основана на положении контракта, гласящем: “Все значения
idNum должны быть разными”. 
Возлагая ответственность за генерацию значений idNum на сам класс Body, мы тем самым
предотвращаем ошибки подобного рода. Конструктор Body становится единственным
местом в программе, где idNum присваивается значение. Следующим шагом является
объявление поля nextID с ключевым словом private, чтобы доступ к нему осуществлялся
только из класса. Тем самым мы устраняем еще один возможный источник ошибок для
программистов, работающих с классом Body. 
Кроме того, такое решение позволит нам свободно изменять способ присвоения значений
полю idNums в объектах Body. Например, будущая реализация нашего класса может
просматривать базу данных известных астрономических объектов и присваивать новое
значение idNum лишь в том случае, если ранее данному небесному телу не был присвоен
другой идентификатор. Такое изменение никак не скажется на существующем коде
программы, поскольку он никак не участвует в присвоении значения idNum. 
При инициализации полям name и orbits присваиваются некоторые разумные начальные
значения. Следовательно, после приведенного ниже вызова конструктора все поля нового
объекта Body будут инициализированы. После этого вы можете изменить состояние
объекта, присвоив его полям нужные значения: 
Body sun = new Body(); // значение idNum равно 0 
sun.name = “Sol”; 
Body earth = new Body(); // значение idNum равно 1 
earth.name = “Earth”; 
earth.orbits = sun; 
Конструктор Body вызывается при создании нового объекта оператором new, но после
того, как полям name и orbits будут присвоены начальные значения. Инициализация поля
orbits значением null означает, что sun.orbits в нашей программе не присваивается
никакого значения. 
Если создание объекта для небесного тела с двумя параметрами — названием и центром
обращения — будет происходить довольно часто, то можно предусмотреть отдельный
конструктор, в который оба этих значения передаются в качестве параметров: 
Body(String bodyName, Body orbitsAround) { 
this(); 
name = bodyName; 
orbits = orbitsAround; 
} 
converted to PDF by BoJIocКак видите, из одного конструктора можно вызывать другой конструктор этого же
класса — для этого первым выполняемым оператором должен быть вызов this(). Это
называется “явным вызовом конструктора”. Если для вызова конструктора необходимы
параметры, они могут передаваться. В нашем случае для присвоения значения полю
idNum используется конструктор, вызываемый без аргументов. Теперь создание объектов
происходит значительно проще: 
Body sun = new Body(“Sol”, null); 
Body earth = new Body(“Earth”, sun); 
При желании можно задать отдельный конструктор с одним аргументом для тех случаев, 
когда для создаваемого объекта Body не существует центра вращения. Вызов такого
конструктора равносилен применению конструктора с двумя аргументами, при котором
второй из них равен null. 
Для некоторых классов необходимо, чтобы создатель объекта предоставлял некоторые
данные. Например, приложение может требовать, чтобы для всех объектов Body было
указано их название. Чтобы убедиться, что всем создаваемым объектам Body передается
название, необходимо включить соответствующий параметр во все конструкторы класса
Body. 
Приведем несколько общепринятых соображений в пользу создания специализированных
конструкторов: 
· Некоторые классы не обладают разумным начальным состоянием, если не
передать им параметры. 
· При конструировании объектов некоторых видов передача исходного состояния
оказывается самым удобным и разумным выходом (примером может служить
конструктор Body с двумя аргументами). 
· Конструирование объектов потенциально сопряжено с большими накладными
расходами, так что желательно при создании объекта сразу устанавливать
правильное исходное состояние. Например, если каждый объект класса содержит
таблицу, то конструктор, получающий исходный размер таблицы в качестве
параметра, позволит с самого начала создать объект с таблицей нужного размера. 
· Конструктор, атрибут доступа которого отличается от public, ограничивает
возможности создания объектов данного класса. Например, вы можете запретить
программистам, работающим с вашим пакетом, расширять тот или иной класс, 
если сделаете все конструкторы доступными лишь из пакета. Кроме того, можно
пометить ключевым словом protected те конструкторы, которые предназначены
для использования исключительно в подклассах. 
Конструкторы, не получающие при вызове никаких аргументов, встречаются настолько
часто, что для них даже появился специальный термин: “безаргументные” (no-arg) 
конструкторы. 
Если вы не предоставите для класса никаких конструкторов, язык создает
безаргументный конструктор по умолчанию, который не делает ничего. Этот конструктор
создается автоматически лишь в тех случаях, когда нет никаких других конструкторов, — 
существуют классы, для которых безаргументный конструктор будет работать неверно
(например, класс Attr, с которым мы познакомимся в следующей главе). 
Если безаргументный конструктор должен существовать наряду с одним или несколькими
конструкторами, использующими аргументы, можно явно определить его. Автоматически
создаваемый безаргументный конструктор класса, не имеющего суперкласса, 
эквивалентен следующему (как мы увидим на примере расширенного класса в главе 3): 
converted to PDF by BoJIoc{ 
 int i; 
 for (i = 0; i << Objs.length; i++) 
 if (Objs[i] == null) 
 break; 
 if (i >>= Objs.length) 
 throw new NoEmptySlotException(); 
 Objs[i] = obj; // занести в найденный элемент
 obj.inside(this); // сообщить о занесении
} 
} 
Оператор break без метки применяется для выхода из внутренних операторов switch, for, 
while или do. Чтобы выйти из внешнего оператора, снабдите его меткой и укажите ее в
операторе break: 
private float[][] Matix; 
public boolean workOnFlag(float flag) { 
int y, x; 
boolean found = false; 
 search: 
for (y = 0; y << Matrix.length; y++) { 
 for (x = 0; x << Matrix[y].length; x++) { 
 if (Matrix[y][x] == flag) { 
 found = true; 
 break search; 
 } 
} 
} 
if (!found) 
 return false; 
// сделать что-нибудь с найденным элементом матрицы
return true; 
} 
Использование меток оставляется на усмотрение программиста. Впрочем, оно может
оказаться хорошей защитной мерой против модификации ваших программ — например, 
включения их фрагментов в оператор switch или цикл. 
Обратите внимание: break с меткой — это не то же самое, что goto. Оператор goto 
приводит к хаотичным прыжкам по программе и усложняет понимание ее смысла. 
Напротив, оператор break или continue, в котором используется метка, осуществляет
выход лишь из конкретного помеченного блока, а порядок выполнения программы
остается вполне понятным. 
6.8. Оператор continue 
Оператор continue осуществляет переход в конец тела цикла и вычисляет значение
управляющего логического выражения. Этот оператор часто используется для пропуска
некоторых значений в диапазоне цикла, которые должны игнорироваться или
обрабатываться в отдельном фрагменте. Например, при работе с потоком, состоящим из
отдельных лексем, лексема “skip” (“пропуск”) может обрабатываться следующим образом: 
while (!stream.eof()) { 
token = stream.next(); 
if (token.equals("skip")) 
 continue; 
converted to PDF by BoJIoc// ... обработка лексемы ... 
} 
Оператор continue имеет смысл только внутри циклов — while, do-while и for. В нем может
указываться метка внешнего цикла, и в этом случае continue относится к указанному
циклу, а не к ближайшему внутреннему. При выполнении такого помеченного оператора
continue осуществляется выход из всех внутренних циклов, чтобы выполнить следующую
итерацию указанного цикла. В приведенном выше примере можно обойтись без метки в
операторе continue, поскольку имеется всего один внешний цикл. 
6.9. Оператор return 
Оператор return завершает выполнение метода и передает управление в точку его
вызова. Если метод не возвращает никакого значения, достаточно простого оператора
return: 
return; 
Если же метод имеет возвращаемый тип, то в оператор return должно входить такое
выражение, которое может быть присвоено переменной возвращаемого типа. Например, 
если метод возвращает double, то в оператор return могут входить выражения типа double, 
float или целого типа: 
protected double nonNegative(double val) { 
if (val << 0) 
 return 0; // константа типа int 
else 
 return val; // double 
} 
Оператор return также используется для выхода из конструкторов и статических
инициализаторов. Конструктор не может возвращать никакого значения, поэтому в этом
случае return не содержит возвращаемого значения. Конструкторы вызываются как часть
процесса new, который в конечном счете возвращает ссылку на объект, однако каждый
конструктор играет в этом процессе лишь частичную роль; ни один из конструкторов не
возвращает итоговую ссылку. 
6.10. Где же goto? 
В Java нет конструкции goto, которая служила бы для передачи управления
произвольному оператору внутри метода, хотя она достаточно распространена в языках
того семейства, с которым связан язык Java. Оператор goto чаще всего применяется для
следующих целей: 
· Управление выполнением внешних циклов из внутренних. Для этого в Java 
предусмотрены операторы break и continue с метками. 
· Пропуск оставшейся части блока, не входящего в цикл, при нахождении ответа
или обнаружении ошибки. Используйте break с меткой. 
· Выполнение завершающего кода при выходе из блока или метода. Используйте
либо break с меткой, либо (более наглядно) — конструкцию finally оператора try, 
рассмотренную в следующей главе. 
Операторы break и continue с метками имеют то преимущество, что они передают
управление в строго определенную точку программы. Блок finally подходит к передаче
управления еще более жестко и работает при всех обстоятельствах, в том числе и при
возникновении исключений. С помощью этих конструкций можно писать наглядные
программы на Java без применения goto. 
converted to PDF by BoJIocГлава 7 
ИСКЛЮЧЕНИЯ
Плохо подогнанное снаряжение может заставить ваш гранатомет M203 выстрелить в
самый неожиданный момент. 
Подобное происшествие плохо скажется на вашей репутации среди тех, кто останется в
живых.
Журнал PS армии США, 
август 1993 года 
Во время своей работы приложение иногда сталкивается с разного рода нештатными
ситуациями. При вызове метода некоторого объекта он может обнаружить у себя
внутренние проблемы (неверные значения переменных), найти ошибки в других объектах
или данных (например, в файле или сетевом адресе), определить факт нарушения своего
базового контракта (чтение данных из закрытого потока) и так далее. 
Многие программисты не проверяют все возможные источники ошибок, и на то есть
веская причина: если при каждом вызове метода анализировать все мыслимые ошибки, 
текст программы становится совершенно невразумительным. Таким образом достигается
компромисс между правильностью (проверка всех ошибок) и ясностью (отказ от
загромождения основной логики программы множеством проверок). 
Исключения предоставляют удобную возможность проверки ошибок без загромождения
текста программы. Кроме того, исключения непосредственно сигнализируют об ошибках, 
а не меняют значения флагов или каких-либо полей, которые потом нужно проверять. 
Исключения превращают ошибки, о которых может сигнализировать метод, в явную часть
контракта этого метода. Список исключений виден программисту, проверяется
компилятором и сохраняется в расширенных классах, переопределяющих данный метод. 
Исключение возбуждается, когда возникает неожиданное ошибочное состояние. Затем
исключение перехватывается соответствующим условием в стеке вызова методов. Если
исключение не перехвачено, срабатывает обработчик исключения по умолчанию, 
который обычно выводит полезную информацию об исключении (скажем, содержимое
стека вызовов). 
7.1. Создание новых типов исключений
Исключения в Java представляют собой объекты. Все типы исключений (то есть все
классы, объекты которых возбуждаются в качестве исключений) должны расширять класс
языка Java, который называется Throwable, или один из его подклассов. Класс Throwable 
содержит строку, которая может использоваться для описания исключения. По
соглашению, новые типы исключений расширяют класс Exception, а не Throwable. 
Исключения Java, главным образом, являются проверяемыми — это означает, что
компилятор следит за тем, чтобы ваши методы возбуждали лишь те исключения, о
которых объявлено в заголовке метода. Стандартные исключения времени выполнения и
ошибки расширяют классы RuntimeException и Error, тем самым создавая непроверяемые
исключения. Все исключения, определяемые программистом, должны расширять класс
Exception, и, таким образом, они являются проверяемыми. 
Иногда хочется иметь больше данных, описывающих состояние исключения, — одной
строки, предоставляемой классом Exception, оказывается недостаточно. В таких случаях
можно расширить класс Exception и создать на его основе новый класс с
дополнительными данными (значения которых обычно задаются в конструкторе). 
converted to PDF by BoJIocНапример, предположим, что в интерфейс Attributed, рассмотренный в главе 4, добавился
метод replaceValue, который заменяет текущее значение именованного атрибута новым. 
Если атрибут с указанным именем не существует, возбуждается исключение — вполне
резонно предположить, что заменить несуществующий атрибут не удастся. Исключение
должно содержать имя атрибута и новое значение, которое пытались ему присвоить. Для
работы с таким исключением создается класс NoSuchAttribiteException: 
 public class NoSuchAttributeException extends Exception { 
 public String attrName; 
 public Object newValue; 
 NoSuchAttributeException(String name, Object value) { 
 super("No attribute named \"" + name + "\" found"); 
 attrName = name; 
 newValue = value; 
 } 
} 
NoSuchAttribiteException расширяет Exception и включает конструктор, которому
передается имя атрибута и присваиваемое значение; кроме того, добавляются открытые
поля для хранения данных. Внутри конструктора вызывается конструктор суперкласса со
строкой, описывающей происходящее. Исключения такого рода могут использоваться во
фрагменте программы, перехватывающем исключения, поскольку они выводят понятное
человеку описание ошибки и данные, вызвавшие ее. Добавление полезной
информации — одна из причин, по которым создаются новые исключения. 
Другая причина для появления новых типов исключений заключается в том, что тип
является важной частью данных исключения, поскольку исключения перехватываются по
их типу. Из этих соображений исключение NoSuch AttribiteException стоит создать даже в
том случае, если вы не собираетесь включать в него новые данные; в этом случае
программист, для которого представляет интерес только это исключение, сможет
перехватить его отдельно от всех прочих исключений, запускаемых методами интерфейса
Attributed или иными методами, применяемыми к другим объектам в том же фрагменте
программы. 
В общем случае исключения новых типов следует создавать тогда, когда программист
хочет обрабатывать ошибки одного типа и пропускать ошибки другого типа. В этом случае
он может воспользоваться новыми исключениями для выполнения нужного фрагмента
программы, вместо того чтобы изучать содержимое объекта-исключения и решать, 
интересует ли его данное исключение или же оно не относится к делу и перехвачено
случайно. 
7.2. Оператор throw 
Исключения возбуждаются оператором throw, которому в качестве параметра передается
объект. Например, вот как выглядит реализация replaceValue в классе AttributedImpl из
главы 4: 
public void replaceValue(String name, Object newValue) 
throws NoSuchAttributeException 
{ 
 Attr attr = find(name); // Искать attr 
 if (attr == null) // Если атрибут не найден
 throw new NoSuchAttributeException(name, this); 
 attr.valueOf(newValue); 
} 
Метод replaceValue сначала ищет имя атрибута в текущем объекте Attr. Если атрибут не
найден, то возбуждается объект-исключение типа NoSuch AttribiteException и его
конструктору предоставляются содержательные данные. Исключения являются
converted to PDF by BoJIocобъектами, поэтому перед использованием их необходимо создать. Если атрибут не
существует, то его значение заменяется новым.
Разумеется, исключение может быть порождено вызовом метода, внутри которого оно
возбуждается. 
7.3. Условие throws 
Первое, что бросается в глаза в приведенном выше методе replace Value, — это список
проверяемых исключений, которые в нем возбуждаются. В Java необходимо перечислить
проверяемые исключения, возбуждаемые методом, поскольку программист при вызове
метода должен знать их в такой же степени, в какой он представляет себе нормальное
поведение метода. Проверяемые исключения, возбуждаемые методом, не уступают по
своей важности типу возвращаемого значения — и то и другое необходимо объявить. 
Проверяемые исключения объявляются в условии throws, которое может содержать
список значений, отделяемых друг от друга запятыми. 
Внутри метода разрешается возбуждать исключения, являющиеся расширениями типа
Exception в условии throws, поскольку всегда допускается полиморфно использовать
класс вместо его суперкласса. Метод может возбуждать несколько различных
исключений, являющихся расширениями одного конкретного класса, и при этом объявить
в условии throws всего один суперкласс. Тем не менее, поступая таким образом, вы
скрываете от работающих с методом программистов полезную информацию, потому что
они не будут знать, какие из возможных расширенных типов исключений возбуждаются
методом. В целях надлежащего документирования условие throws должно быть как можно
более полным и подробным. 
Контракт, определяемый условием throws, обязан неукоснительно соблюдаться — можно
возбуждать лишь те исключения, которые указаны в данном условии. Возбуждение
любого другого исключения (прямое, с помощью throw, или косвенное, через вызов
другого метода) является недопустимым. Отсутствие условия throws не означает, что
метод может возбуждать любые исключения; наоборот, оно говорит о том, что он не
возбуждает никаких исключений. 
Все стандартные исключения времени выполнения (такие, как ClassCast Exception и
ArithmeticException) представляют собой расширения класса RuntimeException. О более
серьезных ошибках сигнализируют исключения, которые являются расширениями класса
Error и могут возникнуть в произвольный момент в произвольной точке программы. 
RuntimeException и Error — единственные исключения, которые не нужно перечислять в
условии throws; они являются общепринятыми и могут возбуждаться в любом методе, 
поэтому компилятор не проверяет их. Полный список классов стандартных
непроверяемых исключений приведен в Приложении Б. 
Инициализаторы и блоки статической инициализации не могут возбуждать проверяемые
исключения, как прямо, так и посредством вызова метода, возбуждающего исключение. 
Во время конструирования объекта нет никакого способа перехватить и обработать
исключение. При инициализации полей выход заключается в том, чтобы
инициализировать их внутри конструктора, который может возбуждать исключения. Для
статических инициализаторов можно поместить инициализацию в статический блок, 
который бы перехватывал и обрабатывал исключение. Статические блоки не возбуждают
исключений, но могут перехватывать их. 
Java довольно строго подходит к обработке проверяемых исключений, поскольку это
помогает избежать программных сбоев, вызванных невниманием к ошибкам. Опыт
показывает, что программисты забывают про обработку ошибок или откладывают ее на
будущее, которое так никогда и не наступает. Условие throws ясно показывает, какие
исключения возбуждаются методом, и обеспечивает их обработку. 
converted to PDF by BoJIocПри вызове метода, у которого в условии throws приведено проверяемое исключение, 
имеются три варианта:
· Перехватить исключение и обработать его. 
· Перехватить исключение и перенаправить его в обработчик одного из ваших
исключений, для чего возбудить исключение типа, объявленного в вашем условии
throws. 
· Объявить данное исключение в условии throws и отказаться от его обработки в
вашем методе (хотя в нем может присутствовать условие finally, которое сначала
выполнит некоторые завершающие действия; подробности приводятся ниже). 
При любом из этих вариантов вам необходимо перехватить исключение, возбужденное
другим методом; это станет темой следующего раздела. 
Упражнение 7.1 
Создайте класс-исключение ObjectNotFoundException для класса Linked List, построенного
нами в предыдущих упражнениях. Включите в него метод find, предназначенный для
поиска объектов в списке, который либо возвращает нужный объект LinkedList, либо
возбуждает исключение, если объект отсутствует в списке. Почему такой вариант
оказывается более предпочтительным, нежели возврат значения null для ненайденного
объекта? Какие данные должны входить в ObjectNotFoundException? 
7.4. Операторы try, catch и finally 
Чтобы перехватить исключение, необходимо поместить фрагмент программы в оператор
try. Базовый синтаксис оператора try выглядит следующим образом: 
try 
 блок
catch (тип-исключения идентификатор) 
 блок
catch (тип-исключения идентификатор) 
 блок
 ..... 
finally 
 блок
Тело оператора try выполняется вплоть до возбуждения исключения или до успешного
завершения. Если возникает исключение, то по порядку просматриваются все условия
catch, пока не будет найдено исключение нужного класса или одного из его суперклассов. 
Если подходящее условие catch так и не найдено, то исключение выходит из текущего
оператора try во внешний, который может обработать его. В операторе try может
присутствовать любое количество условий catch, в том числе и ни одного. Если ни одно из
условий catch внутри метода не перехватывает исключение, то оно передается в тот
фрагмент программы, который вызвал данный метод. 
Если в try присутствует условие finally, то составляющие его операторы выполняются
после того, как вся обработка внутри try будет завершена. Выполнение finally происходит
независимо от того, как завершился оператор — нормально, в результате исключения или
при выполнении управляющего оператора типа return или break. 
В приводимом ниже примере осуществляется подготовка к обработке одного из
исключений, возбуждаемых в replaceValue: 
try { 
 attributedObj.replaceValue("Age", new Integer(8)); 
} catch (NoSuchAttributeException e) { 
converted to PDF by BoJIoc // так не должно быть, но если уж случилось - восстановить
 Attr attr = new Attr(e.attrName, e.newValue); 
 attrbuteObj.add(attr); 
} 
try содержит оператор (представляющий собой блок), который выполняет некоторые
действия, в обычных условиях заканчивающиеся успешно. Если все идет нормально, то
работа блока на этом завершается. Если же во время выполнения программы в try-блоке
возбудилось какое-либо исключение (прямо, посредством throw, либо косвенно, через
внутренний вызов метода), то выполнение кода внутри try прекращается, и
просматриваются связанные с ним условия catch, чтобы определить, нужно ли
перехватывать исключение. 
Условие catch чем-то напоминает внедренный метод с одним параметром —типом
перехватываемого исключения. Внутри условия catch вы можете пытаться восстановить
работу программы после произошедшего исключения или же выполнить некоторые
действия и повторно возбудить исключение, чтобы вызывающий фрагмент также имел
возможность перехватить его. Кроме того, catch может сделать то, что сочтет нужным, и
прекратить свою работу — в этом случае управление передается оператору, следующему
за оператором try (после выполнения условия finally, если оно имеется). 
Универсальное условие catch (например, перехватывающее исключения типа Exception) 
обычно говорит о плохо продуманной реализации, поскольку оно будет перехватывать
все исключения, а не только то, которое нас интересует. Если воспользоваться подобным
условием в своей программе, то в результате при возникновении проблем с атрибутами
будет обрабатываться, скажем, исключение ClassCastException. 
Условия catch в операторе try просматриваются поочередно, от первого к последнему, 
чтобы определить, может ли тип объекта-исключения присваиваться типу, объявленному
в catch. Когда будет найдено условие catch с подходящим типом, происходит выполнение
его блока, причем идентификатору в заголовке catch присваивается ссылка на объект-
исключение. Другие условия catch при этом не выполняются. С оператором try может
быть связано произвольное число условий catch, если каждое из них перехватывает
новый тип исключения. 
Поскольку условия catch просматриваются поочередно, перехват исключения некоторого
типа перед перехватом исключения расширенного типа является ошибкой. Первое
условие всегда будет перехватывать исключение, а второе — никогда. По этой причине
размещение условия catch для исключения-суперкласса перед условием для одного из его
подклассов вызывает ошибку во время компиляции: 
class SuperException extends Exception { } 
class SubException extends SuperException { } 
class BadCatch { 
 public void goodTry() { 
 /* НЕДОПУСТИМЫЙ порядок перехвата исключений */ 
 try { 
 throw new SubException(); 
 } catch (SuperException superRef) { 
 // Перехватывает и SuperException, и SubException 
 } catch (SubException subRef) { 
 // Никогда не выполняется
 } 
 } 
} 
В каждом операторе try обрабатывается только один исключительный случай. Если catch 
или finally возбуждают новое исключение, то условия catch данного try не
рассматриваются повторно. Код в условиях catch и finally находится за пределами защиты
converted to PDF by BoJIoc· Импортировать attr.Attributed или attr.*, после чего использовать простое имя
Attributed вместо attr.Attributed и полное имя lingua.Attributed. 
· Сделать обратное — импортировать lingua.Attributed или lingua.*, после чего
использовать простое имя Attributed вместо lingua. Attributed и полное имя
attr.Attributed. 
10.1. Имена пакетов
Имя пакета должно использоваться только один раз, так что выбор содержательного и
уникального имени составляет важный аспект проектирования пакета. Однако сейчас, 
когда программисты всей планеты разрабатывают пакеты на языке Java, невозможно
выяснить, какие имена пакетов ими используются. Следовательно, выбор уникального
имени представляет некоторую проблему. Если вы уверены, что пакет будет
использоваться только внутри вашей организации, то можно привлечь к делу выбора
имени внутреннего арбитра — это позволит быть уверенным, что все пакеты будут иметь
отличающиеся имена. 
Тем не менее в нашем огромном мире такой подход не сработает. Идентификаторы
пакетов Java представляют собой обычные имена, так что неплохой способ обеспечить их
уникальность — включить в них имя домена организации в Internet. Если вы работаете в
компании Magic, Inc., то пакет с атрибутами может быть объявлен следующим образом: 
package COM.magic.attr; 
Обратите внимание: компоненты имени следуют в обратном порядке по сравнению с
обычным именем домена, а имя домена верхнего уровня (в нашем случае COM) написано
прописными буквами. Это сделано для того, чтобы избежать конфликтов с именами
пакетов, которые не следуют этому соглашению — их названия могут случайно совпасть с
именем домена верхнего уровня, но вряд ли при этом они будут написаны прописными
буквами. 
Если вы следуете данному соглашению, конфликты возникать не будут (разве что внутри
вашей организации). Если проблемы все же возникли (скажем, в очень большой
организации), можно пойти дальше и уточнить имя домена. Во многих крупных компаниях
имеются внутренние домены с именами east, europe и т. д. Вы можете уточнить имя
пакета с использованием имени внутреннего домена: 
package COM.magic.japan.attr; 
При этом имена пакетов могут стать довольно длинными, однако такая схема
относительно безопасна — никто из тех, кто ее использует, не выберет имя, совпадающее
с названием вашего пакета. 
Во многих средах разработки имена пакетов отражаются на уровне файловой системы — 
часто требуется, чтобы все программы из одного пакета находились в определенной
папке или каталоге, а имя этого каталога соответствовало имени пакета. Подробности
можно узнать в документации, относящейся к вашей среде разработки. 
10.2. Пакетный доступ
Классы и интерфейсы, входящие в пакет, обладают одним из двух уровней доступа: 
пакетным (package) и открытым (public). Открытый класс или интерфейс доступен для
программ, не входящих в пакет. Типы, не являющиеся открытыми, обладают областью
видимости на уровне пакета: они доступны для всех программ того же пакета, но скрыты
за его пределами, в том числе даже от программ во вложенных пакетах. 
converted to PDF by BoJIocЧлены классов тоже обладают уровнем доступа. Член, не объявленный с ключевым
словом public, protected или private, может использоваться любой программой, входящей в
пакет, но остается невидим за пределами пакета. Другими словами, по умолчанию
идентификаторы обладают “пакетным” уровнем доступа, за исключением членов
интерфейсов, которые являются открытыми.
Поля или методы, не объявленные в пакете с ключевым словом private, доступны для всех
программ этого пакета. Следовательно, классы, входящие в тот же пакет, считаются
“дружественными”, или “заслуживающими доверия”. Однако подпакеты не пользуются
доверием в своих внешних пакетах. Например, защищенные и пакетные идентификаторы
в пакете dit недоступны для программ в пакете dit.dat и наоборот. 
10.3. Содержимое пакета
Если в исходном файле отсутствует объявление package, то входящие в него типы
считаются принадлежащими к “безымянному” пакету. 
К проектированию пакета следует подходить внимательно и включать в него только
функционально связанные классы и интерфейсы. Классы пакета могут свободно
обращаться к незакрытым членам друг друга. Защита членов класса предназначена для
предотвращения некорректных действий со стороны классов, обладающих доступом к
деталям внутренней реализации других классов. В пределах пакета не существует
никаких ограничений доступа, кроме priv a te, и в итоге может получиться так, что
посторонний класс получил к другим классам более близкий доступ, чем вам хотелось бы. 
Кроме того, пакет должен составляться исходя из логического разделения задач, чтобы
помочь программистам, которые ищут полезные для себя интерфейсы и классы. Если
пакет состоит из несвязанных классов, будет сложно понять, что же из этого можно
применить в работе. Логическое разделение способствует повторному использованию
вашего кода, поскольку программистам будет легче ориентироваться в нем. 
Если пакет состоит из взаимосвязанных типов, вы сможете давать им очевидные имена, 
избегая при этом конфликтов с посторонними типами из этого же пакета. 
Пакет может быть составной частью другого пакета. Например, пакет java.lang является
вложенным, то есть пакет lang входит в более обширный пакет java. Пакет java не
содержит ничего, кроме других пакетов. Вложение позволяет построить иерархическую
систему имен для взаимосвязанных пакетов. 
Например, чтобы создать набор пакетов для адаптивных систем (скажем, нейросетей или
генетических алгоритмов), можно воспользоваться вложенными пакетами, разделяя их
имена точками: 
package adaptive.neuralNet; 
Исходный файл с таким объявлением входит в пакет adaptive.neuralNet, который, в свою
очередь, является подпакетом пакета adaptive. Пакет adaptive может содержать классы, 
относящиеся к общим адаптивным алгоритмам, — например, методы с общей постановкой
генетических проблем или способы измерения каких-то показателей. Каждый пакет, 
который находится ниже в иерархии (например, adaptive.neuralNet или adaptive. genetic), 
содержит классы, предназначенные для конкретного типа адаптивных алгоритмов. 
Вложение пакетов является средством организации взаимосвязанных пакетов и не имеет
отношения к правам доступа. Классы пакета adaptive. genetic не смогут работать с
закрытыми на пакетном уровне идентификаторами из adaptive или adaptive.neuralNet. 
Область видимости пакета не выходит за его пределы. Вложение позволяет группировать
взаимосвязанные пакеты и помогает программистам искать классы в иерархической
структуре, но не дает никаких других преимуществ. 
converted to PDF by BoJIocГлава 11
ПАКЕТ ВВОДА/ВЫВОДА
С точки зрения программиста, 
пользователь — это периферийное устройство, 
вводящее символы в ответ
на команду read.
Питер Уильямс 
Ввод/вывод в Java описывается в терминах потоков. Потоком /К сожалению, в
отечественной литературе одним словом "поток" переводятся совершенно разнородные
термины thread (см. выше) и stream, что создает определенную двусмысленность. В тех
случаях, когда контекст не дает однозначного толкования, для перевода thread 
используется уточняющий термин "программный поток" - Примеч. перев./ называется
упорядоченная последовательность данных, которая имеет источник (входной поток) или
приемник (выходной поток). Потоки ввода/вывода избавляют программиста от
необходимости вникать в конкретные детали операционной системы и позволяют
осуществлять доступ к файлам. В основе работы всех потоков лежит ограниченный набор
базовых интерфейсов и абстрактных классов; большинство типов потоков (например, 
потоки для работы с файлами) поддерживают базовые методы, иногда — с минимальными
модификациями. Самый лучший способ освоения ввода/вывода в Java заключается в
изучении базовых интерфейсов и абстрактных классов. В качестве примера мы
рассмотрим файловые потоки. 
Основным исключением из этой модели являются потоки данных, которые читают и
записывают значения базовых типов Java, таких как int или string. Эти потоки
поддерживают более широкий набор методов, спроектированный с учетом их специфики. 
Они рассматриваются во второй части этой главы, начиная с раздела 11.16. 
Пакет ввода/вывода в Java называется java.io. Он импортируется во всех листингах этой
главы, даже если строка import и не входит в пример. 
На момент написания книги не существовало стандартной библиотеки для манипуляций с
выходными числовыми или строковыми форматами — скажем, задания минимальной или
максимальной ширины или желательной точности для числа с плавающей точкой. 
11.1. Потоки
В пакете java.io определяется несколько абстрактных классов для базовых входных и
выходных потоков. Затем эти абстрактные классы расширяются, и на их основе создаются
некоторые полезные типы потоков. Потоки почти всегда являются парными: если
существует FileInputStream, то есть и FileOutputStream. 
Кроме того, существуют классы для работы с именами файлов, класс потока с
возможностью чтения/записи с именем RandomAccessFile и анализатор для деления
входного потока на отдельные лексемы. 
Класс IOException используется многими методами java.io для сигнализации об
исключительных состояниях. Некоторые классы, являющиеся расширениями IOException, 
сообщают о конкретных проблемах, однако в большинстве случаев все же применяются
объекты IOException со строкой-описанием. Подробности приведены в разделе 11.20. 
Перед тем как рассматривать конкретные виды входных и выходных потоков, следует
ознакомиться с базовыми абстрактными классами InputStream и OutputStream. Иерархия
типов пакета java.io изображена на рис. 11.1. 
converted to PDF by BoJIocРис. 11.1. Иерархия типов в java.io 
11.2. Класс InputStream 
В абстрактном классе InputStream объявляются методы для чтения из заданного
источника. InputStream является базовым классом для большинства входных потоков в
java.io и содержит следующие методы: 
public InputStream() 
Класс InputStream содержит только безаргументный конструктор. 
public abstract int read() throws IOException 
Читает из потока один байт данных и возвращает прочитанное значение, лежащее в
диапазоне от 0 до 255 (не от –128 до 127). При достижении конца потока возвращается
флаг –1. Метод блокирует работу программы до появления значения на входе. 
public int read(byte[] buf) throws IOException 
Читает данные в массив байтов. Метод блокирует работу программы до появления
вводимого значения, после чего заполняет buf всеми прочитанными байтами, в
количестве не более buf.length. Метод возвращает фактическое количество прочитанных
байтов или –1 при достижении конца потока. 
public int read(byte[] buf, int off, int len) throws IOException 
Читает данные в байтовый подмассив. Метод блокирует работу программы до начала
ввода, после чего заполняет часть массива buf, начиная со смещения off, в количестве до
len байтов, если не встретится конец массива buf. 
public long skip(long count) throws IOException 
Пропускает до count байтов во входном потоке. Количество пропущенных байтов может
быть меньше count из-за достижения конца потока. Возвращает фактическое количество
пропущенных байтов. 
public int available() throws IOException 
Возвращает количество байтов, которые могут быть прочитаны без блокировки работы
программы. 
converted to PDF by BoJIocpublic void close() throws IOException 
Закрывает входной поток. Метод должен вызываться для освобождения любых ресурсов
(например, файловых дескрипторов), связанных с потоком. Если не сделать это, то
ресурсы будут считаться занятыми, пока сборщик мусора не вызовет метод finalize 
данного потока. 
Приведенная ниже программа подсчитывает общее количество символов и разделителей
(white-space characters) в файле: 
import java.io.*; 
class CountSpace { 
 public static void main(String[] args) 
 throws IOException 
 { 
 InputStream in; 
 if (args.length == 0) 
 in = System.in; 
 else 
 in = new FileInputStream(args[0]); 
 int ch; 
 int total; 
 int spaces = 0; 
 for (total = 0; (ch = in.read()) != -1; total++) { 
 if (Character.isSpace((char)ch) 
 spaces++; 
 } 
 System.out.println(total + " chars, " 
 + spaces + " spaces"); 
 } 
} 
Программа либо берет имя файла из командной строки, либо читает данные из
стандартного входного потока, System.in. Входной поток представлен переменной in. Если
имя файла не задано, используется стандартный входной поток; если же оно указано, то
создается объект FileInputStream, являющийся расширением InputStream. 
Цикл for подсчитывает как общее количество символов в файле, так и количество
символов-разделителей; для идентификации последних применяется метод isSpace класса
Character. В конце происходит вывод результатов. Вот как они выглядят, если программа
используется с файлом, содержащим ее собственный исходный текст: 
434 chars, 109 spaces 
Возможно, вам захочется присвоить значение total с помощью метода available, однако
для потока System.in такой вариант не сработает. Метод available возвращает количество
байтов, которые могут быть прочитаны без блокировки. Для файла оно обычно
представляет собой его длину. Если же поток System.in будет связан с клавиатурой, то
возвращаемое методом значение может быть равно нулю; если необработанные символы
ввода отсутствуют, то следующий вызов read приведет к блокировке. 
11.3. Класс OutputStream 
Абстрактный класс OutputStream во многих отношениях напоминает InputStream; он
абстрагирует поток байтов, направляемых в приемник. Класс содержит следующие
методы: 
converted to PDF by BoJIocpublic OutputStream() 
Класс OutputStream содержит только безаргументный конструктор. 
public abstract void write(int b) throws IOException 
Записывает в поток байт b. Байт передается в виде значения int, поскольку он часто
является результатом арифметической операции над байтами. Выражения, в которых
входят данные типа byte, имеют тип int, так что параметр типа int позволяет использовать
результат без преобразования в byte. Тем не менее обратите внимание на то, что
передаются только младшие 8 бит значения int — старшие 24 бита при этом теряются. 
Метод блокирует работу программы до завершения записи байта. 
public void write(byte[] buf) throws IOException 
Записывает в поток содержимое массива байтов. Метод блокирует работу программы до
завершения записи. 
public void write(byte[] buf, int offset, int len) throws IOException 
Записывает в поток часть массива байтов, которая начинается с buf [offset] и насчитывает
до count байтов, если ранее не будет встречен конец массива. 
public void flush() throws IOException 
Очищает поток, то есть направляет в него все байты, находящиеся в буфере. 
public void close() throws IOException 
Закрывает поток. Метод должен вызываться для освобождения любых ресурсов, 
связанных с потоком. 
Если явно не указывается противное, то при обнаружении ошибки в выходном потоке все
эти методы возбуждают исключение IOException. 
Ниже приводится приложение, копирующее свой входной поток в выходной и попутно
заменяющее некоторые символы. Приложение Translate получает два параметра: строку
from и строку to. Если во входном потоке встречается символ, входящий в строку from, он
заменяется символом строки to, находящимся в той же позиции: 
import java.io.*; 
class Translate { 
 public static void main(String[] args) { 
 InputStream in = System.in; 
 OutputStream out = System.out; 
 if (args.length != 2) 
 error ("must provide from/to arguments"); 
 String from = args[0], to = args[1]; 
 int ch, i; 
 if (from.length() != to.length()) 
 error ("from and to must be same length"); 
 try { 
 while ((ch = in.read()) != 1) { 
 if ((i = from.indexOf(ch)) != -1) 
 out.write(to.charAt(i)); 
converted to PDF by BoJIoc else 
 out.write(ch); 
 } 
 } catch (IOException e) { 
 error ("I/O Exception: " + e); 
 } 
 } 
 public static void error(String err) { 
 System.err.print("Translate: " + err); 
 System.exit(1); // ненулевое значение означает
 // неблагополучное завершение
 } 
} 
Упражнение 11.1 
Перепишите приведенную выше программу Translate в виде метода, который пересылает
символы из InputStream в OutputStream, а метод трансляции (правило замены символов) 
и потоки являются параметрами. Для каждого типа InputStream и OutputStream, о
которых говорилось в этой главе, напишите новый метод main, в котором бы учитывалась
возможность трансляции символов при вводе или выводе. Если потоки ввода и вывода
оказываются симметричными, то для них может применяться общий метод main. 
11.4. Стандартные типы потоков
Как видно из рис. 11.1, в пакете java.io определяются несколько типов потоков. Обычно
они составляют пары ввода/вывода: 
· Конвейерные потоки Piped спроектированы для парного использования, при
котором байты, записываемые в PipedOutputStream, могут читаться из
PipedInputStream. 
· Байтовые потоки ByteArray осуществляют ввод/вывод в массив байтов. 
· Фильтрующие потоки Filtered представляют собой абстрактные классы байтовых
потоков, в которых с читаемыми байтами выполняются некоторые операции-
фильтры. Объект FilterInputStream получает ввод от другого объекта InputStream, 
некоторым образом обрабатывает (фильтрует) байты и возвращает результат. 
Фильтрующие потоки могут объединяться в последовательности, при этом
несколько фильтров превращаются в один сквозной фильтр. Аналогичным
образом осуществляется и фильтрация вывода — для этого применяются
различные классы Filter OutputStream. 
· Буферизованные потоки Buffered расширяют понятие фильтрующих потоков, 
добавляя буферизацию, чтобы при каждом вызове read и write не приходилось
обращаться к файловой системе. 
· Потоки данных Data разделяются на две категории. Интерфейсы Data Input и
DataOutput определяют методы для чтения и записи данных встроенных типов, 
причем вывод одного из них воспринимается в качестве ввода другого. Эти
интерфейсы реализуются классами DataInputStream и Data OutputStream. 
· Файловые потоки File расширяют понятие фильтрующих потоков — байтовый
поток в них связывается с определенным файлом. В них встроены некоторые
методы, относящиеся к работе с файлами. 
converted to PDF by BoJIocВ пакет также входит ряд потоков ввода (вывода), для которых отсутствуют парные им
потоки вывода (ввода):
· Поток SequenceInputStream преобразует последовательность объектов
InputStream в один общий InputStream, благодаря чему несколько объединенных
входных потоков могут рассматриваться в виде единого входного потока. 
· StringBufferInputStream использует объект StringBuffer в качестве входного
потока. 
· LineNumberInputStream расширяет FilterInputStream и следит за нумерацией строк
входного потока. 
· PushbackInputStream расширяет FilterInputStream, добавляя возможность отката
на один байт, что оказывается полезным при сканировании и синтаксическом
анализе входного потока. 
· PrintStream расширяет OutputStream и включает методы print и println для
форматирования данных на выводе. К этому типу относятся потоки System.out и
Sy stem.err. 
Кроме указанных выше типов, имеются еще несколько полезных классов ввода/вывода: 
· Класс File (не путать с потоковым классом File!) предназначен для работы с
именами и путями файлов в локальной файловой системе. Он включает
разделители для компонентов пути, локальный разделитель- суффикс и ряд
полезных методов для работы с именами файлов. 
· RandomAccessFile позволяет работать с файлами на уровне потоков с
произвольным доступом. Он реализует интерфейсы DataInput и Data Output, а
также большинство методов ввода/вывода классов Input Stream и OutputStream. 
· Класс StreamTokenizer разбивает InputStream на отдельные лексемы. Он
представляет входной поток в виде понятных “слов”, что часто бывает необходимо
при синтаксическом анализе введенных пользователем выражений. 
Все эти классы могут расширяться и порождать новые разновидности потоковых классов, 
предназначенные для конкретных приложений. 
11.5. Фильтрующие потоки
Фильтрующие потоки добавляют несколько новых конструкторов к базовым
конструкторам классов InputStream и OutputStream. Им передается поток
соответствующего типа (входной или выходной), с которым необходимо соединить
объект. Фильтрующие потоки позволяют объединять потоки в “цепочки” и тем самым
создавать составной поток с большими возможностями. Приведенная программа печатает
номер строки файла, в которой будет обнаружено первое вхождение заданного символа: 
import java.io.*; 
class FindChar { 
 public static void main (String[] args) 
 throws Exception 
 { 
 if (args.length != 2) 
 throw new Exception("need char and file"); 
 int match = args[0].charAt(0); 
 FileInputStream 
converted to PDF by BoJIoc fileIn = new FileInputStream(filein); 
 int ch; 
 while ((ch == in.read()) != -1) { 
 if (ch == match) { 
 System.out.println("'" + (char)ch + 
 "' at line " + in.getLineNumber()); 
 System.exit(0); 
 } 
 } 
 System.out.println(ch + " not found"); 
 System.exit(1); 
 } 
} 
Программа создает поток класса FileInputStream с именем fileIn для чтения из указанного
файла и затем вставляет перед ним объект класса LineNumberInputStream с именем in. 
Объекты LineNumberInputStream получают ввод от входных потоков, за которыми они
закреплены, и следят за нумерацией строк. При чтении байтов из in на самом деле
происходит чтение из потока fileIn, который получает эти байты из входного файла. Если
запустить программу с файлом ее собственного исходного текста и буквой ‘I’ в качестве
аргументов, то результат работы будет выглядеть следующим образом: 
‘I’ at line 10 
Вы можете “сцепить” произвольное количество объектов FilterInput Stream. В качестве
исходного источника байтов допускается произвольный объект InputStream, не
обязательно относящийся к классу FilterInput Stream. Возможность сцепления является
одним из основных достоинств фильтрующих потоков, причем самый первый поток в
цепочке не должен относиться к классу FilterInputStream. 
Объекты FilterOutputStream могут сцепляться аналогичным образом. При этом байты, 
записанные в один выходной поток, будут подвергаться фильтрации и записываться в
другой выходной поток. Все потоки, от первого до предпоследнего, должны относиться к
классу FilterOutputStream, но последний поток может представлять собой любую из
разновидностей Output Stream. 
Применение фильтрующих потоков позволяет усовершенствовать поведение стандартных
потоков. Например, чтобы всегда знать номер текущей строки в System.in, можно
вставить в начало программы следующий фрагмент: 
LineNumberInputStream 
 lnum = new LinenumberInputStream(System.in); 
System.in = lnum; 
Во всем остальном тексте программы производятся обычные операции с System.in, однако
теперь появляется возможность следить за нумерацией строк. Для этого используется
следующий вызов: 
lnum.getLineNumber(); 
Поток LineNumberInputStream, закрепленный за другим потоком Input Stream, следует
контракту последнего, если InputStream — единственный тип, к которому мог бы
относиться данный поток. System.in может быть отнесен только к типу InputStream, так
что весь код программы, в котором он используется, вправе рассчитывать только на
выполнение контракта этого типа. LineNu m berInputStream поддерживает более широкий
спектр функций, так что замена исходного объекта на тот же самый объект с
добавленными функциями нумерации строк оказывается вполне допустимой. 
Упражнение 11.2 
converted to PDF by BoJIocРасширьте FilterInputStream для создания класса, который осуществляет построчное
чтение и возврат данных, с использованием метода, блокирующего работу программы до
появления полной строки ввода.
Упражнение 11.3 
Расширьте FilterOutputStream для создания класса, который преобразует каждое слово
входного потока в заглавный регистр (title case). /См. раздел 13.5 - Примеч. перев/ 
Упражнение 11.4 
Создайте пару фильтрующих потоковых классов для работы со сжатыми в произвольном
формате данными; при этом поток CompressInputStream должен уметь расшифровывать
данные, созданные потоком Compress OutputStream. 
11.6. Класс PrintStream 
Класс PrintStream используется каждый раз, когда в вашей программе встречается вызов
метода print или println. PrintStream является расширением FilterOutputStream, так что
передаваемые байты могут подвергаться фильтрации. Класс содержит методы print и
println для следующих типов: 
char int float Object boolean 
char[] long double String 
Кроме того, простой вызов println без параметров осуществляет переход на другую строку
без вывода информации. 
PrintStream содержит два конструктора. Один из них — конструктор FilterOutputStream, 
получающий в качестве параметра объект-поток. У другого конструктора имеется второй
параметр логического типа, который управляет автоматической очисткой (autoflushing) 
потока. Если значение этого аргумента равно true, то запись в поток символа перехода на
новую строку ‘\n’ приводит к вызову метода flush. В противном случае такой
символ ничем не отличается от всех остальных, и flush не вызывается. После
конструирования потока его поведение в отношении автоматической очистки уже не
может быть изменено. 
При включении автоматической очистки вызов какого-либо из методов write, 
записывающего массив байтов, приводит к обращению к flush. Символы ‘\n’, которые
встречаются внутри массивов, не вызывают flush, независимо от состояния флага
автоматической очистки. 
Методы print(String) и print(char[]) являются синхронизированными. Все остальные
методы print и println реализуются с помощью этих двух методов, так что печать в объект
PrintStream является безопасной при многопоточной работе. 
11.7. Буферизованные потоки
Объекты классов BufferedInputStream и BufferedOutputStream обладают свойством
буферизации, благодаря чему удается избежать вызова операций чтения/записи при
каждом новом обращении к потоку. Эти классы часто используются в сочетании с
файловыми потоками — работа с файлом на диске происходит сравнительно медленно, и
буферизация позволяет сократить количество обращений к физическому носителю. 
При создании буферизованного потока можно явно задать размер буфера или положиться
на значение, принятое по умолчанию. Буферизованный поток использует массив типа
byte для промежуточного хранения байтов, проходящих через поток. 
converted to PDF by BoJIocЕсли метод read вызывается для пустого потока BufferedInputStream, он выполняет
следующие действия: обращается к методу read потока-источника, заполняет буфер
максимально возможным количеством байтов и возвращает запрошенные данные из
буфера. 
Аналогично ведет себя и BufferedOutputStream. Когда очередной вызов write приводит к
заполнению буфера, вызывается метод write потока-приемника, направляющий
содержимое буфера в поток. 
Буферизованный выходной поток, используемый для записи данных в файл, создается
следующим образом: 
OutputStream bufferedFile(String path) 
 throws IOExceptioon 
{ 
 OutputStream out = new FileOutputStream(path); 
 return new BufferedOutputStream(out); 
} 
Сначала для указанного пути создается FileOutputStream, затем порождается
BufferedOutputStream и возвращается полученный буферизованный объект-поток. 
Подобная схема позволяет буферизовать вывод, предназначенный для занесения в файл. 
Чтобы пользоваться методами объекта FileOutputStream, необходимо сохранить ссылку на
него, поскольку для фильтрующих потоков не существует способа получить объект, 
следующий за данным объектом-потоком в цепочке. Перед тем как работать со
следующим потоком, необходимо очистить буфер, иначе данные в буфере не достигнут
следующего потока. 
11.8. Байтовые потоки
Байтовые массивы, используемые в качестве источников входных или приемников
выходных потоков, могут применяться для построение строк с данными для печати, 
декодирования данных и т. д. Эти возможности предоставляются потоками ByteArray. 
Методы потоков ByteArray являются синхронизированными, а следовательно — 
безопасными в условиях многопоточной среды. 
Класс ByteArrayInput использует в качестве источника данных массив типа byte. Он
содержит два конструктора: 
public ByteArrayInputStream(byte[] buf) 
Создает объект ByteArrayInputStream по заданному байтовому массиву. Массив
используется непосредственно, а не копируется. Достижение конца массива buf означает
завершение ввода данных из потока. 
public ByteArrayInputStream(byte[] buf, int offset, int length) 
Создает объект ByteArrayInputStream по заданному байтовому массиву, однако
используется лишь часть массива buf от buf[offset] до buf [offset+length-1] или до конца
массива (в зависимости от того, какая величина окажется меньше). 
Класс ByteArrayOutput осуществляет вывод в динамически увеличиваемый байтовый
массив. Он содержит следующие конструкторы и методы: 
public ByteArrayOutputStream() 
Создает объект ByteArrayOutputStream, размер которого выбирается по умолчанию. 
public ByteArrayOutputStream(int size) 
converted to PDF by BoJIoc public void add(Point p) { 
 vertices.addElements(p); 
 } 
 public void remove(Point p) { 
 vertices. RemoveElement(p); 
 } 
 public int numVertices() { 
 return vertices.size(); 
 } 
 // ... другие методы ... 
} 
Существует ряд методов, предназначенных для просмотра содержимого вектора. При
задании недопустимого индекса возбуждается исключение Index OutOfBoundsException. 
Все методы, которые ищут элемент в векторе, используют метод Object.equals для
сравнения искомого объекта с элементами Vector. 
public final synchronized Object elementAt(int index) 
Возвращает элемент с индексом index. 
public final boolean contains(Object obj) 
Возвращает true, если obj является элементом вектора. 
public final synchronized int indexOf(Object obj, int index) 
Ищет первое вхождение заданного объекта obj начиная с позиции index, и возвращает его
индекс или –1, если объект не найден. 
public final int indexOf(Object obj) 
Эквивалентен indexOf(obj,0). 
public final synchronized int lastIndexOf(Object obj, 
int index) 
Осуществляет поиск obj в обратном направлении от позиции index и возвращает его
индекс или –1, если объект не найден. 
public final int lastIndexOf(Object obj) 
Эквивалентен lastIndexOf(obj,size()-1). 
public final synchronized void copyInto(Object[] anArray) 
Копирует элементы вектора в заданный массив. Метод может применяться для
“фотографирования” содержимого вектора. 
public final synchronized Enumeration elements() 
Возвращает Enumeration для текущего состава элементов. Для последовательной выборки
элементов возвращаемого объекта применяются методы Enumeration. Исходное состояние
при этом не фиксируется, поэтому для получения “фотографии” содержимого вектора
пользуйтесь методом copy Into. 
public final synchronized Object firstElement() 
converted to PDF by BoJIocВозвращает первый элемент вектора. Если вектор пуст, возбуждается исключение
NoSuchElementException. 
public final synchronized Object lastElement() 
Возвращает последний элемент вектора. Если вектор пуст, возбуждается исключение
NoSuchElementException. Пара методов firstElement/ lastElement может использоваться для
перебора элементов вектора, но существует риск изменения вектора во время
выполнения цикла. Для получения “фотографии” содержимого вектора пользуйтесь
методом copyInto. 
Размер вектора равен количеству элементов, содержащихся в нем. Чтобы изменить
размер вектора, можно добавлять или удалять элементы либо вызвать метод setSize или
trimSize: 
public final int size() 
Возвращает количество элементов, содержащихся в векторе. Обратите внимание, что эта
величина отличается от емкости вектора. 
public final boolean isEmpty() 
Возвращает true, если вектор не содержит ни одного элемента. 
public final synchronized void trimToSize() 
Сокращает емкость вектора до текущего размера. Этот метод используется для
минимизации объема памяти, когда вектор находится в устойчивом состоянии. 
Последующие добавления элементов к вектору приведут к его увеличению. 
public final synchronized void setSize(int newSize) 
Устанавливает размер вектора равным newSize. Если при этом вектор сокращается, то
элементы за его концом теряются; если вектор увеличивается, то новые элементы равны
null. 
Правильное управление емкостью вектора существенно влияет на эффективность работы
с ним. Если приращение емкости оказывается небольшим, а количество добавляемых
элементов велико, то слишком много времени будет тратиться на то, чтобы повторно
создавать новый буфер увеличенного размера и копировать в него содержимое вектора. 
Лучше сразу создавать вектор, емкость которого равна максимальному возможному
размеру или близка к нему. Если вы знаете, сколько элементов будет добавлено в вектор, 
используйте метод ensureCapacity для однократного увеличения емкости вектора. 
Параметры управления емкостью задаются при конструировании вектора. Для создания
объектов Vector применяются следующие конструкторы: 
public Vector(int initialCapacity, int capacityIncrement) 
Создает пустой вектор с заданной исходной емкостью и запоминает ее приращение. 
Приращение, равное 0, означает удвоение емкости буфера при каждом его увеличении; в
противном случае буфер увеличивается на capacityIncrement элементов. 
public Vector(int initialCapacity) 
Эквивалентен Vector(initialCapacity, 0). 
public Vector() 
Конструирует пустой вектор со значениями исходной емкости и приращения, заданными
по умолчанию. 
converted to PDF by BoJIocpublic final int capacity() 
Возвращает текущую емкость вектора — количество элементов, которые могут храниться
в векторе без увеличения его размера. 
public final synchronized void ensureCapacity (int minCapacity) 
Метод гарантирует, что емкость вектора будет не ниже заданной, и при необходимости
увеличивает текущую емкость. 
Приведем метод класса Polygon, который присоединяет к объекту вершины другого
многоугольника: 
public void merge(Polygon other) { 
 int otherSize = other.vertices.size(); 
 vertices.enusreCapacity(vertices.size() + otherSize); 
 for (int i = 0; i << otherSize; i++) 
 vertices.addElement(other.vertices.elementAt(i)); 
} 
В этом примере метод ensureCapacity используется для того, чтобы с добавлением новых
вершин емкость вектора увеличивалась не более одного раза. 
Реализация vector.toString выдает строку с полным описанием вектора, включающую
результат вызова toString для каждого из содержащихся в нем элементов. 
Помимо этих открытых методов, подклассы Vector могут использовать защищенные поля
класса. Соблюдайте осторожность в работе с ними — например, методы Vector 
предполагают, что размер буфера превышает количество элементов вектора. 
public Object elementData[] 
Буфер, в котором хранятся элементы вектора. 
public int elementCount 
Текущее количество элементов в буфере. 
public int capacityIncrement 
Количество элементов, которое добавляется к емкости вектора при заполнении буфера
elementData. Если значение этого поля равно 0, то размер буфера удваивается при
каждой необходимости его увеличения. 
Упражнение 12.1 
Напишите программу, которая открывает файл и читает из него строки (по одной), 
сохраняя каждую строку в объекте Vector, сортируемом методом String.compareTo. В этом
вам может пригодиться класс для чтения строк из файла, созданный в упражнении 11.2. 
12.5. Класс Stack 
Класс Stack расширяет Vector, добавляя методы для реализации простейшего стека
объектов Object, построенного по принципу LIFO (“последним пришел, первым вышел”). 
Метод push заносит объект в стек, а pop выталкивает верхний элемент стека. Метод peek 
возвращает значение верхнего элемента стека, при этом сам элемент остается в стеке. 
Метод empty возвращает true, если стек пуст. Попытка вызова pop или peek для пустого
объекта-стека приводит к возбуждению исключения EmptyStackException. 
converted to PDF by BoJIocЧтобы выяснить, насколько далеко расположен тот или иной элемент от вершины стека, 
применяется метод search; 1 соответствует вершине стека. Если объект не найден, 
возвращается –1. Для проверки совпадения искомого объекта с объектами в стеке
применяется метод Object.equals.
В приведенном ниже примере класс Stack используется для слежения за тем, у кого в
данный момент находится некоторый предмет — скажем, игрушка. Имя исходного
владельца попадает в стек первым. Когда кто-нибудь одалживает у него игрушку, имя
должника также заносится в стек. При возврате игрушки имя должника выталкивается из
стека. Последнее имя должно всегда оставаться в стеке, так как в противном случае
будет утрачена информация о владельце. 
import java.util.Stack; 
public class Borrow { 
 private String itemName; 
 private Stack hasIt = new Stack(); 
 public Borrow(String name, String owner) { 
 itemName = name; 
 hasIt.push(owner); // первым следует имя владельца
 } 
 public void borrow(String borrower) { 
 hasIt.push(borrower); 
 } 
 public String currentHolder() { 
 return (String)hasIt.peek(); 
 } 
 public String returnIt() { 
 String ret = (String)hasIt.pop(); 
 if (hasIt.empty()) // случайно вытолкнутый владелец
 hasIt.push(ret); // вернуть его обратно
 return ret; 
 } 
} 
Упражнение 12.2 
Добавьте метод, который использует метод search, чтобы определить количество
должников. 
12.6. Класс Dictionary 
Абстрактный класс Dictionary фактически представляет собой интерфейс. В нем
определен ряд абстрактных методов, предназначенных для хранения элемента с
некоторым ключом и последующей выборки элемента по ключу. Этот интерфейс является
базовым для класса Hashtable, однако класс Dictionary определен отдельно, чтобы другие
реализации могли использовать разные алгоритмы для сопоставления ключа с элементом. 
Класс Dictionary возвращает null, сигнализируя о таких событиях, как отсутствие элемента
с заданным ключом; следовательно, ни ключ, ни элемент не могут быть равны null. Если
задать значение null для аргумента-ключа или элемента, возбуждается исключение
NullPointerException. В случае, если вам понадобится ввести специальный элемент-
маркер, следует использовать для него значение, отличное от null. 
Класс Dictionary содержит следующие методы: 
converted to PDF by BoJIocpublic abstract Object put(Object key, Object element) 
Заносит element в словарь с ключом key. Возвращает старый элемент, хранившийся с
ключом key, или null, если такого элемента нет. 
public abstract Object get(Object key) 
Возвращает объект, занесенный в словарь с ключом key, или null, если ключ не
определен. 
public abstract Object remove(Object key) 
Удаляет из словаря элемент с ключом key и возвращает значение удаленного элемента
или null, если ключ не определен. 
public abstract int size() 
Возвращает количество элементов в словаре. 
public abstract boolean isEmpty() 
Возвращает true, если словарь не содержит ни одного элемента. 
public abstract Enumeration keys() 
Возвращает объект-перечисление для всех ключей, входящих в словарь. 
public abstract Enumeration elements() 
Возвращает объект-перечисление для всех элементов, входящих в словарь. 
Объекты-перечисления, возвращаемые методами keys и elements, не гарантируют
фиксации исходного состояния, однако при создании класса, в котором используется
Dictionary, можно осуществить такую гарантию в вашей собственной реализации этих
методов. 
12.7. Класс Hashtable 
Хеш-таблицы представляют собой распространенный механизм для хранения пар
ключ/элемент. Они обладают такими достоинствами, как универсальность и простота, а
также высокая эффективность при хорошо продуманной генерации хеш-кода. Класс
Hashtable реализует интерфейс Dictionary. Он обладает определенной емкостью и
средствами, определяющими момент увеличения таблицы. Расширение хеш-таблицы
требует повторного хеширования всех ее элементов в соответствии с их новым
положением в увеличенной таблице, так что важно обеспечить однократное изменение
таблицы. 
Другой фактор, влияющий на эффективность хеш-таблицы, — процесс генерации хеш-
кода по ключу. Конфликты хеш-кодов должны происходить как можно реже. Хеш-коды
обязаны равномерно распределяться по диапазону возможных значений, который для
класса Hashtable совпадает с полным диапазоном типа int. Если различные ключи часто
приводят к одним и тем же хеш-кодам, то некоторая часть хеш-таблицы быстро
переполнится, в результате чего пострадает эффективность. 
Значение хеш-кода возвращается методом hashCode для объекта, являющегося ключом. 
По умолчанию каждый объект имеет уникальный хеш-код. Использование в качестве
ключей случайно выбранных объектов приводит к порождению различных хеш-кодов. 
Классы String, BitSet и большинство других, переопределяющих метод equal, обычно
переопределяют и hashCode. Это важно, поскольку класс Hashtable использует хеш-код
converted to PDF by BoJIocдля нахождения набора ключей, которые могут совпадать с заданным, и вызывает equal 
для каждого из таких объектов, пока не будет найден совпадающий. Если для некоторых
объектов equal и hashCode окажутся несовместимыми, то при использовании объектов
этого типа в качестве ключей Hastable их поведение окажется непредсказуемым.
Пример использования класса Hastable приведен в классе Attributed Impl (см. раздел
“Реализация интерфейсов”), в котором объект Hashtable использован для хранения
атрибутов объекта. В этом примере ключами являются строковые объекты, 
представляющие собой имена атрибутов, а объекты Attr были значениями атрибутов. 
Кроме методов, входящих в класс Dictionary (get, put, remove, size, isEmpty, keys и
elements), Hastable содержит следующие методы: 
public synchronized boolean containsKey(Object key) 
Возвращает true, если хеш-таблица содержит элемент с заданным ключом. 
public synchronized boolean contains(Object element) 
Возвращает true, если заданный element является элементом хеш-таблицы. Данная
операция является более сложной, чем метод containsKey, поскольку хеш-таблица
спроектирована с расчетом на эффективный поиск ключей, а не элементов. 
public synchronized void clear() 
Делает хеш-таблицу пустой. 
public synchronized Object clone() 
Создает дубликат хеш-таблицы. Ключи и элементы при этом не дублируются. 
Объекты Hashtable автоматически увеличиваются, когда они становятся слишком
заполненными. Под выражением “слишком заполненными” понимается превышение
показателя загрузки таблицы, который представляет собой отношение количества
элементов к текущей емкости таблицы. Когда таблица увеличивается, ее новая емкость
примерно вдвое превышает текущую. Для повышения эффективности следует выбирать
емкость, представленную простым числом, чтобы при увеличении объекта Hastable также
было выбрано ближайшее простое число. Исходная емкость хеш-таблицы и показатель
загрузки могут задаваться в конструкторах Hashtable: 
public Hashtable() 
Конструирует новую, пустую хеш-таблицу с принятой по умолчанию исходной емкостью и
показателем загрузки, равным 0, 75. 
public Hashtable(int initialCapacity) 
Конструирует новую, пустую хеш-таблицу с заданной емкостью initial Capacity и принятым
по умолчанию показателем загрузки, равным 0,75. 
public Hashtable(int initialCapacity, float loadFactor) 
Конструирует новую, пустую хеш-таблицу с заданной емкостью и показателем загрузки
loadFactor, который представляет собой число, лежащее в диапазоне 0,0–1,0 и
определяющее момент увеличения хеш-таблицы. Если количество элементов хеш-
таблицы превышает текущую емкость, умноженную на показатель загрузки, то хеш-
таблица автоматически увеличивается. 
converted to PDF by BoJIocЕмкость по умолчанию выбирается “разумной”, причем критерий разумности зависит от
реализации. После конструирования объекта Hashtable невозможно изменить показатель
загрузки или явно задать новую емкость.
При увеличении объекта Hashtable повторное хеширование осуществляется методом
rehash. Метод rehash является защищенным, так что расширенные классы могут вызывать
его по своему усмотрению, когда они решат, что наступило время увеличить емкость
таблицы. Задать новый размер при этом невозможно — он всегда вычисляется методом
rehash. 
При реализации метод Hashtable.toString возвращает строку, которая полностью
описывает содержимое таблицы, включая результаты вызова to String для всех ключей и
элементов, входящих в нее. 
Упражнение 12.3 
В классе WhichChars, имеется проблема с пометкой символов в верхней части диапазона
Unicode, поскольку высокие значения символов оставляют много неиспользованных битов
в нижней части диапазона. Решите эту проблему с помощью класса Hashtable, сохраняя
объект Character для каждого обнаруженного символа. Не забудьте написать свой класс-
перечисление. 
Упражнение 12.4 
Теперь воспользуйтесь классом Hashtable, чтобы сохранять объект BitSet для каждого
нового старшего байта (старшие 8 бит), встречающегося во входной строке, причем
каждый BitSet должен содержать младшие байты вместе с данным старшим байтом. Не
забудьте написать свой класс-перечисление. 
Упражнение 12.5 
Напишите программу, которая пользуется объектом StreamTokenizer для разбиения
входного файла на слова и подсчета количества слов в файле, с выводом результата. 
12.8. Класс Properties 
Еще один распространенный вариант пары ключ/элемент — список свойств, состоящий из
строковых имен и связанных с ними строковых элементов. Эта разновидность словаря
часто обладает вспомогательным набором элементов по умолчанию для свойств, 
отсутствующих в таблице. Класс Properties является расширением Hashtable. Практически
для всех манипуляций со списками свойств используются методы Hashtable, однако для
получения свойств применяется один из двух методов getProperty: 
public String getProperty(String key) 
Возвращает элемент для заданного ключа key. Если ключ отсутствует в списке свойств, 
просматривается список свойств по умолчанию (если он существует). Метод возвращает
null, если свойство не найдено. 
public String getProperty(String key, String defaultElement) 
Возвращает элемент для заданного ключа key. Если ключ отсутствует в списке свойств, 
просматривается список свойств по умолчанию (если он существует). Если элемент
отсутствует в обоих списках, возвращается строка defaultElement. 
Класс Properties содержит два конструктора: один вызывается без аргументов, а второму
передается объект Properties, который представляет вспомогательный список свойств по
умолчанию. Если поиск в основном списке свойств оказывается неудачным, то
просматривается вспомогательный объект Properties, который, в свою очередь, может
converted to PDF by BoJIocиметь собственный вспомогательный объект со свойствами по умолчанию, и так далее. 
Цепочка основных и вспомогательных списков свойств может иметь произвольную длину.
public Properties() 
Создает пустой список свойств. 
public Properties(Properties defaults) 
Создает пустой список свойств с заданным вспомогательным объектом Properties для
поиска свойств, отсутствующих в основном списке. 
Если список свойств состоит только из строковых ключей и элементов, можно записывать
или считывать его из файла или иного потока ввода/вывода с помощью следующих
методов: 
public void save(OutputStream out, String header) 
Сохраняет содержимое списка свойств в OutputStream. Строка header записывается в
выходной поток в виде комментария, состоящего из одной строки. Не пользуйтесь
многострочными заголовками-комментариями, иначе сохраненный список свойств не
удастся загрузить. В файле сохраняются только свойства, входящие в основной список, 
но не во вспомогательный. 
public synchronized void load(InputStream in) throws 
IOException 
Загружает список свойств из InputStream. Предполагается, что список свойств был ранее
сохранен методом save. Метод загружает свойства только в основной список, но не во
вспомогательный. 
Для получения объекта Enumeration, представляющего собой “фотографию” ключей в
списке свойств, применяется метод propertyNames: 
public Enumeration propertyNames() 
Создает объект-перечисление с перечнем всех ключей. Метод гарантирует фиксацию
исходного состояния. 
public void list(PrintStream out) 
Выводит свойства из списка в заданный поток PrintStream. Метод полезен во время
отладки. 
После создания объекта невозможно изменить его вспомогательный перечень свойств. 
Если это все же необходимо сделать, можно создать подкласс класса Properties и
изменить значение защищенного поля defaults, содержащее список свойств по
умолчанию. 
12.9. Классы Observer/Observable 
Типы Observer/Observable предоставляют протокол, в соответствии с которым
произвольное количество объектов-наблюдателей Observer получают уведомления о
каких-либо изменениях или событиях, относящихся к произвольному количеству объектов
Observable. Объект Observable производится от подкласса Observable, благодаря чему
можно вести список объектов Observer, уведомляемых об изменениях в объекте
Observable. Все объекты- “наблюдатели”, входящие в список, должны реализовывать
интерфейс Observer. Когда с наблюдаемым объектом происходят изменения, заслужи
converted to PDF by BoJIocвающие внимания, или случаются некоторые события, которые представляют интерес для
Observer, вызывается метод notifyObservers объекта Observable, который обращается к
методу update для каждого из объектов Observer. Метод update интерфейса Observable 
выглядит следующим образом:
public abstract void update(Observable obj, Object arg) 
Метод вызывается, когда объект Observable должен сообщить наблюдателям об
изменении или некотором событии. Параметр arg дает возможность передачи
произвольного объекта, содержащего описание изменения или события в объекте
Observer. 
Механизм Observer/Observable проектировался с расчетом на универсальность. Каждый
класс Observable сам определяет, когда и при каких обстоятельствах должен вызываться
метод update объекта Observer. 
Класс Observable реализует методы для ведения списка объектов Observer, для установки
флага, сообщающего об изменении объекта, а также для вызова метода update любого из
объектов Observer. Для ведения списка объектов Observer используются следующие
методы: 
public synchronized void addObserver(Observer o) 
Добавляет аргумент o типа Observer к списку объектов-наблюдателей. 
public synchronized void deleteObserver(Observer o) 
Удаляет аргумент o типа Observer из списка объектов-наблюдателей. 
public synchronized void deleteObservers() 
Удаляет все объекты Observer из списка наблюдателей. 
public synchronized int countObservers() 
Возвращает количество объектов-наблюдателей. 
Следующие методы извещают объекты Observer о произошедших изменениях: 
public synchronized void notifyObservers(Object arg) 
Уведомляет все объекты Observer о том, что с наблюдаемым объектом что-то произошло, 
после чего сбрасывает флаг изменения объекта. Для каждого объекта-наблюдателя, 
входящего в список, вызывается его метод update, первым параметром которого является
объект Observable, а вторым — arg. 
public void notifyObservers() 
Эквивалентен notifyObservers(null). 
Приведенный ниже пример показывает, как протокол Observer/Observable может
применяться для наблюдения за пользователями, зарегистрированными в системе. 
Сначала определяется класс Users, расширяющий Observable: 
import java.util.*; 
public class Users extends Observable { 
 private Hashtable loggedIn = new Hashtable(); 
 public void login(String name, String password) 
 throws BadUserException 
 { 
converted to PDF by BoJIoc // метод возбуждает исключение BadUserException 
 if (!passwordValid(name, password) 
 throw new BadUserException(name); 
 UserState state = new UserState(name); 
 loggedIn.put(name, state); 
 setChanged(); 
 notifyObservers(state); 
 } 
 public void logout(UserState state) { 
 loggedIn.remove(state.name()); 
 setChanged(); 
 notifyObservers(state); 
 } 
 // ... 
} 
Объект Users содержит список активных пользователей и для каждого из них заводит
объект UserState. Когда кто-либо из пользователей входит в систему или прекращает
работу, то всем объектам Observer передается его объект UserState. Метод notifyObservers 
рассылает сообщения наблюдателям лишь в случае изменения состояния наблюдаемого
объекта, так что мы должны также вызвать метод setChanged для Users, иначе
notifyObservers ничего не сделает. Кроме метода setChanged, существует еще два метода
для работы с флагом изменения состояния: clearChanged помечает объект Observable как
неизменявшийся, а hasChanged возвращает логическое значение флага. 
Ниже показано, как может выглядеть реализация update для объекта Observer, постоянно
следящего за составом зарегистрированных пользователей: 
import java.util.*; 
public class Eye implements Observer { 
 Users watching; 
 public Eye(Users users) { 
 watching = users; 
 watching.addObserver(this); 
 } 
 public void update(Observable users, Object whichState) 
 { 
 if (users != watching) 
 throw new IllegalArgumentException(); 
 UserState state = (UserState)whichState; 
 if (watching.loggedIn(state)) // вход в систему
 addUser(state); // внести в список
 else 
 removeUser(state); // удалить из списка
 } 
} 
Каждый объект Eye наблюдает за конкретным объектом Users. Когда пользователь входит
в систему или прекращает работу, объект Eye извещается об этом, поскольку в его
конструкторе вызывается метод addObserver для объекта User, в котором объект Eye 
указывается в качестве объекта-наблюдателя. При вызове метода update происходит
converted to PDF by BoJIocпроверка на правильность параметров и изменение выводимой информации в
зависимости от того, вошел ли данный пользователь в систему или вышел.
Проверка того, что происходит с объектом UserState, в данном случае выполняется
просто. Впрочем, ее можно избежать — для этого следует вместо самого объекта UserState 
передавать объект-оболочку, который описывает, что и с кем происходит. Такой вариант
выглядит нагляднее и облегчает добавление новых возможностей без нарушения
существующего программного кода. 
Механизм Observer/Observable отчасти напоминает механизм wait/ notify для потоков, 
описанный на стр. , однако он отличается большей гибкостью и меньшим количеством
ограничений. Механизм потоков гарантирует, что синхронный доступ защитит программу
от нежелательных эффектов многозадачности. Механизм наблюдения позволяет
организовать между участниками любую связь, не зависящую от используемых потоков. В
обоих механизмах предусмотрен поставщик информации (Observable и объект, 
вызывающий notify) и ее потребитель (Obs e rver и объект, вызывающий wait), однако
они удовлетворяют различные потребности. Используйте wait/ notify, когда механизм
должен учитывать специфику потоков, и Observer/ Observable для более общих случаев. 
Упражнение 12.6 
Создайте реализацию интерфейса Attributed, в которой механизм Observer/Observable 
используется для уведомления наблюдателей об изменениях, происходящих c объектами. 
12.10. Класс Date 
Класс Date предоставляет в распоряжение программиста механизм для вычислений, 
связанных с датами и временем, а также для вывода их результатов (по умолчанию вывод
осуществляется в формате, используемом в Соединенных Штатах). Вы можете установить
дату и определить ее, при необходимости учитывая локальный часовой пояс. 
Предполагается, что класс Date работает в соответствии со стандартом UTC (Coordinated 
Universal Time — координированное универсальное время), однако это не всегда
возможно. Неточности возникают из-за механизмов обращения со временем, 
используемых в операционной системе. /Почти все современные системы временного
исчисления предполагают, что одни сутки состоят из 24*60*60 секунд. В системе UTC 
примерно раз в год к суткам прибавляется дополнительная секунда, называемая
"переходной". Большинство компьютерных часов не обладает необходимой точностью, 
чтобы отражать этот факт, поэтому класс Date также не учитывает его. Некоторые
компьютерные стандарты определены в GMT - это название является
общеупотребительным, тогда как UT представляет собой "научное" название того же
самого стандарта. Различие между UTC и UT состоит в том, что стандарт UT основан на
атомных часах, а UTC - на астрономических наблюдениях. На практике отличие
оказывается пренебрежимо малым. Ссылки на дополнительную информацию приведены в
разделе "Библиография"./ Компоненты дат задаются в единицах, принятых в стандарте
UTC, и принадлежат соответствующим диапазонам. Значение, выходящее за пределы
диапазона, интерпретируется правильно — например, 32 января эквивалентно 1 февраля. 
Диапазоны определяются следующим образом: 
год год после 1900, со всеми цифрами
месяц 0–11 
дата день месяца, 1–31 
час 0–23 
минуты 0–59 
converted to PDF by BoJIocсекунды 0–61 (с учетом переходной секунды).
Класс Date прост в использовании, но содержит много методов: 
public Date() 
Создает объект Date, соответствующий текущей дате/времени. 
public int Date(int year, int month, int date, int hrs, int min, sec) 
Создает объект Date, соответствующий заданной дате/времени. 
public Date(int year, int month, int date, int hrs, int min) 
Эквивалентно Date(year, month, date, hrs, min, 0), то есть началу текущей минуты. 
public Date(int year, int month, int date) 
Эквивалентно Date(year, month, date, 0, 0, 0), то есть полуночи заданной даты. 
public Date(String s) 
Создает дату из строки в соответствии с синтаксисом, принятым в методе parse 
(см. ниже). 
public static long int hrs, int min, UTC(int year, int month, int date, int sec) 
Вычисляет значение в стандарте UTC для указанной даты. 
public static long parse(String s) 
Анализирует строку, представляющую время, и возвращает полученное значение. Метод
может работать со многими форматами, но важнее всего, что он воспринимает даты в
стандарте IETF: “Sat, 12 Aug 1995 13:30:00 GMT”. Он также понимает сокращения для
часовых поясов, используемые в США, но в общем случае должно использоваться
смещение для часового пояса: “Sat, 12 Aug 1995 13:30:00 GMT+0430" (4 часа 30 минут к
западу от Гринвичского меридиана). Если часовой пояс не указан, предпо-лагается
локальный часовой пояс. При указании часового пояса стандарты GMT и UTC считаются
эквивалентными. 
public Date(long date) 
Создает объект-дату. Перед созданием объекта Date происходит нормализация полей. 
Метод воспринимает в качестве параметра значение, возвращаемое методами parse и
UTC. 
public int getYear() 
Возвращает год, всегда следующий после 1900. 
public int getMonth() 
Возвращает значение месяца в диапазоне 0–11 (с января по декабрь соответственно). 
public int getDate() 
Возвращает число месяца. 
public int getDay() 
Возвращает день недели в диапазоне 0–6 (с воскресенья до субботы соответственно). 
converted to PDF by BoJIocpublic int getHours() 
Возвращает час в диапазоне 0–23 (значение 0 соответствует полуночи). 
public int getMinutes() 
Возвращает минуты в диапазоне 0–59. 
public int getSeconds() 
Возвращает секунды в диапазоне 0–61. 
public long getTime() 
Возвращает время в формате UTC. 
public int getTimezoneOffset() 
Возвращает смещение часового пояса в минутах. Результат учитывает время суток, и на
него может влиять летнее время — если оно учитывается, то в зависимости от времени
года может присутствовать дополнительное смещение часового пояса. 
public void setYear(int year) 
Устанавливает значение года. Год должен быть после 1900. 
public void setMonth(int month) 
Устанавливает месяц. 
public void setDate(int date) 
Устанавливает число месяца. 
public void setDay(int day) 
Устанавливает день недели. 
public void setHours(int hours) 
Устанавливает час. 
public void setMinutes(int minutes) 
Устанавливает минуты. 
public void setSeconds(int seconds) 
Устанавливает секунды. 
public boolean before(Date other) 
Возвращает true, если дата объекта наступает раньше даты other. 
public boolean after(Date other) 
Возвращает true, если дата объекта наступает после даты other. 
public boolean equals(Object other) 
Возвращает true, если дата объекта представляет в стандарте UTC ту же дату, что и other. 
converted to PDF by BoJIocpublic int hashCode() 
Вычисляет хеш-код, чтобы объекты Date могли использоваться в качестве ключей в хеш-
таблицах. 
public String toString() 
Преобразует дату в String, например: “Fri Oct 13 14:33:57 EDT 1995". /Формат строки
совпадает с форматом, используемым в функции ctime в соответствии со стандартом ANSI 
C./ 
public String toLocaleString() 
Преобразует дату в String с использованием национального формата. Другими словами, 
дата будет представлена в виде, принятом в локализованной операционной системе. 
Например, жители США привыкли видеть месяц перед числом (“June 13"), тогда как в
Европе обычно используется обратный порядок (”13 June "). 
public String toGMTString() 
Преобразует дату в String с использованием конвенции Internet GMT, в форме
d mon yyyy hh:mm:ss GMT 
где d — число месяца (одна или две цифры), mon — первые три буквы месяца, yyyy — 
год из четырех цифр, hh — часы (0–23), mm — минуты, а ss — секунды. Информация о
местном часовом поясе при этом игнорируется. 
12.11. Класс Random 
Объекты класса Random предназначены для работы с независимыми
последовательностями псевдослучайных чисел. Если вам нужна последовательность типа
double и вас не интересует порядок следования чисел, можно воспользоваться методом
java.lang.Math.random — он создает объект Random при первом вызове и в дальнейшем
возвращает псевдослучайные числа из этого объекта. Чтобы иметь больше средств для
контроля за последовательностью (например, чтобы иметь возможность задать стартовое
значение), создайте объект Random и получайте числа от него. 
public Random() 
Создает новый генератор случайных чисел. Стартовое значение определяется на
основании текущего времени. 
public Random(long seed) 
Создает новый генератор случайных чисел с заданным стартовым значением. Два объекта
Random, созданные с одинаковым seed, будут порождать совпадающие
последовательности псевдослучайных чисел. 
public synchronized void setSeed(long seed) 
Устанавливает стартовое значение генератора случайных чисел равным seed. Метод
может быть вызван в любой момент — в результате произойдет сброс последовательности
и последующее ее порождение на основе стартового значения. 
public int nextInt() 
Возвращает псевдослучайное значение типа int, равномерно распределенное между
величинами Integer.MIN_VALUE и Integer.MAX_VALUE включительно. 
converted to PDF by BoJIocpublic long nextLong() 
Возвращает псевдослучайное значение типа long, равномерно распределенное между
величинами Long.MIN_VALUE и Long.MAX_VALUE включительно. 
public float nextFloat() 
Возвращает псевдослучайное значение типа float, равномерно распределенное между
величинами Float.MIN_VALUE и Float.MAX_VALUE включительно. 
public double nextDouble() 
Возвращает псевдослучайное значение типа double, равномерно распределенное между
величинами Double.MIN_VALUE и Double.MAX_VALUE включительно. 
public synchronized double nextGaussian() 
Возвращает псевдослучайное значение типа double, подчиняющееся распределению
Гаусса, с математическим ожиданием 0,0 и стандартным отклонением 1,0. 
Упражнение 12.7 
Для известного количества шестигранных кубиков можно вычислить теоретическую
вероятность выпадения каждой из возможных сумм. Например, для двух шестигранных
кубиков вероятность выпадения семи очков составляет 1/6. Напишите программу, которая
сравнивает теоретическое распределение очков для известного числа кубиков с
экспериментальными данными, полученными в результате многочисленных “бросков”, 
использующих Random для генерации чисел между 1 и 6. Имеет ли значение выбор
метода, генерирующего числа? 
Упражнение 12.8 
Напишите программу, которая тестирует метод nextGaussian, отображая распределение
для большого количества чисел в виде графика — его роль может играть гистограмма из
символов *. 
12.12. Класс String Tokenizer 
Класс StringTokenizer делит строку на части, используя для этого символы-разделители. 
Последовательность лексем, выделенных из строки, фактически представляет собой
упорядоченный объект-перечисление, поэтому класс StringTokenizer реализует интерфейс
Enumeration. Вы можете передавать объекты StringTokenizer методам, которые
обрабатывают объекты-перечисления, или воспользоваться методами Enumeration для
проведения итераций. StringTokenizer также предоставляет ряд методов с более
конкретной типизацией. Перечисление StringTokenizer не гарантирует фиксации
исходного состояния, но это не имеет значения, поскольку объекты String доступны
только для чтения. Например, для деления строки на лексемы, отделяемые запятыми и
пробелами, может использоваться следующий цикл: 
String str = "Gone, and forgotten"; 
StringTokenizer tokens = new StringTokenizer(str, " ,"); 
while (tokens.hasMoreTokens()) 
System.out.println(tokens.nextToken()); 
Запятая включена в список разделителей в конструкторе StringTokenizer для того, чтобы
анализатор “поглощал” запятые вместе с пробелами, оставляя только слова, которые
возвращаются по одному. Результат работы примера выглядит следующим образом: 
Gone 
converted to PDF by BoJIocАбстрактные методы Number возвращают значение объекта, преобразованное в один из
следующих числовых типов:
public int intValue() 
public int longValue() 
public int floatValue() 
public int doubleValue() 
Каждый класс, расширяющий Number, переопределяет эти методы для преобразования
своего типа в любой другой по тем же правилам, которые применяются при явном
приведении типов. Например, для объекта Float со значением 32.87 метод intValue 
возвращает 32 — столько же, сколько дает выражение (int)32.87. 
Кроме того, каждый числовой класс содержит следующие методы и константы: 
· Статический метод toString(тип), который возвращает объект String для заданного
значения примитивного типа. Если строка не подходит к формату данного типа, 
конструктор возбуждает исключение Number FormatException. 
· Статический метод valueOf(String), который возвращает объект числового типа по
строке. Все эти методы возбуждают NumberFormatException, если содержимое
строки не является допустимым представлением примитивного типа. 
· Статические final-константы с именами MIN_VALUE и MAX_VALUE для
минимального и максимального значения, принимаемого переменными типа. 
13.7. Класс Integer 
Класс Integer расширяет Number и служит для представления типа int в виде класса. Для
типов short и byte классы-оболочки отсутствуют, поэтому соответствующие значения
должны храниться в объектах Integer. Помимо стандартных методов класса Number, класс
Integer содержит следующие методы: 
public static int parseInt(String str, int radix) 
throws NumberFormatException 
Возвращает значение типа int, вычисленное по строке str в системе счисления с заданным
основанием radix. Если преобразование с заданным основанием невозможно, 
возбуждается исключение NumberFormat Exception. 
public static int parseInt(String str) throws NumberFormatException 
Эквивалентен parse Int(str, 10). 
public static Integer valueOf(String str, int radix) throws NumberFormatException 
Аналогично parseInt(str, Radix), за исключением того, что вместо int возвращается объект
Integer. 
public static String toString(int i, int radix) 
Дополнительный статический метод, который возвращает объект String, представляющий
i в системе счисления с заданным основанием radix. Стандартный метод toString 
предполагает работу в десятичной системе счисления. 
converted to PDF by BoJIocКроме того, имеется три статических метода toHexString, toOctal String и toBinaryString, 
которые преобразуют аргумент типа int в шестнадцатеричную, восьмеричную или
двоичную строку соответственно.
13.8. Класс Long 
Класс Long расширяет Number и служит для представления типа long в виде класса. 
Помимо стандартных для Number методов, класс Long содержит следующие методы: 
public static long parseLong(String str, int radix) throws NumberFormatException 
Возвращает значение типа long, вычисленное по строке str в системе счисления с
заданным основанием radix. Если строка не может быть преобразована в long с заданным
основанием, возбуждается исключение NumberFormatException. 
public static long parseLong(String str) throws NumberFormatException 
Эквивалентен parseLong(str, 10). 
public static Long valueOf(String str, int radix) throws NumberFormatException 
Аналогично parseLong(str, radix), за исключением того, что вместо long возвращается
объект Long. 
public static String toString(long l, int radix) 
Дополнительный статический метод, который возвращает объект String, представляющий
l в системе счисления с заданным основанием radix. Стандартный метод toString 
предполагает работу в десятичной системе счисления. 
По аналогии с эквивалентными методами Integer, статические методы toHexString, 
toOctalString и toBinaryString преобразуют аргумент типа long в шестнадцатеричную, 
восьмеричную или двоичную строку соответственно. 
13.9. Классы Float и Double 
Классы Float и Double расширяют Number и служат для представления типов float и double 
в виде класса. За редкими исключениями, имена методов и константы совпадают для
обоих типов. Приведенный ниже список соответствует классу Float, однако float и Float 
всюду могут быть заменены на double и Double соответственно, что даст эквивалентные
поля и методы для класса Double. Помимо стандартных методов класса Number, классы
Float и Double содержат следующие методы: 
public final static float POSITIVE_INFINITY 
Значение для +. 
public final static float NEGATIVE_INFINITY 
Значение для –. 
public final static float NaN 
“Не-число” (“Not-a-Number”, NaN). Константа предназначена для задания значения NaN, 
но не для его проверки. Чтобы узнать, является ли число NaN, следует воспользоваться
методом isNaN, а не сравнением с этой константой. 
public static boolean isNaN(float val) 
converted to PDF by BoJIocВозвращает true, если val представляет собой не-число (NaN). 
public static boolean isInfinite(float val) 
Возвращает true, если val представляет собой положительную или отрицательную
бесконечность. 
public boolean isNaN() 
Возвращает true, если значение текущего объекта представляет собой не-число (NaN). 
public boolean isInfinite() 
Возвращает true, если значение текущего объекта представляет собой положительную
или отрицательную бесконечность. 
Кроме перечисленных выше методов, Float также содержит конструктор, который
получает аргумент типа double. Этот аргумент используется в качестве исходного
значения после его приведения к float. 
Для манипуляций с битами внутри представления числа с плавающей точкой Double 
содержит методы для получения битовой последовательности типа long и для ее
обратного приведения к типу double. Класс Float содержит эквивалентные методы для
преобразования значения типа float в битовую последовательность типа int и наоборот: 
public static int floatToIntBits(float value) 
Возвращает битовое представление значения float в виде int. 
public static float intBitsToFloat(int Bits) 
Возвращает значение float, соответствующее заданному битовому представлению. 
public static int doubleToLongBits(double value) 
Возвращает битовое представление значения double в виде long. 
public static double longBitsToDouble(long Bits) 
Возвращает значение double, соответствующее заданному битовому представлению. 
Упражнение 13.3 
Напишите программу, которая читает файл, состоящий из элементов вида “тип значение”, 
где тип — название одного из классов (Boolean, Character и т. д.), а значение — строка, 
которую может воспринимать конструктор заданного типа. Для каждого элемента файла
создайте объект нужного типа с указанным значением и добавьте его к вектору Vector. 
Выведите окончательный результат. 
converted to PDF by BoJIocГлава 14
СИСТЕМНОЕ
ПРОГРАММИРОВАНИЕ
Глендаур: Я духов вызывать могу из бездны. 
Хотспер: И я могу, и каждый это может, 
Вопрос лишь, явятся ль они на зов. 
Вильям Шекспир, “Генрих IV”, перевод Е. Бируковой 
В этой главе рассказано, как работать с общими функциями runtime-системы Java и
операционной системы. К ним относятся: определение системных свойств, 
математические вычисления, запуск других программ, управление памятью, отсчет
времени. Эти функции предоставляются четырьмя классами Java: 
· Класс Runtime описывает состояние runtime-системы Java. Объект этого класса
отвечает за доступ к функциям времени выполнения — например, управлению
сборщиком мусора и прекращением работы. 
· Класс Process представляет выполняемый процесс, созданный вызовом метода
Runtime.exec. 
· Класс System содержит статические методы для работы с состоянием системы в
целом. Некоторые методы System оперируют с текущим runtime-контекстом. 
Класс Math содержит статические методы для выполнения многих стандартных
математических вычислений — например, для определения значения тригонометрических
функций и логарифмов. 
14.1. Стандартный поток ввода/вывода
Вы можете осуществлять стандартные операции ввода/вывода с помощью трех системных
потоков, которые являются статическими полями класса System,— System.in, System.out 
и System.err: 
public static InputStream in 
Стандартный входной поток для чтения символьных данных. 
public static OutputStream out 
Стандартный выходной поток для вывода сообщений. 
public static PrintStream err 
Стандартный поток для вывода сообщений об ошибках. Пользователи часто
перенаправляют стандартный вывод программы в файл, однако приложение при этом
должно иметь возможность вывести сообщение об ошибке так, чтобы пользователь его
увидел. Поток err предназначен для тех сообщений об ошибках, которые не
перенаправляются вместе со стандартным выводом. Потоки out и err являются объектами
класса PrintStream, поэтому для вывода сообщений в err используются те же методы, что
и для out. 
converted to PDF by BoJIoc14.2. Управление памятью
Хотя Java не позволяет явно уничтожать ненужные объекты, вы можете непосредственно
вызвать сборщик мусора, используя метод gc класса Runtime. Класс Runtime также
содержит метод runFinalization для вызова ожидающих блоков завершения (finalizers). 
Класс Runtime содержит два метода для вывода информации о состоянии памяти: 
public long freeMemory() 
Возвращает примерное количество свободных байтов системной памяти. 
public long totalMemory() 
Возвращает общее количество байтов системной памяти. 
Класс System содержит статические методы gc и runFinalization, которые вызывают
соответствующий метод для текущего runtime-контекста. 
Не исключено, что метод Runtime.gc не сможет освободить дополнительную память за
счет избавления от “мусора” — его может и не быть, к тому же не все сборщики мусора
могут находить ненужные объекты по требованию. Тем не менее перед созданием
большого количества объектов (особенно в приложениях, критических по времени, на
работе которых могут отрицательно сказаться накладные расходы по сборке мусора) все
же стоит вызвать метод gc. Он приносит двойную пользу: вы начинаете работу с
максимальным объемом свободной памяти и сокращаете вероятность вызова сборщика
мусора во время выполнения программы. Следующий метод освобождает всю возможную
память: 
public static void fullGC() { 
 Runtime rt = Runtime.getRuntime(); 
 long isFree = rt.freeMemory(); 
 long wasFree; 
 do { 
 wasFree = isFree; 
 rt.gc(); 
 isFree = rt.freeMemory(); 
 } while (isFree >> wasFree); 
 rt.runFinalization(); 
} 
Данный метод в цикле вызывает gc, при этом объем свободной памяти freeMemory 
увеличивается до определенного предела, после достижения которого дальнейшие
вызовы gc, скорее всего, ни к чему не приведут. Затем мы обращаемся к runFinalization, 
чтобы немедленно выполнить все завершающие действия, не позволяя сборщику мусора
отложить их на потом. 
Обычно вам незачем использовать runFinalization, поскольку методы finalize вызываются
сборщиком мусора асинхронно. Однако при некоторых обстоятельствах (например, при
нехватке ресурса, освобождаемого методом finalize) вынужденное исполнение всех
возможных завершающих действий способно принести пользу. Конечно, нет никакой
гарантии, что ожида-ющие завершения объекты используют данный ресурс, так что вызов
run Finalization может оказаться бесполезным. 
14.3. Системные свойства
Существует ряд системных свойств, которые хранятся внутри класса System в виде
объекта класса Properties. Они определяют системное окружение и используются
классами, которым необходима соответствующая информация. Например, приведем
распечатку свойств одной системы: 
converted to PDF by BoJIoc#System properties 
#Tue Feb 27 19:45:22 EST 1996 
java.home=/lab/east/tools/java/java 
java.version=1.0.1 
file.separator=/ 
line.separator=\n 
java.vendor=Sun Microsystem Inc. 
user.name=arnold 
os.arch=sparc 
os.name=Solaris 
java.vendor.url=http://www.sun.com/
user.dir=/vob/java_prog/src 
java.class.path=.:./classes:/home/arnold/java/lib/ 
classes.zip:/home/arnold/java/classes 
java.class.version=45.3 
os.version=2.x 
path.separator=: 
user.home=/home/arnold 
Все перечисленные выше свойства определены во всех системах, хотя их значения, 
конечно же, меняются. Некоторые из них применяются стандартными пакетами Java. 
Например, класс File использует свойство file.separator для построения и анализа путей к
файлам. Программисты тоже могут задействовать эти свойства. Следующий метод ищет
файл конфигурации в личной папке пользователя: 
public static File personalConfig(String fileName) { 
 String home = System.getPropety("user.home"); 
 if (home == null) 
 return null; 
 else 
 return new File(home, fileName); 
} 
Ниже перечислены все методы класса System, которые служат для работы с системными
свойствами: 
public static Properties getProperties() 
Возвращает объект класса Properties, представляющий системные свойства. 
public static void setProperties(Properties props) 
Задает системные свойства, используя для этого заданный объект класса Properties. 
public static String getProperty(String key) 
Возвращает текущее значение системного свойства, заданного в виде строки key. 
Эквивалентно
System.getProperties().getProperty(key); 
public static String getProperty(String key, 
String defaultValue) 
Возвращает текущее значение системного свойства, заданного в виде строки key. Если
оно не определено, возвращается defaultValue. Эквивалентно
System.getProperties().getProperty(key, def); 
converted to PDF by BoJIocЗначения свойств хранятся в виде строк, однако некоторые строки представляют другие
типы — например, целые или логические. Существуют специальные методы, которые
являются статическими методами соответствующих классов-оболочек, для чтения свойств
и преобразования их в значения примитивных типов. Каждый из таких методов получает
строковый параметр с именем свойства, интересующего программиста. Некоторые методы
имеют и второй параметр (обозначенный ниже как def) со значением по умолчанию, 
которое возвращается в том случае, если свойство с данным именем не найдено. Методы, 
в которых этот параметр отсутствует, в этом случае возвращают объект, содержащий 0 
для числового типа или false — для логического. Все эти методы преобразуют значения в
стандартный для Java формат примитивного типа. 
public static boolean Boolean.getBoolean(String name) 
public static Integer Integer.getInteger(String name) 
public static Integer Integer.getInteger(String name, int def) 
public static Long Long.getLong(String nm) 
public static Long Long.getLong(String nm, long def) 
Метод getBoolean отличается от других тем, что он возвращает логическое значение
(boolean) вместо объекта класса Boolean. Если свойство не найдено, getBoolean передает
false. 
14.4. Создание процессов
Как упоминалось выше, в программах Java могут одновременно выполняться несколько
потоков. Большинство систем, на которых функционирует среда Java, также
поддерживают запуск нескольких программ. Приложения Java могут вызывать новые
программы, обращаясь к одной из двух форм метода System.exec. Каждый успешный
вызов exec создает новый объект Process, который представляет собой работающую
программу. Вы можете запросить информацию о состоянии процесса и вызвать методы, 
управляющие его ходом. Существуют две основные формы метода exec: 
public Process exec(String[] cmdarray) throws IOException 
Выполняет в текущей системе команду, заданную в объекте cmdarray, и возвращает
объект Process (описанный ниже), который представляет запущенный процесс. В
cmdarray[0] задается имя команды, а во всех последующих строках массива — аргументы. 
public Process exec(String command) throws IOException 
Эквивалентен первой форме exec, в которой элементы массива собраны в одну строку и
разделены символами-разделителями. Приведем пример использования этой формы exec: 
String cmd = “/bin/ls” + opts + “ ” + dir; 
Process child = Runtime.getRuntime().exec(cmd); 
Порожденный процесс носит название процесса-потомка (child process ). По аналогии, 
создающий процесс называется родителем. Метод exec возвращает объект Process для
каждого запущенного процесса-потомка. Этот объект обладает двумя важными аспектами
по отношению к процессу-потомку: во-первых, он содержит методы для обращения ко
входному и выходному потоку процесса-потомка, а также к его потоку ошибок: 
public abstract OutputStream getOutputStream() 
converted to PDF by BoJIocИсключение CloneNotSupportedException непосредственно порождается от класса
Exception, поскольку каждая программа, которая вызывает метод clone, возбуждающий
данное исключение, должна явным образом его обработать. Оно рассмотрено в разделе
“Дублирование объектов. 
Настоящая глава делится на две части — одна посвящена классам RuntimeException, а
другая — классам Error. Для каждого исключения приводится его значение, описание
ситуации, в которой оно возбуждается, а также все дополнительные конструкторы. 
Б.1 Классы RuntimeException 
ArithmeticException extends RuntimeException 
Возникла исключительная ситуация во время вычислений (например, 
деление целого числа на ноль). 
ArrayStoreException extends RuntimeException 
Попытка сохранения в массиве объекта неверного типа. 
ClassCastException extends RuntimeException 
Попытка недопустимого приведения типа. 
IllegalArgumentException extends RuntimeException 
Метод получил неверный аргумент (например, метод String.equals вызван
для объекта, который не относится к типу String). 
IllegalMonitorStateException extends RuntimeException 
Механизм wait/notify использован за пределами синхронного кода. 
IllegalThreadStateException extends IllegalArgumentException 
Состояние потока не допускает выполнения требуемой операции. 
IndexOutOfBoundsException extends RuntimeException 
Runtime-система генерирует это исключение при выходе индекса массива
или объекта String за пределы диапазона допустимых значений. 
NegativeArraySizeException extends RuntimeException 
Попытка создания массива отрицательного размера. 
NullPointerException extends RuntimeException 
Для доступа к полю или методу использована null-ссылка. Это же
исключение сигнализирует о передаче методу параметра null, если для
данного параметра это значение является недопустимым. Используется
аналогично IllegalArgumentException. 
NumberFormatException extends IllegalArgumentException 
Неверное содержимое строки, в которой должно было находиться число. 
Исключение возбуждается такими методами, как Integer.parseInt. 
SecurityException extends RuntimeException 
converted to PDF by BoJIocПопытка выполнения действия, запрещенного системой безопасности — 
обычно объектом SecurityManager для текущего runtime-контекста. 
Б.2 Классы Error 
AbstractMethodError extends IncompatibleClassChangeError 
Вызван абстрактный метод. Это может произойти лишь в очень редких
случаях, так что вы никогда не столкнетесь с этим исключением. 
ClassFormatError extends LinkageError 
Загружаемый класс или интерфейс имеет неверный формат (обычно это
связано с использованием “преобразованных” (mangled) имен). 
IllegalAccessError extends IncompatibleClassChangeError 
Исключение неразрешенного доступа. 
IncompatibleClassChangeError extends linkageError 
При загрузке класса или интерфейса было обнаружено изменение, 
несовместимое с информацией об этом классе или интерфейсе. Например, 
в период времени между компиляцией класса и компиляцией
использующей его программы, из класса был удален незакрытый метод. 
InstantiationError extends IncompatibleClassChangeError 
Интерпретатор попытался создать объект абстрактного класса или
интерфейса. 
InternalError extends VirtualMachineError 
Произошел внутренний сбой runtime-системы. В нормальных условиях
такая ошибка не должна возникнуть. 
LinkageError extends Error 
Исключения класса LinkageError и его подклассов означают, что класс тем
или иным образом зависит от другого класса и что связь между ними не
может быть установлена. 
NoClassDefFoundError extends LinkageError 
Нужный класс не найден. 
NoSuchFieldError extends IncompatibleClassChangeError 
Поле отсутствует в классе или интерфейсе. 
NoSuchMethodError extends IncompatibleClassChangeError 
Метод отсутствует в классе или интерфейсе. 
OutOfMemoryError extends VirtualMachineError 
Нехватка памяти. 
StackOverflowError extends VirtualMachineError 
converted to PDF by BoJIocПереполнение стека. Может свидетельствовать о бесконечной рекурсии. 
ThreadDeath extends Error 
Исключение ThreadDeath возбуждается потоком-“жертвой” при его
уничтожении методом thread.stop. Если исключение ThreadDeath 
перехватывается, его необходимо возбудить повторно, чтобы поток был
уничтожен. Если ThreadDeath не перехватывается, то обработчик ошибок
верхнего уровня не выводит никаких сообщений. 
UnknownError extends VirtualMachineError 
Произошла неизвестная, но серьезная ошибка. 
UnsatisfiedLinkError extends LinkageError 
Ошибка связывания внутри родного метода. Обычно это означает, что
библиотека, реализующая родной метод, содержит неопределенные
символы, которые не были найдены ни в одной библиотеке. 
VerifyError extends LinkageError 
Произошла ошибка верификации — то есть во время загрузки класс не
прошел проверку, в ходе которой обычно выясняется не нарушает ли
класс каких-нибудь требований безопасности Java. 
VirtualMachineError extends Error 
Нарушена работа виртуальной машины, или наблюдается нехватка
ресурсов. 
converted to PDF by BoJIocПриложение В
Полезные таблицы
Таблица 1. Ключевые слова
abstract double int super 
boolean else interface switch 
break extends long synchronized 
byte final native this 
case finally new throw 
catch float package throws 
char for private transient† 
class goto† protected try 
const† if public void 
continue implements return volatile 
default import short while 
do instanceof static 
Ключевые слова, помеченные символом †, в настоящее время не используются
Таблица 2. Специальные символы, содержащие \ 
Последовательность Значение 
\n переход на новую строку (\u000A) 
\t табуляция (\u0009) 
\b забой (\u0008) 
\r ввод (\u000D) 
\f подача листа (\u000C) 
\\ обратная косая черта (\u005C) 
\’ апостроф (\u0027) 
\" кавычка (\u0022) 
\ddd 
символ в восьмеричном представлении, где каждое d соответствует
восьмеричной цифре от 0 до 7 
\uddd 
символ Unicode, где каждое d соответствует шестнадцатеричной
цифре (0–9, a–f, A–F) 
converted to PDF by BoJIocТаблица 3. Приоритет операторов
постфиксные операторы [] . (параметры) expr++ expr-- 
унарные операторы ++expr --expr +expr -expr ~ ! 
создание и преобразование
типа 
new (тип)expr 
операторы умножения/деления * / % 
операторы сложения/вычитания + - 
операторы сдвига <<<< >>>> >>>>>> 
операторы отношения << >> >>= <<= instanceof 
операторы равенства == != 
поразрядное И & 
поразрядное исключающее ИЛИ ^ 
поразрядное включающее ИЛИ | 
логическое И && 
логическое ИЛИ || 
условный оператор ?: 
операторы присвоения 
= += -= *= /= %= >>>>= <<<<= >>>>>>= &= ^= 
|= 
Таблица 4. Цифры Unicode 
Unicode Описание 
\u0030–\u0039 Цифры ISO-latin-1 (и ASCII) 
\u0660–\u0669 Арабско-индийские цифры 
\u06f0–\u06f9 Восточные арабско-индийские цифры 
\u0966–\u096f Цифры деванагари 
\u09e6–\u09ef Цифры бенгали 
\u0a66–\u0a6f Цифры гурмукхи 
\u0ae6–\u0aef Цифры гуджарати 
\u0b66–\u0b6f Цифры ория 
\u0be7–\u0bef Тамильские цифры (только девять — без нуля) 
\u0c66–\u0c6f Цифры телугу 
\u0ce6–\u0cef Цифры каннада 
converted to PDF by BoJIoc\u0d66–\u0d6f Малайские цифры 
\u0e50–\u0e59 Тайские цифры 
\u0ed0–\u0ed9 Цифры лао 
\uff10–\uff19 Цифры полной ширины 
Таблица 5. Буквы и цифры Unicode 
\u0041–
\u005a 
Буквы верхнего регистра ISO-latin-1 и ASCII ('A'–'Z') 
\u0061–
\u007a 
Буквы нижнего регистра ISO-latin-1 и ASCII ('a'–'z') 
\u00c0–
\u00d6 
Дополнительные буквы ISO-latin-1 
\u00d8–
\u00f6 
Дополнительные буквы ISO-latin-1 
\u00f8–
\u00ff 
Дополнительные буквы ISO-latin-1 
\u0100–
\u1fff 
Расширенная кодировка Latin-A, расширенная кодировка Latin-B, расширения
IPA, буквы-модификаторы интервалов, диакритические знаки, базовый
греческий алфавит, греческий и коптский алфавиты, кириллица, армянский, 
иврит расширенный-A, базовый иврит, иврит расширенный-B, базовый
арабский, расширенный арабский, деванагари, бенгали, гурмукхи, гуджарати, 
ория, тамильский, телугу, каннада, малайский, тайский, лао, базовый
грузинский, расширенный грузинский, хангульский, латинский расширенный
дополнительный, греческий расширенный 
\u3040–
\u9fff 
Хирагана, катакана, бопомофо, хангульский совместимый, CJK, символы и
месяцы CJK, CJK совместимый, хангульский, хангульский дополнительный-A, 
хангульский дополнительный-B, единые идеографы CJK 
\uf900–
\ufdff 
Совместимые идеографы CJK, алфавитные формы, арабские презентационные
формы-A 
\ufe70–
\ufefe 
Арабские презентационные формы-B 
\uff10–
\uff19 
Цифры полной ширины 
\uff21–
\uff3a 
Латинский полной ширины, верхний регистр 
\uff41–
\uff5a 
Латинский полной ширины, нижний регистр 
\uff66–
\uffdc 
Катакана и хангульский половинной ширины 
Примечание: Символ Unicode является буквой или цифрой, если он принадлежит одному
из диапазонов, содержащихся в таблице, и также определен как символ Unicode. 
Примечание: Символ Unicode является буквой, если он присутствует в таблице “Буквы и
цифры Unicode”, но отсутствует в таблице “Цифры Unicode”. 
Таблица 6. Java 1.0 и Java 1.0.2: Отличия между Java 1.0 и Java 1.0.2, 
существенные для данной книги (с разделами, к которым они относятся) 
converted to PDF by BoJIoc· Константы MIN_VALUE и MAX_VALUE класса Character в Java 1.0 ошибочно
присутствовали в классе Boolean. См. раздел 13.5. 
· В классах String и Character в Java 1.0 некоторые свойства символов
(принадлежность к верхнему/нижнему регистру, цифрам и т.д.) определялись
только для подмножества символов Unicode, принадлежащего к набору ISO-Latin-1 
(с \u0000 по \u00ff); все символы за пределами этого диапазона считались
буквами без регистра. Кроме того, отсутствовали методы класса Character, 
возвращавшие сведение о классе символа помимо принадлежности к верхнему
или нижнему регистру (например, методы заглавного регистра и isLetter). См. 
раздел 8.2, раздел 8.4 и раздел 13.5. 
· Java 1.0 не гарантирует, что литералам String с одинаковыми значениями
соответствуют одинаковые ссылки, хотя иногда это было так. См. раздел 8.2. 
· Список букв и цифр, используемых в идентификаторах Java 1.0, несколько
отличается от списка Java 1.0.2 за пределами диапазона ISO-Latin-1. См. табл. 4 и
табл. 5. 
· В Java разрешалась (и даже ошибочно наделялась смыслом) комбинация
ключевых слов private protected. 
· Классы-оболочки Integer и Long в Java 1.0 не содержали метод