Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

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

.pdf
Скачиваний:
160
Добавлен:
02.05.2014
Размер:
3.59 Mб
Скачать

--декремент

Вот примеры выражений с унарными знаками операций:

i++

-i

~i

Унарные минус и плюс

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

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

byte i=-128 System.out.println(-i);

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

Побитовое дополнение

Знак операции побитового дополнения применяется только к целым типам. Эта операция интерпретирует целое число как набор битов и меняет в этом наборе все нули на единицы, а все единицы на нули. Применив этот знак операции к числу x, вы получите в результате число (-x)- 1. Те, кому не доводилось работать с данными на уровне битов, возможно, найдут эту операцию непривычной для себя. Область ее применения - те задачи, в которых нас не интересует числовое значение целой переменной: мы используем эту переменную просто как набор отдельных битов. Например, число типа short, равное нулю (0x0000), после операции побитового дополнения превращается в -1 (0xFFFF).

Знаки операций инкремента и декремента

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

Знак операции в префиксной записи возвращает значение своего операнда после вычисления выражения. При постфиксной записи знак операции сначала возвращает значение своего операнда и только после этого вычисляет инкремент или декремент. Рассмотрим фрагмент кода, иллюстрирующий эту разницу:

int i=0; int j=0;

System.out.println(++i);

System.out.println(j++);

На выходе этой программы вы получите сначала 1, а затем 0. В первом операторе печати мы использовали префиксную запись операции, при которой переменная i сначала инкрементируется, а затем ее значение выводится на печать. Во втором случае переменная j сначала выводится на печать, а затем инкрементируется. В обоих случаях после обоих операторов печати значением как i, так и j будет единица.

Бинарные знаки операций

Ⱦɚɧɧɚɹ ɜɟɪɫɢɹ ɤɧɢɝɢ ɜɵɩɭɳɟɧɚ ɷɥɟɤɬɪɨɧɧɵɦ ɢɡɞɚɬɟɥɶɫɬɜɨɦ %RRNV VKRS Ɋɚɫɩɪɨɫɬɪɚɧɟɧɢɟ ɩɪɨɞɚɠɚ ɩɟɪɟɡɚɩɢɫɶ ɞɚɧɧɨɣ ɤɧɢɝɢ ɢɥɢ ɟɟ ɱɚɫɬɟɣ ɁȺɉɊȿɓȿɇɕ Ɉ ɜɫɟɯ ɧɚɪɭɲɟɧɢɹɯ ɩɪɨɫɶɛɚ ɫɨɨɛɳɚɬɶ ɩɨ ɚɞɪɟɫɭ piracy@books-shop.com

Бинарные знаки операции имеют два операнда и возвращают некий результат. Как уже упоминалось выше, результат будет иметь тот же тип, что и больший из операндов - например, сложение целых чисел типа byte и типа int даст в результате тип int. Сами операнды при этой операции не изменяются. Все бинарные знаки операций можно разделить на те, что вычисляют некий числовой результат, и те, что предназначены для сравнения операндов. Знаки операций, возвращающие числовой результат, приведены в табл. 4-8.

Таблица 4-8. Бинарные знаки операций, возвращающие числовые значения

Знак операции Описание

++сложение

-вычитание

*умножение

/деление

% остаток от деления

&побитовое И

| побитовое ИЛИ

^побитовое исключающее ИЛИ

<< побитовый сдвиг влево с учетом знака >> побитовый сдвиг вправо с учетом знака

>>>побитовый сдвиг вправо без учета знака

op= комбинация присваивания и одного из знаков операций

Сложение и вычитание

Если один из операндов операций + или - является числом с плавающей точкой, то перед выполнением операции оба операнда преобразуются в числа с плавающей точкой. Все целые типы, кроме типа long, при сложении и вычитании приводятся к типу int - иными словами, операции + и - не могут возвращать значения типа byte или short. Чтобы присвоить результат операции переменной одного из этих типов, вы должны будете прибегнуть к явному приведению типа.

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

Умножение, деление и нахождение остатка

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

float one = lf;

float max = 2^24e104;

После этого, если мы вычислим значение выражения one + max - one == max - one + one

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

Операция нахождения остатка от деления (%), или деление по модулю, определена следующим образом: (a/b)*b + (a%b) = a. Эта операция возвращает положительный результат, если делимое положительно, и отрицательный результат в случае отрицательного операнда. Результат этой операции по абсолютному значению всегда меньше делителя.

Вот пример использования операции нахождения остатка:

www.books-shop.com

int i=10; int j=4; int k=-4;

System.out.println(i % j); // =2 System.out.println(i % k); // = -2

СОВЕТ Поведение операции нахождения остатка в Java отличается от требований стандарта IEEE 754. Однако вы можете пользоваться другой операцией, определенной в полном соответствии с этим стандартом. Эта операция определена в библиотеке math под названием

Math.IEEEremainder.

Побитовые знаки операций

К побитовым знакам операций относятся те, что оперируют с числами в их битовом представлении. Эти операции применяются только к целочисленным значениям. Поскольку в числах с плавающей точкой битовые цепочки кодируют не само число, а его особое математическое представление, применение побитовых операций к таких числам не имеет смысла. Как правило, побитовые операции логического И (&), логического ИЛИ (|) и исключающего ИЛИ (^) используются для изменения и получения значения отдельных битов числа.

Представьте, что каждый бит целого числа хранит в себе какой-то флаг. Работая с целыми числами типа byte, мы можем хранить в них восемь таких флагов, по одному на каждый бит. Ниже показаны некоторые распространенные манипуляции с флагами, упакованными в целом числе:

byte flags=0xff; // исходное значение флагов 11111111 byte mask=0xfe; // битовая маска 11111110

flads = flags & mask; // установить флаг номер 8 flads = flags | mask; // сбросить флаг номер 8 flads = flags ^ mask; // = 00000001

Вниманию пользователей C/C++

Язык Java не поддерживает битовые поля. Чтобы получить доступ к отдельным битам,, вы должны пользоваться либо битовыми масками,, либо средствами класса java.util.BitSet.

Знак операции сдвига

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

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

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

int i = 129; // в двоичном представлении это число равно 10000001 i = i >> 1; // теперь мы имеем 1000000, или 64

Комбинированные знаки операций

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

Программист должен отдавать себе отчет в том, как происходит загрузка операндов в

www.books-shop.com

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

int i = 0; i += ++i;

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

Знаки операций сравнения

Помимо знаков операций, производящих вычисления с числами, в Java есть группа знаков операций, предназначенных для сравнения двух значений. Эти операции так и называются - операции сравнения. Они имеют по два параметра и возвращают булевское значение, соответствующее результату сравнения. Знаки операций сравнения языка Java перечислены в табл. 4-9.

Таблица 4-9. Знаки операций сравнения

Знак операции Описание

< меньше чем

>больше чем

<= меньше или равно >= больше или равно

==равно

!=

не равно

СОВЕТ Существует одна очень распространенная ошибка, связанная с использованием операций сравнения. Многие программисты, особенно начинающие, пытаются использовать в качестве знака операции проверки равенства одиночный, а не двойной знак равенства. Помните, что одиночный знак равенства используется только в операции присваивания, а в операции сравнения нужно использовать двойной символ знака равенства. Это соглашение заимствовано из C/C++, и хотя компилятор Java, в отличие от этих языков, способен сам обнаружить такую ошибку, разумнее иметь в виду этот момент заранее.

Булевские знаки операций

Булевские знаки операций аналогичны соответствующим знакам операций для чисел. Все булевские операции возвращают значения типа boolean. Список булевских знаков операций языка Java приведен в табл. 4-10.

Таблица 4-10. Булевские знаки операций

Знак операции Описание

!отрицание

&логическое И

| логическое ИЛИ ^ исключающее ИЛИ

&&условное И

||условное ИЛИ

==равно

!=

не равно

op=

комбинация оператора присваивания и одной из операций

www.books-shop.com

?: условный оператор

Условный оператор - это единственная операция языка Java, имеющая три операнда. Этот оператор записывается в форме a?b:c. При этом сначала вычисляется выражение a, которое должно дать булевское значение, а затем, в соответствии с полученным результатом, возвращается либо b, если a имеет значение true, либо c, если a имеет значение false. По своей функции этот оператор аналогичен оператору if:

int i;

boolean cont=false;

//обычный оператор if if (cont) i=5; *

else i=6;

//сокращенная запись с помощью условного оператора i = (cont?5:6);

Вэтом фрагменте кода переменной i присваивается значение либо 5, либо 6 в зависимости от того, чему равна булевская переменная cont. Если cont имеет значение true, i получает значение 5; в обратном случае i получает значение 6. Условный оператор позволяет достичь этого результата быстрее и удобнее.

Знаки операций над символьными значениями

Особых знаков операций, возвращающих символьные значения, в языке Java не существует. Большинство знаков операций, которые мы обсуждали выше, возвращают целочисленные значения. Если вам потребуется применить эти знаки операций к символьным значениям, вы должны будете явным образом привести результат этих операций обратно к типу char. Если одним из операндов знака операции является символ, он перед выполнением операции приводится к типу int. Очевидно, что это преобразование никогда не приведет к потере информации.

Предположим, что нам требуется написать код для перевода символа из верхнего в нижний регистр. Воспользуемся тем, что код заглавной буквы A (как в ASCII, так и в Unicode) равен 98, а код строчной буквы a равен 65. Если мы возьмем любую заглавную букву и вычтем из ее кода разницу между кодами заглавной и строчной букв A, то в результате мы получим код, соответствующий строчной букве. Проиллюстрируем этот принцип примером:

char c='B';

c = (char) c - ('A' - 'a');

Знаки операций над объектами

Объекты в языке Java могут объединяться с помощью следующих знаков операций: =, ==, !=, instanceof. Как правило, все остальные операции, такие как сложение, не имеют никакого смысла с объектами. Исключением служит особый случай сложения двух строк.

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

foo test = new foo(); foo test2 = null; test2 = test;

В этом примере показаны допустимые варианты потребления операторов присваивания с объектами. В первой строке оператор присваивания объединен с созданием нового экземпляра объекта. Во второй строке создаваемой ссылочной переменной test2 присваивается null (нулевой указатель). Это означает, что теперь test2 не указывает ни на какой объект и любая попытка использовать эту переменную приведет к возникновению исключительной ситуации NullPointerException. В последней строке значение переменной test присваивается переменной test2. Теперь эти две переменные указывают на один и тот же экземпляр объекта.

К объектам применимы две операции сравнения: проверка на равенство (==) и на

www.books-shop.com

неравенство (!=). Фактически эти операции проверяют равенство не объектов, а указателей на них, то есть возвращают свое значение в зависимости от того, указывают ли сравниваемые переменные на один и тот же объект в памяти. Никакого сравнения отдельных компонентов объекта при этом не производится. Это значит, что два объекта с одним и тем же содержимым, но являющиеся разными экземплярами класса, не будут равны друг другу с точки зрения операции сравнения. Пусть, например, мы имеем два экземпляра класса foo, определенные следующим образом:

foo test = new foo(); foo test2 = new foo(); foo test3 = test;

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

Таблица 4-11. Равенство объектов test test2 test3

test == != == test2 != == != test3 == != ==

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

shape shapeHolder;

if (shapeHolder instanceof polygonShape) {

polygonShape polygon = (polygonShape) shapeHolder;

//объект является многоугольником, и с ним можно производить соответствующие

//действия

...

}

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

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

Операции над строками

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

www.books-shop.com

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

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

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

Таблица 4-12. Правила преобразования нестроковых значений в строки для конкатенации

Операнд

Правило

Пустая

Любая ссылочная переменная, не указывающая ни на какой объект,

переменная

преобразуется в строку "null".

Целочисленное

Преобразуется в строку, содержащую десятичное представление данного

значение

целого числа, возможно, со знаком минус впереди. Возвращаемое значение

 

никогда не начинается с символа "0" за единственным исключением: если

 

преобразуемое значение равно 0, возвращается строка из одного символа "0".

Значение с

Преобразуется в строку, представляющую данное число в компактной записи.

плавающей

Это значит, что если представление числа занимает более десяти символов,

точкой

число будет преобразовано в экспоненциальную форму. Для отрицательных

 

чисел возвращаемая строка начинается со знака минус.

Одиночный

Преобразуется в эквивалентную строку длиной в один символ.

символ

 

Булевское

Преобразуется в одну из двух строк символов - "true" или "false", в

значение

зависимости от преобразуемого значения.

Объект

Для преобразования в строку вызывается метод toString(), определенный в

 

данном объекте.

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

String foo = "Hello "; String bar = "World"; int i = 42;

boolean cont = false; String result = null;

result = foo + bar; // = "Hello World" result = foo + i; // = "Hello 42" result = foo + cont; // = "Hello false"

Как видите, использование знака операции плюс со строками весьма удобно. Однако зададимся вопросом: если знак операции плюс имеет со строками такое значение, то что должен в аналогичной ситуации делать знак операции минус? Ответ прост - он не делает ничего. А как насчет операций сравнения == и !=? Давайте проведем такой эксперимент:

String foo = "Hello"; String bar = "Hello";

if (foo == bar) System.out.println ("Равно"); else System.out.println ("Не равно");

www.books-shop.com

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

class testString {

String st = "Hello";

}

class testString2 {

String st = "Hello"; String st2 = "Hello";

public static void main(String args[]) { testString test = new testString(); testString2 test2 = new testString2();

if (test.st == test.st2) System.out.println ("Равно"); else System.out.println ("Не равно");

if (test.st == test2.st) System.out.println ("Равно"); else System.out.println ("Не равно");

}

}

На сей раз результат может показаться неожиданным. Первое сравнение дает результат "Равно", а второе - "Не равно". Дело здесь в том, что компилятор в подобных случаях производит оптимизацию кода с целью уменьшения занимаемой программой памяти. В частности, переменные st и st2, объявленные в пределах одного класса, на самом деле указывают на один и тот же экземпляр объекта в памяти - увидев, что вы заводите две одинаковые строки, компилятор решает сэкономить и поместить в код только один экземпляр этой строки, на который будут ссылаться две строковые переменные. Вот почему, оказывается, операция сравнения со строками иногда работает правильно, а иногда - нет.

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

public static void main(String args[]) { testString test = new testString();

testString2 test2 = new testString2(); if (test.st.equals(test.st2))

System.out.println ("Равно");

else

System.out.println ("Не равно"); if (test.st.equals(test2.st))

System.out.println ("Равно");

else

System.out.println ("Не равно");

}

}

Подробнее о классе String мы будем говорить в разделе "Строки" в этой главе, а также в главе

6.

Пакеты

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

Содержимое пакета может храниться в одном или в нескольких файлах. Каждый такой файл

www.books-shop.com

должен начинаться с декларации пакета, к которому он принадлежит. В каждом файле может содержаться только один общедоступный класс. При компиляции этих файлов получающиеся в результате файлы с расширением .class будут помещены в каталоге, соответствующем имени пакета, все точки в котором заменены на символы /. Например, если нам нужно создать пакет, скомпилированные файлы которого будут размещаться в каталоге ventana/awt/shapes, то каждый из исходных файлов, входящих в этот пакет, должен начинаться со следующего объявления: package ventana.awt.shapes;

Основное назначение пакетов - создание библиотек кода. Об этом мы будем подробно говорить в главе 10, "Структура программы".

Импорт

Допустим, у нас есть пакет. Как получить доступ к входящим в него классам и интерфейсам? Один из способов - использование полного имени нужного класса. Предположим, что мы реализовали упоминавшийся выше пакет ventana.awt.shapes и что этот пакет содержит два класса - circle и rectangle. Если нам потребуется создать новый экземпляр класса circle, то это можно сделать с помощью следующего выражения:

ventana.awt.shapes.circle circ = new ventana.awt.shapes();

Однако доступ к классам через их полные имена не особенно удобен. Существует более экономный способ - использование оператора import для импортирования содержимого пакетов. Импортировав таким способом те или иные классы или интерфейсы пакета, вы получаете возможность обращаться к ним по их кратким именам, без приписывания имени пакета. Например, вот как осуществляется импорт класса circle из пакета shapes:

import ventana.awt.shapes.circle; class tryShapes {

public static void main(String args[]) { circle circ = new circle();

}

}

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

Существует способ еще более сокращенной записи оператора import. Если вы собираетесь пользоваться большим количеством классов из какого-либо пакета, запись каждого из них в операторе import потребовала бы много места и сил. Вместо этого можно пользоваться символом *, который, будучи поставлен вместо имени класса или интерфейса в операторе import, заставляет Java импортировать все классы и интерфейсы из данного пакета. Так, чтобы получить доступ ко всему содержимому пакета shapes, можно написать следующий оператор:

import ventana.awt.shapes.*;

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

Вниманию пользователей C/C++

Оператор import напоминает директиву компилятора #include, применяющуюся в C/C++. Однако важной отличительной чертой оператора import является то, что сам по себе он не генерирует кода - вы никогда не сможете увеличить размер скомпилированной программы, просто добавляя в нее операторы import. В противоположность этому директива #include эквивалентна вставке в текущий файл содержимого другого файла, который вполне может генерировать при компиляции некий код. В языке Java подобная операция невозможна в принципе - вы не можете просто копировать функции из одного файла в другой, а можете пользоваться только механизмом наследования.

Классы

www.books-shop.com

В приводимых выше примерах мы с вами уже не раз создавали новые классы. Теперь настало время познакомиться с формальным определением синтаксиса классов. Этот раздел не только познакомит вас с некоторыми новыми свойствами классов, но и будет служить справочником, к которому вы сможете обращаться по мере дальнейшего изучения языка. Если у вас вызывает затруднение понятие объекта, перечитайте главу 3, "Объектная ориентация в Java". Собственно говоря, без хорошего понимания, что такое объекты и зачем они нужны, вряд ли стоит двигаться дальше.

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

Конструкторы

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

class foo {

// конструктор без параметров

foo() {...}

foo(int n) {...}

// конструктор с одним параметром типа int

foo(String s) {...} // конструктор с одним параметром типа String

}

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

Деструкторы

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

Деструктор в языке Java должен иметь имя finalize. Он не имеет параметров и не возвращает никакого значения. Так, к приводившемуся выше в качестве примера классу foo можно добавить деструктор следующим образом:

class foo {

finalize() {...} // действия, которые нужно выполнить перед уничтожением объекта

}

Модификаторы классов

В объявлениях класса можно использовать три модификатора - abstract, final или public. Они должны располагаться перед ключевым словом class. Вот, например, как объявляется класс foo с использованием двух из этих модификаторов:

public final class foo {...}

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

www.books-shop.com