К этому моменту может сложиться впечатление, что программа — это просто «свалка» объектов со своими методами, которые принимают другие объекты в качестве аргументов и посылают им сообщения. По большому счету так оно и есть, но в следующей главе вы узнаете, как производить кропотливую низко¬уровневую работу с принятием решений внутри метода. В этой главе достаточ¬но рассмотрения на уровне посылки сообщений.
Создание программы на Java
Есть еще несколько вопросов, которые необходимо понять перед созданием первой программы на Java.
Создание программы на Java
Видимость имен
Проблема управления именами присуща любому языку программирования. Если имя используется в одном из модулей программы и оно случайно совпало с именем в другом модуле у другого программиста, то как отличить одно имя от другого и предотвратить их конфликт? В С это определенно является про¬блемой, потому что программа с трудом поддается контролю в условиях «моря» имен. Классы С-и- (на которых основаны классы Java) скрывают функции внутри классов, поэтому их имена не пересекаются с именами функций других классов. Однако в С++ дозволяется использование глобальных данных и гло¬бальных функций, соответственно, конфликты полностью не исключены. Для решения означенной проблемы в С++ введены пространства имен (namespa¬ces), которые используют дополнительные ключевые слова.
В языке Java для решения этой проблемы было использовано свежее реше¬ние. Для создания уникальных имен библиотек разработчики Java предлагают использовать доменное имя, записанное «наоборот», так как эти имена всегда уникальны. Мое доменное имя — MindView.net, и утилиты моей программной библиотеки могли бы называться net.mindview.utility.foibles. За перевернутым доменным именем следует перечень каталогов, разделенных точками.
В версиях Java 1.0 и 1.1 доменные суффиксы com, edu, org, net по умолчанию записывались заглавными буквами, таким образом, имя библиотеки выглядело так: NET.mindview.utility.foibles. В процессе разработки Java 2 было обнаружено, что принятый подход создает проблемы, и с тех пор имя пакета записывается строчными буквами.
Такой механизм значит, что все ваши файлы автоматически располагаются в своих собственных пространствах имен, и каждый класс в файле должен иметь уникальный идентификатор. Язык сам предотвращает конфликты имен.
Использование внешних компонентов
Когда вам понадобится использовать уже определенный класс в вашей про¬грамме, компилятор должен знать, как этот класс обнаружить. Конечно, класс может уже находиться в том же самом исходном файле, откуда он вызывается. В таком случае вы просто его используете — даже если определение класса сле¬дует где-то дальше в файле (В Java не существует проблемы «опережающих ссылок».)
Но что, если класс находится в каком-то внешнем файле? Казалось бы, ком¬пилятор должен запросто найти его, но здесь существует проблема. Представьте, что вам необходим класс с неким именем, для которого имеется более одного определения (вероятно, отличающихся друг от друга). Или, что еще хуже, пред¬ставьте, что вы пишете программу и при ее создании в библиотеку добавляется новый класс, конфликтующий с именем уже существующего класса.
59
Для решения проблемы вам необходимо устранить все возможные неодно¬значности. Задача решается при помощи ключевого слова import, которое гово¬рит компилятору Java, какие точно классы вам нужны. Слово import приказыва¬ет компилятору загрузить пакет (package), представляющий собой библиотеку
классов. (В других языках библиотека может состоять как из классов, так и из функций и данных, но в Java весь код принадлежит классам.)
Большую часть времени вы будете работать с компонентами из стандартных библиотек Java, поставляющихся с компилятором. Для них не нужны длинные обращенные доменные имена; вы просто записываете
import java.util ArrayList;
чтобы сказать компилятору, что вы хотите использовать класс ArrayList. Впро¬чем, пакет util содержит множество классов, и вам могут понадобиться несколь¬ко из них. Чтобы избежать последовательного перечисления классов, исполь¬зуйте подстановочный символ *:
import java util.*.
Как правило, импортируется целый набор классов именно таким образом, а не выписывается каждый класс по отдельности.
Ключевое слово static
Обычно при создании класса вы описываете, как объекты этого класса ведут себя и как они выглядят. Объект появляется только после того, как он будет создан ключевым словом new, и только начиная с этого момента для него выде¬ляется память и появляется возможность вызова методов.
Но есть две ситуации, в которых такой подход недостаточен. Первая — это когда некотороые данные должны храниться «в единственном числе» независи¬мо от того, сколько было создано объектов класса. Вторая — когда вам потребу¬ется метод, не привязанный ни к какому конкретному объекту класса (то есть метод, который можно вызвать даже при полном отсутствии объектов класса). Такой эффект достигается использованием ключевого слова static, делающего элемент класса статическим. Когда вы объявляете что-либо как static, это озна¬чает, что данные или метод не привязаны к определенному экземпляру этого класса. Поэтому, даже если вы никогда не создавали объектов класса, вы може¬те вызвать статический метод или получить доступ к статическим данным. С обычным объектом вам необходимо сначала создать его и использовать для вызова метода или доступа к информации, так как нестатические данные и ме¬тоды должны точно знать объект, с которым работают.
Некоторые объектно-ориентированные языки используют термины данные уровня класса и методы уровня класса, подразумевая, что данные и методы су¬ществуют только на уровне класса в целом, а не для отдельных объектов этого класса. Иногда эти термины встречаются в литературе по Java.
Чтобы сделать данные или метод статическими, просто поместите ключевое слово static перед их определением. Например, следующий код создает статиче¬ское поле класса и инициализирует его:
class StaticTest {
static int i =47;
}
Теперь, даже при создании двух объектов StaticTest, для элемента StaticTest.i выделяется единственный блок памяти. Оба объекта совместно используют одно значение i. Пример:
StaticTest stl = new StaticTestО;
StaticTest st2 = new StaticTestO;
В данном примере как stl.i, так и st2.i имеют одинаковые значения, равные 47, потому что расположены они в одном блоке памяти.
Существует два способа обратиться к статической переменной. Как было видно выше, вы можете указать ее с помощью объекта, например st2.i. Также можно обратиться к ней прямо по имени класса (для нестатических членов класса такая возможность отсутствует):
StaticTest i++,
Оператор ++ увеличивает значение на единицу (инкремент). После выполне¬ния этой команды значения stl.i и st2.i будут равны 48.
Синтаксис с именем класса является предпочтительным, потому что он не только подчеркивает, что переменная описана как static, но и в некоторых случаях предоставляет компилятору больше возможностей для оптимизации.
Та же логика верна и для статических методов. Вы можете обратиться к та¬кому методу или через объект, как это делается для всех методов, или в специ¬альном синтаксисе имяКласса.метод(). Статические методы определяются по ана¬логии со статическими данными:
class Incrementable {
static void increment ) { StaticTest i++; }
}
Нетрудно заметить, что метод increment() класса Incrementable увеличивает значение статического поля i. Метод можно вызвать стандартно, через объект:
Incrementable sf = new IncrementableO.
sf incrementO;
Или, поскольку increment() является статическим, можно вызвать его с пря¬мым указанием класса:
Incrementable.incrementC);