Глава 2 Thinking in Java 4th edition |
ВСЁ ЯВЛЯЕТСЯ ОБЪЕКТОМ
Хотя язык Java основан на C++, он является более «чистокровным» объектно-ориентированным языком. Как C++, так и Java относятся к семейству смешанных языков, но для создателей Java эта неоднородность была не так важна, если сравнивать с C++. Смешанный язык позволяет использовать несколько стилей программирования; причиной смешанной природы C++ стало желание сохранить совместимость с языком C. Так как язык C++ является надстройкой языка C, он включает в себя много нежелательных характеристик своего предшественника, что приводит к излишнему усложнению некоторых аспектов этого языка. Язык программирования Java подразумевает, что вы занимаетесь только объектно-ориентированным программированием. А это значит, что прежде, чем начать с ним работать, нужно «переключиться» на понятия объектно-ориентированного мира (если вы уже этого не сделали). Выгода от этого начального усилия — возможность программировать на языке, который по простоте изучения и использования превосходит все остальные языки ООП. В этой главе мы рассмотрим основные компоненты Java-программы и узнаем, что в Java (почти) все является объектом.
Для работы с объектами используются ссылки
Каждый язык программирования имеет свои средства манипуляции данными. Иногда программисту приходится быть постоянно в курсе, какая именно манипуляция производится в программе. Вы работаете с самим объектом или же с каким-то видом его косвенного представления (указатель в C или в C++), требующим особого синтаксиса? Все эти различия упрощены в Java. Вы обращаетесь со всем как с объектом, и поэтому повсюду используется единый последовательный синтаксис. Хотя вы обращаетесь со всем как с объектом, идентификатор, которым вы манипулируете, на самом деле представляет собой ссылку на объект. Представьте себе телевизор (объект) с пультом дистанционного управления (ссылка). Во время владения этой ссылкой у вас имеется связь с телевизором, но при переключении канала или уменьшении громкости вы распоряжаетесь ссылкой, которая, в свою очередь, манипулирует объектом. А если вам захочется перейти в другое место комнаты, все еще управляя телевизором, вы берете с собой «ссылку», а не сам телевизор. Также пульт может существовать сам по себе, без телевизора. Таким образом, сам факт наличия ссылки еще не означает наличия присоединенного к ней объекта. Например, для хранения слова или предложения создается ссылка String:
String s;
Однако здесь определяется только ссылка, но не объект. Если вы решите послать сообщение s, произойдет ошибка, потому что ссылка s на самом деле ни к чему не присоединена (телевизора нет). Значит, безопаснее всегда инициализировать ссылку при ее создании:
String s = "asdf";
В данном примере используется специальная возможность Java: инициализация строк текстом в кавычках. Обычно вы будете использовать более общий способ инициализации объектов.
Все объекты должны создаваться явно
Когда вы определяете ссылку, желательно присоединить ее к новому объекту. В основном это делается при помощи ключевого слова new. Фактически оно означает: «Создайте мне новый объект». В предыдущем примере можно написать:
String s = new String("asdf"):
Это не только значит «предоставьте мне новый объект String», но также указывает, как создать строку посредством передачи начального набора символов. Конечно, кроме String, в Java имеется множество готовых типов. Важнее то, что вы можете создавать свои собственные типы. Вообще говоря, именно создание новых типов станет вашим основным занятием при программировании на Java, и именно его мы будем рассматривать в книге.
Где хранятся данные
Полезно отчетливо представлять, что происходит во время работы программы — и в частности, как данные размещаются в памяти. Существует пять разных мест для хранения данных:
- Регистры. Это самое быстрое хранилище, потому что данные хранятся прямо внутри процессора. Однако количество регистров жестко ограничено, поэтому регистры используются компилятором по мере необходимости. У вас нет прямого доступа к регистрам, вы не сможете найти и малейших следов их поддержки в языке. (С другой стороны, языки C и C++ позволяют порекомендовать компилятору хранить данные в регистрах.)
- Стек. Эта область хранения данных находится в общей оперативной памяти (RAM), но процессор предоставляет прямой доступ к ней с использованием указателя стека. Указатель стека перемещается вниз для выделения памяти или вверх для ее освобождения. Это чрезвычайно быстрый и эффективный способ размещения данных, по скорости уступающий только регистрам. Во время обработки программы компилятор Java должен знать жизненный цикл данных, размещаемых в стеке. Это ограничение уменьшает гибкость ваших программ, поэтому, хотя некоторые данные Java хранятся в стеке (особенно ссылки на объекты), сами объекты Java не помещаются в стек.
- Куча. Пул памяти общего назначения (находится также в RAM), в котором размещаются все объекты Java. Преимущество кучи состоит в том, что компилятору не обязательно знать, как долго просуществуют находящиеся там объекты. Таким образом, работа с кучей дает значительное преимущество в гибкости. Когда вам нужно создать объект, вы пишете код с использованием new, и память выделяется из кучи во время выполнения программы. Конечно, за гибкость приходится расплачиваться: выделение памяти из кучи занимает больше времени, чем в стеке (даже если бы вы могли явно создавать объекты в стеке, как в C++).
- Постоянная память. Значения констант часто встраиваются прямо в код программы, так как они неизменны. Иногда такие данные могут размещаться в постоянной памяти (ROM), если речь идет о «встроенных» системах.
- Не-оперативная память. Если данные располагаются вне программы, они могут существовать и тогда, когда она не выполняется. Два основных примера: потоковые объекты (streamed objects), в которых объекты представлены в виде потока байтов, обычно используются для посылки на другие машины, и долгоживущие (persistent) объекты, которые запоминаются на диске и сохраняют свое состояние даже после окончания работы программы. Особенностью этих видов хранения данных является возможность перевода объектов в нечто, что может быть сохранено на другом носителе информации, а потом восстановлено в виде обычного объекта, хранящегося в оперативной памяти. В Java организована поддержка легковесного (lightweight) сохранения состояния, а такие механизмы, как JDBC и Hibernate, предоставляют более совершенную поддержку сохранения и выборки информации об объектах из баз данных.
Особый случай: примитивные типы
Одна из групп типов, часто применяемых при программировании, требует особого обращения. Их можно назвать «примитивными» типами (табл. 2.1). Причина для особого обращения состоит в том, что создание объекта с помощью new — особенно маленькой простой переменной — недостаточно эффективно, так как new помещает объекты в кучу. В таких случаях Java следует примеру языков C и C++. То есть вместо создания переменной с помощью new создается «автоматическая» переменная, не являющаяся ссылкой. Переменная напрямую хранит значение и располагается в стеке, так что операции с ней гораздо производительнее.
В Java размеры всех примитивных типов жестко фиксированы. Они не меняются с переходом на иную машинную архитектуру, как это происходит во многих других языках. Незыблемость размера — одна из причин улучшенной переносимости Java-npoгpaмм.
Таблица 2.1. Примитивные типы
Примитивный тип | Размер, бит | Минимум | Максимум | Тип упаковки |
---|---|---|---|---|
boolean (логические значения) | — | — | — | Boolean |
char (символьные значения) | 16 | Unicode 0 | Unicode 2^16-1 | Character |
byte (байт) | 8 | -128 | +127 | Byte |
short (короткое целое) | 16 | -2^15 | +2^15-1 | Short |
int (целое) | 32 | -2^31 | +2^31-1 | Integer |
long (длинное целое) | 64 | -2^63 | +2^63-1 | Long |
float (число с плавающей запятой) | 32 | IEEE754 | IEEE754 | Float |
double (число с повышенной точностью) | 64 | IEEE754 | IEEE754 | Double |
void (пустое значение) | — | — | — | Void |
Все числовые значения являются знаковыми, так что не ищите слова unsigned.
Размер типа boolean явно не определяется; указывается лишь то, что этот тип может принимать значения true и false.
«Классы-обертки» позволяют создать в куче не-примитивный объект для представления примитивного типа. Например:
char с = 'х';
Character ch = new Character(c),
Также можно использовать такой синтаксис:
Character ch = new Character('x');
Механизм автоматической упаковки Java SE5 автоматически преобразует примитивный тип в объектную «обертку»:
Character ch = 'х';
и обратно:
char с = ch;
Причины создания подобных конструкций будут объяснены в последующих главах.
Числа повышенной точности
В Java существует два класса для проведения арифметических операций повышенной точности: BigInteger и BigDecimal. Хотя эти классы примерно подходят под определение «классов-оберток», ни один из них не имеет аналога среди примитивных типов.
Оба класса содержат методы, производящие операции, аналогичные тем, что проводятся над примитивными типами. Иначе говоря, с классами Biglnteger и BigDecimal можно делать то же, что с int или float, просто для этого используются вызовы методов, а не встроенные операции. Также из-за использования увеличенного объема данных операции занимают больше времени. Приходится жертвовать скоростью ради точности.
Класс BigInteger поддерживает целые числа произвольной точности. Это значит, что вы можете использовать целочисленные значения любой величины без потери данных во время операций.
Класс BigDecimal представляет числа с фиксированной запятой произвольной точности; например, они могут применяться для финансовых вычислений. За подробностями о конструкторах и методах этих классов обращайтесь к документации JDK.
Массивы в Java
Фактически все языки программирования поддерживают массивы. Использование массивов в C и C++ небезопасно, потому что массивы в этих языках представляют собой обычные блоки памяти. Если программа попытается получить доступ к массиву за пределами его блока памяти или использовать память без предварительной инициализации (типичные ошибки при программировании), последствия могут быть непредсказуемы.
Одной из основных целей Java является безопасность, поэтому многие проблемы, досаждавшие программистам на C и C++, не существуют в Java. Массив в Java гарантированно инициализируется, к нему невозможен доступ за пределами его границ. Проверка границ массива обходится относительно дорого, как и проверка индекса во время выполнения, но предполагается, что повышение безопасности и подъем производительности стоят того (к тому же Java иногда может оптимизировать эти операции). При объявлении массива объектов на самом деле создается массив ссылок, и каждая из этих ссылок автоматически инициализируется специальным значением, представленным ключевым словом null. Оно означает, что ссылка на самом деле не указывает на объект. Вам необходимо присоединять объект к каждой ссылке перед тем, как ее использовать, или при попытке обращения по ссылке null во время исполнения программы произойдет ошибка. Таким образом, типичные ошибки при работе с массивами в Java предотвращаются заблаговременно.
Также можно создавать массивы простейших типов. И снова компилятор гарантирует инициализацию — выделенная для нового массива память заполняется нулями. Массивы будут подробнее описаны в последующих главах.
В большинстве языков программирования концепция жизненного цикла переменной требует относительно заметных усилий со стороны программиста. Сколько «живет» переменная? Если ее необходимо удалить, когда это следует делать? Путаница со сроками существования переменных может привести ко многим ошибкам, и этот раздел показывает, насколько Java упрощает решение затронутого вопроса, выполняя всю работу по удалению за вас.
Ограничение области действия
В большинстве процедурных языков существует понятие области действия (scope). Область действия определяет как видимость, так и срок жизни имен, определенных внутри нее. В 'C, C++' и Java область действия устанавливается положением фигурных скобок { }. Например:
{
int х = 12;
// доступно только х
{
int q = 96;
// доступны как х, так и q
}
// доступно ТОЛЬКО x
// q находится "за пределами видимости"
}
Переменная, определенная внутри области действия, доступна только в пределах этой области.
Весь текст после символов // и до конца строки является комментарием. Отступы упрощают чтение программы на Java. Так как Java относится к языкам со свободным форматом, дополнительные пробелы, табуляция и переводы строк не влияют на результирующую программу.
Учтите, что следующая конструкция не разрешена, хотя в C и C++ она возможна:
{ int х = 12;
{
int х = 96; // неверно
}
}
Компилятор объявит, что переменная х уже была определена. Таким образом, возможность языков C и C++ «прятать» переменные во внешней области действия не поддерживается. Создатели Java посчитали, что она приводит к излишнему усложнению программ.
Область действия объектов
Объекты Java имеют другое время жизни в сравнении с примитивами. Объект, созданный оператором Java new, будет доступен вплоть до конца области действия. Если вы напишете:
{String s = new String("строка"); } // конец области действия
то ссылка s исчезнет в конце области действия. Однако объект типа String, на который указывала s, все еще будет занимать память. В показанном фрагменте кода невозможно получить доступ к объекту, потому что единственная ссылка вышла за пределы видимости. В следующих главах вы узнаете, как передаются ссылки на объекты и как их можно копировать во время работы программы.
Благодаря тому, что объекты, созданные new, существуют ровно столько, сколько вам нужно, в Java исчезает целый пласт проблем, присущих C++. В C++ приходится не только следить за тем, чтобы объекты продолжали существовать на протяжении своего жизненного цикла, но и удалять объекты после завершения работы с ними.
Возникает интересный вопрос. Если в Java объекты остаются в памяти, что же мешает им постепенно занять всю память и остановить выполнение программы? Именно это произошло бы в данном случае в C++. Однако в Java существует сборщик мусора (garbage collector), который наблюдает за объектами, созданными оператором new, и определяет, на какие из них больше нет ссылок. Тогда он освобождает память от этих объектов, которая становится доступной для дальнейшего использования. Таким образом, вам никогда не придется «очищать» память вручную. Вы просто создаете объекты, и как только надобность в них отпадет, эти объекты исчезают сами по себе. При таком подходе исчезает целый класс проблем программирования: так называемые «утечки памяти», когда программист забывает освобождать занятую память.
Создание новых типов данных
Если все является объектом, что определяет строение и поведение класса объектов? Другими словами, как устанавливается тип объекта? Наверное, для этой цели можно было бы использовать ключевое слово type («тип»); это было бы вполне разумно. Впрочем, с давних времен повелось, что большинство объектно-ориентированных языков использовали ключевое слово class в смысле «Я собираюсь описать новый тип объектов». За ключевым словом class следует имя нового типа. Например:
class ATypeName { /* Тело класса */ }
Эта конструкция вводит новый тип, и поэтому вы можете теперь создавать объект этого типа ключевым словом new:
ATypeName а = new ATypeName();
Впрочем, объекту нельзя «приказать» что-то сделать (то есть послать ему необходимые сообщения) до тех пор, пока для него не будут определены методы.
Поля и методы
При определении класса (строго говоря, вся ваша работа на Java сводится к определению классов, созданию объектов этих классов и посылке сообщений этим объектам) в него можно включить две разновидности элементов: поля (fields) (иногда называемые переменными класса) и методы (methods) (еще называемые функциями класса). Поле представляет собой объект любого типа, с которым можно работать по ссылке, или объект примитивного типа. Если используется ссылка, ее необходимо инициализировать, чтобы связать с реальным объектом (ключевым словом new, как было показано ранее).
Каждый объект использует собственный блок памяти для своих полей данных; совместное использование обычных полей разными объектами класса невозможно. Пример класса с полями:
class DataOnly { int і; double d; boolean b;}
Такой класс ничего не делает, кроме хранения данных, но вы можете создать объект этого класса:
DataOnly data = new DataOnly();
Полям класса можно присваивать значения, но для начала необходимо узнать, как обращаться к членам объекта. Для этого сначала указывается имя ссылки на объект, затем следует точка, а далее — имя члена, принадлежащего объекту:
ссылка.член
Например:
data.і = 47;
data.d = 1.1;
data.b = false;
Также ваш объект может содержать другие объекты, данные которых вы хотели бы изменить. Для этого просто продолжите «цепочку из точек». Например:
myPlane.leftTank.capacity = 100;
Класс DataOnly не способен ни на что, кроме хранения данных, так как в нем отсутствуют методы. Чтобы понять, как они работают, необходимо разобраться, что такое аргументы и возвращаемые значения. Вскоре мы вернемся к этой теме.
Значения по умолчанию для полей примитивных типов
Если поле данных относится к примитивному типу, ему гарантированно присваивается значение по умолчанию, даже если оно не было инициализировано явно (табл. 2.2).
Таблица 2.2. Значения по умолчанию для полей примитивных типов
Примитивный тип Значение по умолчанию
boolean false
char '\u0000'(null)
byte (byte)0
short (short)0
int 0
long 0L
float 0.0f
double 0.0d
Значения по умолчанию гарантируются Java только в том случае, если переменная используется как член класса. Тем самым обеспечивается обязательная инициализация элементарных типов (что не делается в C++), которая уменьшает вероятность ошибок. Однако значение по умолчанию может быть неверным или даже недопустимым для вашей программы. Переменные всегда лучше инициализировать явно.
Такая гарантия не относится к локальным переменным, которые не являются полями класса. Допустим, в определении метода встречается объявление переменной
int х;
Переменной х будет присвоено случайное значение (как в C и C++); она не будет автоматически инициализирована нулем. Вы отвечаете за присвоение правильного значения перед использованием х. Если же вы забудете это сделать, в Java существует очевидное преимущество в сравнении с C++: компилятор выдает ошибку, в которой указано, что переменная не была инициализирована. (Многие компиляторы C++ предупреждают о таких переменных, но в Java это считается ошибкой.)
Методы, аргументы и возвращаемые значения
Что такое - метод?
Во многих языках (таких как C и C++) для обозначения именованной подпрограммы употребляется термин функция. В Java чаще предпочитают термин метод, как бы подразумевающий «способ что-то сделать». Если вам хочется, вы можете продолжать пользоваться термином «функция». Разница только в написании, но в дальнейшем в книге будет употребляться преимущественно термин «метод».
Методы в Java определяют сообщения, принимаемые объектом. Основные части метода — имя, аргументы, возвращаемый тип и тело. Вот примерная форма:
возвращаемыйТип ИмяМетода( /* список аргументов */ ) { /* тело метода */}
Возвращаемый тип — это тип объекта, «выдаваемого» методом после его вызова. Список аргументов определяет типы и имена для информации, которую вы хотите передать в метод. Имя метода и его список аргументов (объединяемые термином сигнатура) обеспечивают однозначную идентификацию метода.
Методы в Java создаются только как части класса. Метод может вызываться только для объекта, и этот объект должен обладать возможностью произвести такой вызов. Если вы попытаетесь вызвать для объекта несуществующий метод, то получите ошибку компиляции. Вызов метода осуществляется следующим образом: сначала записывается имя объекта, за ним точка, за ней следуют имя метода и его список аргументов:
имяОбъекта.имяМетода(арг1, арг2, арг3)
Например, представьте, что у вас есть метод f(), вызываемый без аргументов, который возвращает значение типа int. Если у вас имеется в наличии объект а, для которого может быть вызван метод f(), в вашей власти использовать следующую конструкцию:
int х = a.f();
Тип возвращаемого значения должен быть совместим с типом х.
Такое действие вызова метода часто называется посылкой сообщения объекту. В примере выше сообщением является вызов f(), а объектом — а. Объектно-ориентированное программирование нередко характеризуется обобщающей формулой «посылка сообщений объектам».
Список аргументов
Список аргументов определяет, какая информация передается методу. Как легко догадаться, эта информация — как и все в Java — воплощается в форме объектов, поэтому в списке должны быть указаны как типы передаваемых объектов, так и их имена. Как и в любой другой ситуации в Java, где мы вроде бы работаем с объектами, на самом деле используются ссылки. Впрочем, тип ссылки должен соответствовать типу передаваемых данных. Если предполагается, что аргумент является строкой (то есть объектом String), вы должны передать именно строку, или ожидайте сообщения об ошибке.
Рассмотрим метод, получающий в качестве аргумента строку (String). Следующее определение должно размещаться внутри определения класса, для которого создается метод:
int storage(String s) {
return s.length() * 2;
}
Метод указывает, сколько байтов потребуется для хранения данных определенной строки. (Строки состоят из символов char, размер которых — 16 бит, или 2 байта; это сделано для поддержки набора символов Unicode.) Аргумент имеет тип String и называется s. Получив объект s, метод может работать с ним точно так же, как и с любым другим объектом (то есть посылать ему сообщения). В данном случае вызывается метод length(), один из методов класса String, он возвращает количество символов в строке.
Также обратите внимание на ключевое слово return, выполняющее два действия. Во-первых, оно означает: «выйти из метода, все сделано». Во-вторых, если метод возвращает значение, это значение указывается сразу же за командой return. В нашем случае возвращаемое значение — это результат вычисления s.length() * 2.
Возвращаемое значение
Метод может возвращать любой тип, но, если вы не хотите пользоваться этой возможностью, следует указать, что метод возвращает void. Ниже приведено несколько примеров:
boolean flag() { return true; }
float naturalLogBaseO { return 2.718; }
void nothing() { return; }
void nothing2() {}
Когда выходным типом является void, ключевое слово return нужно лишь для завершения метода, поэтому при достижении конца метода его присутствие необязательно. Вы можете покинуть метод в любой момент, но если при этом указывается возвращаемый тип, отличный от void, то компилятор заставит вас (сообщениями об ошибках) вернуть подходящий тип независимо от того, в каком месте метода было прервано выполнение.
К этому моменту может сложиться впечатление, что программа — это просто «свалка» объектов со своими методами, которые принимают другие объекты в качестве аргументов и посылают им сообщения. По большому счету так оно и есть, но в следующей главе вы узнаете, как производить кропотливую низкоуровневую работу с принятием решений внутри метода. В этой главе достаточно рассмотрения на уровне посылки сообщений.
Создание программы на Java
Есть еще несколько вопросов, которые необходимо понять перед созданием первой программы на Java.
Видимость имен
Проблема управления именами присуща любому языку программирования. Если имя используется в одном из модулей программы и оно случайно совпало с именем в другом модуле у другого программиста, то как отличить одно имя от другого и предотвратить их конфликт? В C это определенно является проблемой, потому что программа с трудом поддается контролю в условиях «моря» имен. Классы C++ (на которых основаны классы Java) скрывают функции внутри классов, поэтому их имена не пересекаются с именами функций других классов. Однако в C++ дозволяется использование глобальных данных и глобальных функций, соответственно, конфликты полностью не исключены. Для решения означенной проблемы в C++ введены пространства имен (namespaces), которые используют дополнительные ключевые слова.
В языке Java для решения этой проблемы было использовано свежее решение. Для создания уникальных имен библиотек разработчики Java предлагают использовать доменное имя, записанное «наоборот», так как эти имена всегда уникальны. Мое доменное имя — MindView.net, и утилиты моей программной библиотеки могли бы называться net.mindview.utility.foibles. За перевернутым доменным именем следует перечень каталогов, разделенных точками.
В версиях Java 1.0 и 1.1 доменные суффиксы com, edu, org, net по умолчанию записывались заглавными буквами, таким образом, имя библиотеки выглядело так: NET.mindview.utility.foibles. В процессе разработки Java 2 было обнаружено, что принятый подход создает проблемы, и с тех пор имя пакета записывается строчными буквами.
Такой механизм значит, что все ваши файлы автоматически располагаются в своих собственных пространствах имен, и каждый класс в файле должен иметь уникальный идентификатор. Язык сам предотвращает конфликты имен.
Использование внешних компонентов
Когда вам понадобится использовать уже определенный класс в вашей программе, компилятор должен знать, как этот класс обнаружить. Конечно, класс может уже находиться в том же самом исходном файле, откуда он вызывается. В таком случае вы просто его используете — даже если определение класса следует где-то дальше в файле (В Java не существует проблемы «опережающих ссылок».)
Но что, если класс находится в каком-то внешнем файле? Казалось бы, компилятор должен запросто найти его, но здесь существует проблема. Представьте, что вам необходим класс с неким именем, для которого имеется более одного определения (вероятно, отличающихся друг от друга). Или, что еще хуже, представьте, что вы пишете программу и при ее создании в библиотеку добавляется новый класс, конфликтующий с именем уже существующего класса.
Для решения проблемы вам необходимо устранить все возможные неоднозначности. Задача решается при помощи ключевого слова 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 і =47;
}
Теперь, даже при создании двух объектов StaticTest, для элемента StaticTest.i выделяется единственный блок памяти. Оба объекта совместно используют одно значение і. Пример:
StaticTest stl = new StaticTest();
StaticTest st2 = new StaticTest();
В данном примере как st1.i, так и st2.i имеют одинаковые значения, равные 47, потому что расположены они в одном блоке памяти. Существует два способа обратиться к статической переменной. Как было видно выше, вы можете указать ее с помощью объекта, например st2.i. Также можно обратиться к ней прямо по имени класса (для нестатических членов класса такая возможность отсутствует):
StaticTest.i++;
Оператор ++ увеличивает значение на единицу (инкремент). После выполнения этой команды значения st1.i и st2.i будут равны 48.
Синтаксис с именем класса является предпочтительным, потому что он не только подчеркивает, что переменная описана как static, но и в некоторых случаях предоставляет компилятору больше возможностей для оптимизации.
Та же логика верна и для статических методов. Вы можете обратиться к такому методу или через объект, как это делается для всех методов, или в специальном синтаксисе имяКласса.метод(). Статические методы определяются по аналогии со статическими данными:
class Incrementable {
static void increment () { StaticTest.i++; }
}
Нетрудно заметить, что метод increment() класса Incrementable увеличивает значение статического поля і. Метод можно вызвать стандартно, через объект:
Incrementable sf = new Incrementable();
sf.increment();
Или, поскольку increment() является статическим, можно вызвать его с прямым указанием класса:
Incrementable.increment();
Применительно к полям ключевое слово static радикально меняет способ определения данных: статические данные существуют на уровне класса, в то время как нестатические данные существуют на уровне объектов, но в отношении изменения не столь принципиальны. Одним из важных применений static является определение методов, которые могут вызываться без объектов. В частности, это абсолютно необходимо для метода main(), который представляет собой точку входа в приложение.
Наша первая программа на Java
Наконец, долгожданная программа. Она запускается, выводит на экран строку, а затем текущую дату, используя стандартный класс Date из стандартной библиотеки Java:
// НеlloDate.java
import java.util.*;
public class HelloDate {
public static void main(String[] args) {
System.out.println("Привет, сегодня: ");
System out println(new Date());
}
}
После запуска программы получим текст вида:
Привет, сегодня:
Wed Oct 05 14:39:36 MDT 2005
В начале каждого файла с программой должны находиться директивы import, в которых перечисляются все дополнительные классы, необходимые вашей программе. Обратите внимание на слово «дополнительные» — существует целая библиотека классов, присоединяющаяся автоматически к каждому файлу Java: java.lang. Запустите ваш браузер и просмотрите документацию фирмы Sun. (Если вы не загрузили документацию JDK с сайта https://java.sun.com или не получили ее иным способом, обязательно это сделайте.) Учтите, что документация не входит в комплект JDK, ее необходимо загрузить отдельно. Взглянув на список пакетов, вы найдете в нем различные библиотеки классов, поставляемые с Java.
Выберите java.lang. Здесь вы увидите список всех классов, составляющих эту библиотеку. Так как пакет java.lang. автоматически включается в каждую программу на Java, эти классы всегда доступны для использования. Класса Date в нем нет, а это значит, что для его использования придется импортировать другую библиотеку.
Если вы не знаете, в какой библиотеке находится нужный класс, или если вам понадобится увидеть все классы, выберите Tree (дерево классов) в документации. В нем можно обнаружить любой из доступных классов Java. Функция поиска текста в браузере поможет найти класс Date. Результат поиска показывает, что класс называется java.util.Date, то есть находится в библиотеке util, и для получения доступа к классу Date необходимо будет использовать директиву import для загрузки пакета java.util.*.
Если вы вернетесь к началу, выберете пакет java.lang, а затем класс System, то увидите, что он имеет несколько полей. При выборе поля out обнаруживается, что оно представляет собой статический объект PrintStream. Так как поле описано с ключевым словом static, вам не понадобится создавать объекты. Действия, которые можно выполнять с объектом out, определяются его типом: PrintStream.
Для удобства в описание этого типа включена гиперссылка, и, если щелкнуть на ней, вы обнаружите список всех доступных методов. Этих методов довольно много, и они будут позже рассмотрены в книге. Сейчас нас интересует только метод println(), вызов которого фактически означает: «вывести то, что передано методу, на консоль и перейти на новую строку». Таким образом, в любую программу на Java можно включить вызов вида System.out.println ("что-то"), чтобы вывести сообщение на консоль.
Имя класса совпадает с именем файла. Когда вы создаете отдельную программу, подобную этой, один из классов, описанных в файле, должен иметь совпадающее с ним название. (Если это условие нарушено, компилятор сообщит об ошибке.) Одноименный класс должен содержать метод с именем main() со следующей сигнатурой и возвращаемым типом:
public static void main(String[] args) {
Ключевое слово public обозначает, что метод доступен для внешнего мира (об этом подробно рассказывает глава 5). Аргументом метода main() является массив строк. В данной программе массив args не используется, но компилятор Java настаивает на его присутствии, так как массив содержит параметры, переданные программе в командной строке.
Строка, в которой распечатывается число, довольно интересна:
System.out.println (new Date());
Аргумент представляет собой объект Date, который создается лишь затем, чтобы передать свое значение (автоматически преобразуемое в String) методу println(). Как только команда будет выполнена, объект Date становится ненужным, сборщик мусора заметит это, и в конце концов сам удалит его. Нам не нужно беспокоиться о его удалении самим.
Компиляция и выполнение
Чтобы скомпилировать и выполнить эту программу, а также все остальные программы в книге, вам понадобится среда разработки Java. Существует множество различных сред разработок от сторонних производителей, но в этой книге мы предполагаем, что вы избрали бесплатную среду JDK Java Developer's Kit) от фирмы Sun. Если же вы используете другие системы разработки программ, вам придется просмотреть их документацию, чтобы узнать, как компилировать и запускать программы.
Подключитесь к Интернету и посетите сайт https://java.sun.com. Там вы найдете информацию и необходимые ссылки, чтобы загрузить и установить JDK для вашей платформы.
Как только вы установите JDK и правильно установите пути запуска, в результате чего система сможет найти утилиты javac и java, загрузите и распакуйте исходные тексты программ для этой книги (их можно загрузить с сайта https://www.MindView.net). Там вы обнаружите каталоги (папки) для каждой главы книги. Перейдите в папку objects и выполните команду
javac HelloDate java
Команда не должна выводить каких-либо сообщений. Если вы получили сообщение об ошибке, значит, вы неверно установили JDK и вам нужно разобраться со своими проблемами.
И наоборот, если все прошло успешно, выполните следующую команду:
java НеlloDate
и вы увидите сообщение и число как результат работы программы.
Эта последовательность действий позволяет откомпилировать и выполнить любую программу-пример из этой книги. Однако также вы увидите, что каждая папка содержит файл build.xml с командами для инструмента ant по автоматической сборке файлов для данной главы. После установки ant с сайта https://jakarta.apache.org/ant можно будет просто набрать команду ant в командной строке, чтобы скомпилировать и запустить программу из любого примера. Если ant на вашем компьютере еще не установлен, команды javac и java придется вводить вручную.
Комментарии и встроенная документация
В Java приняты два вида комментариев. Первый — традиционные комментарии в стиле C, также унаследованные языком C++. Такие комментарии начинаются с комбинации /* и распространяются иногда на множество строк, после чего заканчиваются символами */. Заметьте, что многие программисты начинают каждую новую строку таких комментариев символом *, соответственно, часто можно увидеть следующее:
/* Это комментарий,
* распространяющийся на
* несколько строк */
Впрочем, все символы между /* и */ игнорируются, и с таким же успехом можно использовать запись
/* Это комментарий,
распространяющийся на несколько строк */
Второй вид комментария пришел из языка C++. Однострочный комментарий начинается с комбинации // и продолжается до конца строки. Такой стиль очень удобен и прост, поэтому широко используется на практике. Вам не приходится искать на клавиатуре сначала символ /, а затем * (вместо этого вы дважды нажимаете одну и ту же клавишу), и не нужно закрывать комментарий. Поэтому часто можно увидеть такие примеры:
// это комментарий в одну строку
Документация в комментариях
Пожалуй, основные проблемы с документированием кода связаны с его сопровождением. Если код и его документация существуют раздельно, корректировать описание программы при каждом ее изменении становится задачей не из легких. Решение выглядит очень просто: совместить код и документацию. Проще всего объединить их в одном файле. Но для полноты картины понадобится специальный синтаксис комментариев, чтобы помечать документацию, и инструмент, который извлекал бы эти комментарии и оформлял их в подходящем виде. Именно это было сделано в Java.
Инструмент для извлечения комментариев называется javadoc, он является частью пакета JDK. Некоторые возможности компилятора Java используются в нем для поиска пометок в комментариях, включенных в ваши программы. Он не только извлекает помеченную информацию, но также узнает имя класса или метода, к которому относится данный фрагмент документации. Таким образом, с минимумом затраченных усилий можно создать вполне приличную сопроводительную документацию для вашей программы.
Результатом работы программы javadoc является HTML-файл, который можно просмотреть в браузере. Таким образом, утилита javadoc позволяет создавать и поддерживать единый файл с исходным текстом и автоматически строить полезную документацию. В результае получается простой и практичный стандарт по созданию документации, поэтому мы можем ожидать (и даже требовать) наличия документации для всех библиотек Java.
Вдобавок, вы можете дополнить javadoc своими собственными расширениями, называемыми доклетами (doclets), в которых можно проводить специальные операции над обрабатываемыми данными (например, выводить их в другом формате).
Далее следует лишь краткое введение и обзор основных возможностей javadoc. Более подробное описание можно найти в документации JDK. Распаковав документацию, загляните в папку tooldocs (или перейдите по ссылке tooldocs).
Синтаксис
Все команды javadoc находятся только внутри комментариев /**. Комментарии, как обычно, завершаются последовательностью */. Существует два основных способа работы с javadoc: встраивание HTML-текста или использование разметки документации (тегов). Самостоятельные теги документации — это команды, которые начинаются символом @ и размещаются с новой строки комментария.
(Начальный символ * игнорируется.) Встроенные теги документации могут располагаться в любом месте комментария javadoc, также начинаются со знака @, но должны заключаться в фигурные скобки.
Существует три вида документации в комментариях для разных элементов кода: класса, переменной и метода. Комментарий к классу записывается прямо перед его определением; комментарий к переменной размещается непосредственно перед ее определением, а комментарий к методу тоже записывается прямо перед его определением. Простой пример:
//: object/Documentation1.java
/** Комментарий к классу */
public class Documentation1 {
/** Комментарий к переменной */
public int і;
/** Комментарий к методу */
public void f() {}
} ///:~
Заметьте, что javadoc обрабатывает документацию в комментариях только для членов класса с уровнем доступа public и protected. Комментарии для членов private и членов с доступом в пределах пакета игнорируются, и документация по ним не строится. (Впрочем, флаг -private включает обработку и этих членов). Это вполне логично, поскольку только public- и protected-члены доступны вне файла, и именно они интересуют программиста-клиента.
Результатом работы программы является HTML-файл в том же формате, что и остальная документация для Java, так что пользователям будет привычно и удобно просматривать и вашу документацию. Попробуйте набрать текст предыдущего примера, «пропустите» его через javadoc и просмотрите полученный HTML-файл, чтобы увидеть результат.
Встроенный HTML
javadoc вставляет команды HTML в итоговый документ. Это позволяет полностью использовать все возможности HTML; впрочем, данная возможность прежде всего ориентирована на форматирование кода:
//: object/Documentation2.java
/**
* <pre>
* System.out.println(new Date());
* </pre>
*/
public class Documentation2 {}
Вы можете использовать HTML точно так же, как в обычных страницах, чтобы привести описание к нужному формату:
//: object/Documentation3.java
/**
* You can <em>even</em> insert a list:
* <ol>
* <li> Пункт первый
* <li> Пункт второй
* <li> Пункт третий
* </ol>
*/
public class Documentation3 {}
javadoc игнорирует звездочки в начале строк, а также начальные пробелы. Текст переформатируется таким образом, чтобы он отвечал виду стандартной документации. Не используйте заголовки вида <h1> или <hr> во встроенном HTML, потому что javadoc вставляет свои собственные заголовки и ваши могут с ними «пересечься».
Встроенный HTML-код поддерживается всеми типами документации в комментариях — для классов, переменных или методов.
Примеры тегов
Далее описаны некоторые из тегов javadoc, используемых при документировании программы. Прежде чем применять javadoc для каких-либо серьезных целей, просмотрите руководство по нему в документации пакета JDK, чтобы получить полную информацию о его использовании.
@see: ссылка на другие классы
Тег позволяет ссылаться на документацию к другим классам. javadoc там, где были записаны теги @see, создает HTML-ссылки на другие документы. Основные формы использования тега:
@see имя класса
@see полное-имя-класса
@see полное-имя-класса#имя-метода
Каждая из этих форм включает в генерируемую документацию замечание See Also («см. также») со ссылкой на указанные классы. javadoc не проверяет передаваемые ему гиперссылки.
{@link пакет.класс#член_класса метка}
Тег очень похож на @see, не считая того, что он может использоваться как встроенный, а вместо стандартного текста See Also в ссылке размещается текст, указанный в поле метка.
{@docRoot}
Позволяет получить относительный путь к корневой папке, в которой находится документация. Полезен при явном задании ссылок на страницы из дерева документации.
{@inheritDoc}
Наследует документацию базового класса, ближайшего к документируемому классу, в текущий файл с документацией.
@version
Имеет следующую форму:
@version информация-о-версии
Поле информации о версии содержит ту информацию, которую вы сочли нужным включить. Когда в командной строке javadoc указывается опция -version, в созданной документации специально отводится место, заполняемое информацией о версиях.
@author
Записывается в виде
@author информация-об-авторе
Предполагается, что поле информация-об-авторе представляет собой имя автора, хотя в него также можно включить адрес электронной почты и любую другую информацию. Когда в командной строке javadoc указывается опция -author, в созданной документации сохраняется информация об авторе.
Для создания списка авторов можно записать сразу несколько таких тегов, но они должны размещаться последовательно. Вся информация об авторах объединяется в один раздел в сгенерированном коде HTML.
@since
Тег позволяет задать версию кода, с которой началось использование некоторой возможности. В частности, он присутствует в HTML-документации по Java, где служит для указания версии JDK.
@param
Полезен при документировании методов. Форма использования:
@param имя-параметра описание
где имя-параметра — это идентификатор в списке параметров метода, а описание — текст описания, который можно продолжить на несколько строк. Описание считается завершенным, когда встретится новый тег. Можно записывать любое количество тегов @param, по одному для каждого параметра метода.
@return
Форма использования:
@return описание
где описание объясняет, что именно возвращает метод. Описание может состоять из нескольких строк.
@throws
Исключения будут рассматриваться в главе 9. В двух словах это объекты, которые можно «возбудить» (throw) в методе, если его выполнение потерпит неудачу. Хотя при вызове метода создается всегда один объект исключения, определенный метод может вырабатывать произвольное количество исключений, и все они требуют описания. Соответственно, форма тега исключения такова:
@throws полное-имя-класса описание
где полное-имя-класса дает уникальное имя класса исключения, который где-то определен, а описание (расположенное на произвольном количестве строк) объясняет, почему данный метод способен создавать это исключение при своем вызове.
@deprecated
Тег используется для пометки устаревших возможностей, замещенных новыми и улучшенными. Он сообщает о том, что определенные средства программы не следует использовать, так как в будущем они, скорее всего, будут убраны. В Java SE5 тег @deprecated был заменен директивой @Deprecated (см. далее).
Пример документации
Вернемся к нашей первой программе на Java, но на этот раз добавим в нее комментарии со встроенной документацией:
//: object/HelloDate.java
import java.util.*;
/** Первая программа-пример книги.
* Выводит строку и текущее число.
* @author Брюс Эккель
* @author www.MindView.net
* @version 4.0
*/
public class HelloDate {
/** Точка входа в класс и приложение
* @param Массив строковых аргументов
* @throws exceptions Исключения не выдаются
*/
public static void main(String[] args) {
System.out.println("Привет, сегодня: ");
System.out.println(new Date());
}
}
/*
Output: (55% match)
Привет, сегодня:
Wed Oct 05 14:39:36 MDT 2005
*///:~
В первой строке файла использована моя личная методика помещения специального маркера //: в комментарий как признака того, что в этой строке комментария содержится имя файла с исходным текстом. Здесь указывается путь к файлу (object означает эту главу) с последующим именем файла. Последняя строка также завершается комментарием (///:~), обозначающим конец исходного текста программы. Он помогает автоматически извлекать из текста книги программы для проверки компилятором и выполнения.
Тег /* Output: обозначает начало выходных данных, сгенерированных данным файлом. В этой форме их можно автоматически проверить на точность.
В данном случае значение (55% match) сообщает системе тестирования, что результаты будут заметно отличаться при разных запусках программы. В большинстве примеров книги результаты приводятся в комментариях такого вида, чтобы вы могли проверить их на правильность.
Стиль оформления программ
Согласно правилам стиля, описанным в руководстве Code Conventions for the Java Programming Language имена классов должны записываться с прописной буквы. Если имя состоит из нескольких слов, они объединяются (то есть символы подчеркивания не используются для разделения), и каждое слово в имени начинается с большой буквы:
class АllTheColorsOfTheRainbow { // ..
Практически для всего остального: методов, полей и ссылок на объекты — используется такой же способ записи, за одним исключением — первая буква идентификатора записывается строчной. Например:
class AllTheColorsOfTheRainbow {
int anIntegerRepresentingColors;
void changeTheHueOfTheColor(int newHue) { /*.......*/ }
// ....
}
Помните, что пользователю ваших классов и методов придется вводить все эти длинные имена, так что будьте милосердны.
В исходных текстах Java, которые можно увидеть в библиотеках фирмы Sun, также используется схема размещения открывающих и закрывающих фигурных скобок, которая встречается в примерах данной книги.
Резюме
В этой главе я постарался привести информацию о программировании на Java, достаточную для написания самой простой программы. Также был представлен обзор языка и некоторых его основных свойств. Однако примеры до сих пор имели форму «сначала это, потом это, а после что-то еще». В следующих двух главах будут представлены основные операторы, используемые при программировании на Java, а также способы передачи управления в вашей программе.
------------------------
ТРИО теплый пол отзыв
Заработок на сокращении ссылок
Earnings on reducing links
Код PHP на HTML сайты
Категория: Книги по Java
Комментарии |