6

6


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




Все является объектом

Е
сли бы мы говорили на другом языке, то и мир воспринимали бы по-другому.



Людвиг Витгенштейн (1889-1951)



Хотя язык Java основан на С++, он является более «чистокровным» объект- но-ориентированным языком.



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



Язык программирования Java подразумевает, что вы занимаетесь только объектно-ориентированным программированием. А это значит, что прежде, чем начать с ним работать, нужно «переключиться» на понятия объектно-ориенти- рованного мира (если вы уже этого не сделали). Выгода от этого начального усилия — возможность программировать на языке, который по простоте изуче¬ния и использования превосходит все остальные языки ООП. В этой главе мы рассмотрим основные компоненты Java-программы и узнаем, что в Java (почти) все является объектом.



Для работы с объектами используются ссылки



Каждый язык программирования имеет свои средства манипуляции данными. Иногда программисту приходится быть постоянно в курсе, какая именно мани¬пуляция производится в программе. Вы работаете с самим объектом или же с каким-то видом его косвенного представления (указатель в С или в С++), требующим особого синтаксиса?












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



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



Также пульт может существовать сам по себе, без телевизора. Таким обра¬зом, сам факт наличия ссылки еще не означает наличия присоединенного к ней объекта. Например, для хранения слова или предложения создается ссылка String:



String s;



Однако здесь определяется только ссылка, но не объект. Если вы решите по¬слать сообщение s, произойдет ошибка, потому что ссылка s на самом деле ни к чему не присоединена (телевизора нет). Значит, безопаснее всегда инициа¬лизировать ссылку при ее создании:



String s = "asdf";



В данном примере используется специальная возможность Java: инициали¬зация строк текстом в кавычках. Обычно вы будете использовать более общий способ инициализации объектов.



Все объекты должны создаваться явно



Когда вы определяете ссылку, желательно присоединить ее к новому объекту. В основном это делается при помощи ключевого слова new. Фактически оно оз¬начает: «Создайте мне новый объект». В предыдущем примере можно написать:



String s = new StringC"asdf"):



Это не только значит «предоставьте мне новый объект String», но также ука¬зывает, как создать строку посредством передачи начального набора символов.



Конечно, кроме String, в Java имеется множество готовых типов. Важнее то, что вы можете создавать свои собственные типы. Вообще говоря, именно созда¬ние новых типов станет вашим основным занятием при программировании на Java, и именно его мы будем рассматривать в книге.



Где хранятся данные



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



1. Регистры. Это самое быстрое хранилище, потому что данные хранятся прямо внутри процессора. Однако количество регистров жестко ограни¬чено, поэтому регистры используются компилятором по мере необходи¬мости. У вас нет прямого доступа к регистрам, вы не сможете найти и ма¬лейших следов их поддержки в языке. (С другой стороны, языки С и С++ позволяют порекомендовать компилятору хранить данные в регистрах.)



2. Стек. Эта область хранения данных находится в общей оперативной па¬мяти (RAM), но процессор предоставляет прямой доступ к ней с исполь¬зованием указателя стека. Указатель стека перемещается вниз для выде¬ления памяти или вверх для ее освобождения. Это чрезвычайно быстрый и эффективный способ размещения данных, по скорости уступающий только регистрам. Во время обработки программы компилятор Java дол¬жен знать жизненный цикл данных, размещаемых в стеке. Это ограниче¬ние уменьшает гибкость ваших программ, поэтому, хотя некоторые дан¬ные Java хранятся в стеке (особенно ссылки на объекты), сами объекты Java не помещаются в стек.



3. Куча. Пул памяти общего назначения (находится также в RAM), в кото¬ром размещаются все объекты Java. Преимущество кучи состоит в том, что компилятору не обязательно знать, как долго просуществуют находя¬щиеся там объекты. Таким образом, работа с кучей дает значительное преимущество в гибкости. Когда вам нужно создать объект, вы пишете код с использованием new, и память выделяется из кучи во время выпол¬нения программы. Конечно, за гибкость приходится расплачиваться: вы¬деление памяти из кучи занимает больше времени, чем в стеке (даже если бы вы могли явно создавать объекты в стеке, как в С++).



4. Постоянная память. Значения констант часто встраиваются прямо в код программы, так как они неизменны. Иногда такие данные могут разме¬щаться в постоянной памяти (ROM), если речь идет о «встроенных» сис¬темах.



5. Не-оперативная память. Если данные располагаются вне программы, они могут существовать и тогда, когда она не выполняется. Два основных примера: потоковые объекты (streamed objects), в которых объекты пред¬ставлены в виде потока байтов, обычно используются для посылки на другие машины, и долгоживущие (persistent) объекты, которые запомина¬ются на диске и сохраняют свое состояние даже после окончания работы программы. Особенностью этих видов хранения данных является воз¬можность перевода объектов в нечто, что может быть сохранено на дру¬гом носителе информации, а потом восстановлено в виде обычного объ¬екта, хранящегося в оперативной памяти. В Java организована поддержка легковесного (lightweight) сохранения состояния, а такие механизмы, как JDBC и Hibernate, предоставляют более совершенную поддержку сохра¬нения и выборки информации об объектах из баз данных.



Особый случай: примитивные типы



Одна из групп типов, часто применяемых при программировании, требует осо¬бого обращения. Их можно назвать «примитивными» типами (табл. 2.1). При¬чина для особого обращения состоит в том, что создание объекта с помощью new — особенно маленькой простой переменной — недостаточно эффективно, так как new помещает объекты в кучу. В таких случаях Java следует примеру языков С и С++. То есть вместо создания переменной с помощью new создается «автоматическая» переменная, не являющаяся ссылкой. Переменная напрямую хранит значение и располагается в стеке, так что операции с ней гораздо произ¬водительнее.



В Java размеры всех примитивных типов жестко фиксированы. Они не ме¬няются с переходом на иную машинную архитектуру, как это происходит во многих других языках. Незыблемость размера — одна из причин улучшен¬ной переносимости Java-nporpaMM.



Таблица 2.1. Примитивные типы



Примитивный тип Размер, бит Минимум Максимум Тип упаковки



boolean (логические значения) — — — Boolean



char (символьные значения) 16 . Unicode 0 Unicode 216- ■1 Character



byte (байт) 8 -128 +127 Byte



short (короткое целое) 16 -215 +215-1 Short



int (целое) 32 -231 +231-1 Integer



long (длинное целое) 64 -263 +2б3-1 Long



float (число.с плавающей запятой) 32 IEEE754 IEEE754 Float



double (число с повышенной 64 IEEE754 IEEE754 Double



точностью)



Void («пустое» значение) — — — Void



Все числовые значения являются знаковыми, так что не ищите слова un¬signed.



Размер типа boolean явно не определяется; указывается лишь то, что этот тип может принимать значения true и false.



«Классы-обертки» позволяют создать в куче не-примитивный объект для представления примитивного типа. Например:



char с = 'х*,



Character ch = new Character(c),



Также можно использовать такой синтаксис:



Character ch = new CharacterC'x');



Механизм автоматической упаковки Java SE5 автоматически преобразует примитивный тип в объектную «обертку»:



Character ch = 'х'; и обратно:



char с = ch;



Причины создания подобных конструкций будут объяснены в последующих главах.



Числа повышенной точности



В Java существует два класса для проведения арифметических операций повы¬шенной точности: Biglnteger и BigDecimal. Хотя эти классы примерно подходят под определение «классов-оберток», ни один из них не имеет аналога среди примитивных типов.



Оба класса содержат методы, производящие операции, аналогичные тем, что проводятся над примитивными типами. Иначе говоря, с классами Biglnteger и BigDecimal можно делать то же, что с int или float, просто для этого использу¬ются вызовы методов, а не встроенные операции. Также из-за использования увеличенного объема данных операции занимают больше времени. Приходится жертвовать скоростью ради точности.



Класс Biglnteger поддерживает целые числа произвольной точности. Это значит, что вы можете использовать целочисленные значения любой величины без потери данных во время операций.



Класс BigDecimal представляет числа с фиксированной запятой произволь¬ной точности; например, они могут применяться для финансовых вычислений.



За подробностями о конструкторах и методах этих классов обращайтесь к документации JDK.



Массивы в Java



Фактически все языки программирования поддерживают массивы. Использо¬вание массивов в С и С++ небезопасно, потому что массивы в этих язы¬ках представляют собой обычные блоки памяти. Если программа попытается получить доступ к массиву за пределами его блока памяти или использовать память без предварительной инициализации (типичные ошибки при програм¬мировании), последствия могут быть непредсказуемы.



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



Объекты никогда не приходится удалять



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



Также можно создавать массивы простейших типов. И снова компилятор га¬рантирует инициализацию — выделенная для нового массива память заполня¬ется нулями.

Report Page