Главная » Java » Набор символов

0

Большинству программистов приходилось иметь дело с исходными текстами программ, в которых использовалось одно из двух представлений символов: кодировка ASCII и ее разновидности (в том числе Latin-1) и EBCDIC. Оба этих набора содержат символы, используемые в английском и некоторых других западно-европейских языках.

В отличие от них, программы на языке Java написаны в Unicode — 16-разрядном наборе символов. Первые 256 символов Unicode представляют собой набор Latin-1, а основная часть первых 128 символов Latin-1 соответствует 7-разрядному набору символов ASCII. В настоящее время окружение Java может читать стандартные файлы в кодировке ASCII или Latin-1, немедленно преобразуя их в Unicode. /В Java используется Unicode 1.1.5 с исправленными  ошибками. Справочная информация приведена в разделе "Библиография"/

В настоящее время лишь немногие текстовые редакторы способны работать с символами Unicode, поэтому Java распознает escape-последовательности вида \udddd, которыми кодируются символы Unicode; каждому d соответствует шестнадцатеричная  цифра (ASCII- символы 0–9, а также a–f или A–F для представления десятичных значений 10–15). Такие последовательности допускаются в любом месте программы — не только в символах и строковых константах, но также и в идентификаторах.  В начале последовательности может стоять несколько u;  записывается и как \u0b87, и как \uu0b87.

следовательно, символ  /Использование "множественных  u" может показаться странной, но на то есть веские причины. При переводе Unicode-файла в формат ASCII, приходится кодировать символы Unicode, лежащие за пределами ASCII-диапазона, в виде escape-

последовательностей. Таким образом,              представляется в виде \u0b87. При обратном переводе осуществляется обратная замена; но что произойдет, если исходный текст в  содержал \u0b87? В этом случае при обратной         кодировке Unicode вместо символа  замене исходный текст изменится (синтаксический  анализатор не заметит никаких изменений – но не читатель программы!) Выход заключается в том, чтобы при прямом переводе вставлять дополнительные  u в уже существующие \udddd, а при обратном – убирать их, и, если u не останется, заменять escape-последовательность эквивалентным символом Unicode./

5.2. Комментарии

Комментарии в Java бывают трех видов:

// комментарий – игнорируются символы от // до конца строки

/* комментарий */      – игнорируются символы между /* и следующим */,

включая

завершающие символы строк \r, \n и \r\n.

/** комментарий */     – игнорируются символы между /** и следующим */,

включая

Документирующие непосредственно после конструктора; они

документацию.

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

Когда мы говорим “символы”, то имеем в виде любые символы Unicode. Комментарии в Java могут включать произвольные символы Unicode: “инь-янь” (\u262f), восклицание (\u203d) или “снеговика” (\u2603).

В Java не разрешаются вложенные комментарии. Приведенный ниже текст (как бы соблазнительно он ни выглядел) компилироваться не будет:

/* Закомментируем  до лучших времен; пока не реализовано

/* Сделать что-нибудь этакое */

universe.neatStuff();

*/

Первая комбинация символов /* начинает комментарий; ближайшая парная */ заканчивает его, оставляя весь последующий код синтаксическому анализатору, который сообщает об оставшихся символах */ как о синтаксической  ошибке. Лучший способ временно убрать фрагмент из программы — либо поместить // в начале каждой строки, либо вставить конструкцию if (false):

if (false) {

// Вызвать метод, когда он будет работать

dwim();

}

Разумеется, данный фрагмент предполагает, что метод dwim определен где-то в другом месте программы.

5.3. Лексемы

Лексемами (tokens) языка называются “слова”, из которых состоит программа. Синтаксический анализатор разбивает исходный текст на отдельные лексемы и пытается понять, из каких операторов, идентификаторов  и т. д. состоит программа. В языке Java символы-разделители (пробелы, табуляция, перевод строки и возврат курсора) применяются исключительно для разделения лексем или содержимого символьных или строковых литералов. Вы можете взять любую работающую программу и заменить произвольное количество символов-разделителей между лексемами (то есть разделителей, не входящих в строки и символы) на другое количество разделителей (не равное нулю) — это никак не повлияет на работу программы.

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

return 0;

нельзя убрать пробел между return и 0, поскольку это приведет к появлению неправильного оператора

return0;

состоящего всего из одного идентификатора return0. Дополнительные  разделители облегчают чтение вашей программы, несмотря на то что синтаксический анализатор их игнорирует. Обратите внимание: комментарии считаются разделителями.

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

j = i+++++i; // НЕВЕРНО неверно интерпретируется  как j = i++ ++ +i; // НЕВЕРНО вместо правильного

j = i++ + ++i;

5.4. Идентификаторы

Идентификаторы  Java, используемые для именования объявленных в программе величин (переменных и констант) и меток, должны начинаться с буквы, символа подчеркивания (_) или знака доллара ($), за которыми следуют буквы или цифры в произвольном порядке. Многим программистам это покажется знакомым, но в связи с тем, что исходные тексты Java-программ пишутся в кодировке Unicode, понятие “буква” или “цифра” оказывается значительно более широким, чем в большинстве языков программирования. “Буквы” в Java могут представлять собой символы из армянского, корейского, грузинского, индийского и практически любого алфавита, который используется в наше время. Следовательно, наряду с идентификатором  kitty можно

пользоваться идентификаторами  maиka, кошка, ,

и             . /Эти слова означают "кошка" или "котенок" на английском, сербо-хорватском,  русском, фарси, тамильском и японском языках соответственно. Если в других языках они имеют иное значение, мы искренне надеемся, что оно не является оскорбительным; в противном случае приносим свои извинения и заверяем, что оскорбление было ненамеренным./ Термины “буква” и “цифра” в Unicode трактуются довольно широко, но если какой-либо символ считается буквой или цифрой в неком языке, то, по всей вероятности, он имеет аналогичный смысл и в Java. Полные определения этих понятий приводятся в таблицах “Цифры Unicode” и “Буквы и цифры Unicode”.

Любые расхождения в символах, входящих в состав идентификаторов,  делают два идентификатора различными. Регистр символов имеет значение:

и т. д. являются разными идентификаторами.  Символы,

которые выглядят одинаково или почти одинаково, нетрудно спутать друг с другом.

Например, латинская заглавная n (N) и греческая заглавная             (N) выглядят практически одинаково, однако им соответствуют разные символы Unicode (\u004e и

\u039d соответственно). Единственная возможность избежать ошибок заключается в том,

чтобы каждый идентификатор был написан только на одном языке (и, следовательно, включал символы известного набора), чтобы программист мог понять, что вы имеете в виду — E или E. /Одна из этих букв входит в кириллицу, а другая – в ASCII. Отличите одну от другой, и вы получите приз./

Идентификаторы в языке Java могут иметь произвольную длину.

5.4.1. Зарезервированные слова Java

Ключевые слова Java не могут использоваться в качестве идентификаторов.  Приведем список ключевых слов Java (слова, помеченные символом *, зарезервированы,  но в настоящее время не применяются):

abstract boolean break byte

double else extends final

int interface long native

super switch synchronized this

case catch char class const*

finally float for goto*

if

new package private protected public

throw throws transient* try

void

continue default do

implements import instanceof

return short static

volatile while

Хотя слова null, true и false внешне похожи на ключевые, формально они относятся к литералам (как, скажем, число 12) и потому отсутствуют в приведенной выше таблице. Тем не менее вы не можете использовать слова null, true и false (как и 12) в качестве идентификаторов,  хотя они и могут входить в состав идентификатора.  Формально null, true и false не являются ключевыми словами, но к ним относятся те же самые ограничения.

5.5. Примитивные типы

Некоторые зарезервированные  слова представляют собой названия типов. В Java

предусмотрены следующие примитивные типы:

boolean либо true, либо false

char   16-разрядный символ Unicode 1.1.5

byte   8-разрядное целое со знаком, дополненное по модулю 2 short  16-разрядное целое со знаком, дополненное по модулю 2 int    32-разрядное целое со знаком, дополненное по модулю 2 long   64-разрядное целое со знаком, дополненное по модулю 2 float  32-разрядное число с плавающей точкой (IEEE 7541985) double 64-разрядное число с плавающей точкой (IEEE 7541985)

Каждому из примитивных типов языка Java, за исключением short и byte, соответствует одноименный класс пакета java.lang. Значения типов short и byte всегда преобразуются в int перед выполнением любых вычислений — приведенный выше формат используется только для хранения, но не для вычислений (см. “Тип выражения”). В классах языка, служащих оболочками для примитивных типов (Boolean, Character, Integer, Long, Float и Double), также определяется ряд полезных констант и методов. Например, в классах- оболочках для некоторых примитивных типов определяются константы MIN_VALUE и MAX_VALUE.

В классах Float и Double определены константы NEGATIVE_INFINITY, POSITIVE_INFINITY  и NaN, а также метод isNaN, который проверяет, не является ли значение с плавающей точкой “не-числом” (Not a Number) — то есть результатом неверной операции, вроде деления на ноль. Значение NaN может использоваться для обозначения недопустимого значения, подобно тому как значение null для ссылок не указывает ни на какой конкретный объект. Классы-оболочки  подробно рассматриваются  в главе 13.

5.6. Литералы

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

5.6.1. Ссылки на объекты

Для ссылок на объекты существует всего один литерал — null. Он может находиться всюду, где допускается использование ссылки. Чаще всего null представляет ссылку на недопустимый или несуществующий  объект. null не относится ни к одному типу, даже к типу Object.

5.6.2. Логические значения

В типе boolean имеются два литерала — true и false.

5.6.3. Целые значения

Целые константы являются последовательностями восьмеричных, десятичных или шестнадцатеричных цифр. Начало константы определяет основание системы счисления: 0 (ноль) обозначает восьмеричное число (основание 8); 0x или 0X обозначает шестнадцатеричное  число (основание 16); любой другой набор цифр указывает на десятичное число (основание 10). Следующие числа имеют одинаковое значение:

29 035 0x1D 0X1d

Целые константы относятся к типу long, если они заканчиваются символом L или l, как

29L; желательно пользоваться L, потому что l легко спутать с 1 (цифрой один). В противном случае считается, что целая константа относится к типу int. Если литерал типа int непосредственно присваивается переменной типа short или byte и его значение находится в пределах диапазона допустимых значений для типа переменной, то операции с литералом осуществляются так, словно он относится к типу short или byte соответственно.

5.6.4. Значения с плавающей точкой

Число с плавающей точкой представляется в виде десятичного числа с необязательной десятичной точкой, за которым (также необязательно) может следовать порядок. Число должно содержать как минимум одну цифру. В конце числа может стоять символ F или f для обозначения константы с одинарной точностью или же символ d или D для обозначения константы с двойной точностью. Следующие литералы обозначают одно и то же значение:

18. 1.8e1 .18E2

Константы с плавающей точкой относятся к типу double, если только они не завершаются символом f или F — в этом случае они имеют тип float, как константа 18.0f. Завершающий символ D или d определяет константу типа double. Ноль может быть положительным  (0.0) или отрицательным (-0.0). Положительный ноль равен отрицательному, но при использовании в некоторых выражениях они могут приводить к различным результатам. Например, выражение 1d/0d равно +, а 1d/-0d равно –.

Константа типа double не может присваиваться переменной типа float, даже если ее значение лежит в пределах диапазона float. Для присваивания значений переменным и полям типа float следует использовать константы типа float или привести double к float.

5.6.5. Символы

Символьные литералы заключаются в апострофы — например, ‘Q’. Некоторые служебные символы могут представляться в виде escape-последовательностей. К их числу относятся:

\n переход на новую строку (\u000A)

\t табуляция (\u0009)

\b забой (\u0008)

\r ввод (\u000D)

\f подача листа (\u000C)

\\ обратная косая черта (\u005C)

\’ апостроф (\u0027)

\" кавычка (\u0022)

\ddd символ в восьмеричном представлении, где каждое d соответствует цифре от 0 до 7

Восьмеричные символьные константы могут состоять из трех или менее цифр и не могут превышать значения \377 (\u00ff). Символы, представленные в шестнадцатеричном  виде, всегда должны состоять из четырех цифр.

5.6.6. Строки

Строковые литералы заключаются в двойные кавычки: “along”. В них могут входить любые escape-последовательности, допустимые в символьных константах. Строковые литералы являются объектами типа String. Более подробно о строках рассказывается в главе 8.

Символы перехода на новую строку не могут находиться в середине строковых литералов. Если вы хотите вставить такой символ в строку, воспользуйтесь escape- последовательностью \n.

В строках может применяться восьмеричная запись символов, но для предотвращения путаницы (в тех случаях, когда символы, представленные  таким образом, соседствуют с другими символами) необходимо указывать все три восьмеричные цифры. Например, строка “\0116" эквивалентна строке ”\t6", тогда как строка “\116" эквивалентна ”N".

5.7. Объявления переменных

В объявлении указывается тип, уровень доступа и другие атрибуты идентификатора. Объявление состоит из трех частей: сначала приводится список модификаторов, за ним следует тип, и в завершение следует список идентификаторов.

Модификаторы могут отсутствовать в объявлении переменной. Модификатор static объявляет, что переменная сохраняет свое значение после выхода из метода; модификатор final объявляет, что значение переменной присваивается всего один раз, при ее инициализации. Модификатор final может использоваться только для полей.

Тип в объявлении указывает на то, какие значения могут принимать объявляемые величины и как они должны себя вести.

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

float[] x, y; равносильно float[] x; float[] y;

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

обязаны ставить их в начале класса, метода или блока. В общем случае,

идентификатором  можно пользоваться в любой момент после его объявления в некотором блоке (см. раздел “Операторы и блоки”), за одним исключением: нестатические поля недоступны в статических методах.

Поля с модификатором  final должны инициализироваться при объявлении.

Объявлению члена класса может предшествовать один из нескольких модификаторов. Модификаторы могут следовать в произвольном порядке, но мы рекомендуем выработать некоторое соглашение и придерживаться его. В этой книге используется следующий порядок: сначала следуют модификаторы доступа (public, private или protected), затем static, затем synchronized, и, наконец, final. Использование  единого порядка модификаторов облегчает чтение исходного текста программы.

5.7.1. Значение имени

Каждый созданный идентификатор существует в некотором пространстве имен (namespace). Имена идентификаторов  в пределах одного пространства имен должны различаться. Когда вы используете идентификатор для того, чтобы присвоить имя переменной, классу или методу, то для определения значения имени производится поиск в следующем порядке:

1. Локальные переменные, объявленные в блоке, цикле for или среди параметров обработчика исключений. Блок представляет собой один или несколько операторов, заключенных в фигурные скобки. Переменные также могут объявляться во время инициализации в цикле for.

2. Параметры метода или конструктора, если код входит в метод или конструктор.

3. Члены данного класса или интерфейсного типа, то есть его поля и методы, в том числе все унаследованные  члены.

4. Импортированные  типы с явным именованием.

5. Другие типы, объявленные в том же пакете.

6. Импортированные  типы с неявным именованием.

7. Прочие пакеты, доступные в системе.

В каждом из вложенных блоков или операторов for могут объявляться новые имена.

Чтобы избежать путаницы, вы не можете воспользоваться  вложением для переобъявления параметра или идентификатора из внешнего блока или оператора for. Так, после появления локального идентификатора или параметра с именем ?ber вы не можете

создать во вложенном блоке новый, отличный от него идентификатор с тем же именем

?ber.

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

class Reuse {

Reuse Reuse(Reuse Reuse) { Reuse:

for (;;) {

if (Reuse.Reuse(Reuse) == Reuse)

break Reuse;

}

return Reuse;

}

}

Описанный выше порядок просмотра означает, что идентификаторы, объявленные внутри метода, могут скрывать внешние идентификаторы. Обычно скрытие идентификаторов считается проявлением плохого стиля программирования,  поскольку при чтении программы приходится просматривать все уровни иерархии, чтобы выяснить, какая же переменная используется в том или ином случае.

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

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

5.8. Массивы

Массив представляет собой упорядоченный набор элементов. Элементы массива могут иметь примитивный тип или являться ссылками на объекты, в том числе и ссылками на другие массивы. Строка

int[] ia = new int[3];

объявляет массив с именем ia, в котором изначально хранится три значения типа int.

При объявлении переменной-массива размер не указывается. Количество элементов в массиве задается при его создании оператором new, а не при объявлении. Размер объекта-массива  фиксируется в момент его создания и не может изменяться в дальнейшем. Обратите внимание: фиксируется именно размер объекта-массива; в приведенном выше примере ia может быть присвоена ссылка на любой массив другого размера.

Первый элемент массива имеет индекс 0 (ноль), а последний — индекс размер–1. В нашем примере последним элементом массива является ia[2]. При каждом использовании индекса проверяется, лежит ли он в диапазоне допустимых значений. При выходе индекса за его пределы возбуждается исключение IndexOutOfBounds.

Размер массива можно получить из поля length. Для нашего примера следующий фрагмент программы перебирает все элементы массива и выводит каждое значение:

for (int i =0; i << ia.length; i++) System.out.println(i + ": " + ia[i]);

Массивы всегда являются неявным расширением класса Object. Если у вас имеется класс X, расширяющий его класс Y и массивы каждого из этих классов, то иерархия будет выглядеть следующим образом:

Благодаря этому обстоятельству массивы ведут себя полиморфно. Вы можете присвоить массив переменной типа Object, после чего осуществить обратное преобразование. Массив объектов типа Y допускается использовать всюду, где разрешено присутствие массива объектов базового типа X.

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

Основное ограничение на “объектность” массивов заключается в том, что они не могут расширяться для включения в них новых методов. Так, следующая конструкция является недопустимой:

class ScaleVector extends double[] { //

// …

}

При объявлении массива объектов вы на самом деле объявляете массив переменных соответствующего  типа. Рассмотрим следующий фрагмент:

Attr[] attrs = new Attr[12];

for (int i = 0; i << attrs.length; i++)

attrs[i] = new Attr(names[i], values[i]);

После выполнения первого оператора new, attrs содержит ссылку на массив из 12 переменных, инициализированных значением null. Объекты Attr создаются только при выполнении цикла.

Если вы пожелаете, Java допускает присутствие квадратных скобок после переменной, а не после типа, как в следующем объявлении:

int ia[] = new int[3];

Оно эквивалентно приведенному выше. Тем не менее первый вариант все же считается более предпочтительным,  поскольку тип объявляется в одном месте.

5.8.1. Многомерные массивы

Элементами массивов в Java могут быть другие массивы. Например, фрагмент программы для объявления и вывода двумерной матрицы может выглядеть следующим образом:

float[][] mat = new float[4][4];

setupMatrix(mat);

for (int y = 0; y << mat.length; y++) {

for (int x = 0; x << mat[y].length; x++) System.out.println(mat[x][y] + " ");

System.out.println();

}

Первый (левый) размер массива должен задаваться при его создании. Другие размеры могут указываться позже. Использование более чем одной размерности является сокращением для вложенного набора операторов new. Приведенный выше массив может быть создан следующим образом:

float[][] mat = new float[4][];

for (int y = 0; y << mat.length; y++)

mat[y] = new float[4];

Одно из преимуществ многомерных массивов состоит в том, что каждый вложенный

массив может иметь свои размеры. Вы можете имитировать работу с массивом 4×4, но при этом создать массив из четырех массивов типа int, каждый из которых имеет свою собственную длину, необходимую для хранения его данных.

Упражнение 5.1

Напишите программу, которая строит треугольник Паскаля до глубины 12. Каждый числовой ряд треугольника сохраняется в массиве соответствующей  длины, а массивы рядов заносятся в массив, элементами которого являются 12 массивов типа int.

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

5.9. Инициализация

Переменная может инициализироваться при ее объявлении. Чтобы задать начальное значений переменной, следует после ее имени поставить = и выражение:

final double p = 3.14159;

float radius = 1.0f;    // начать с единичного радиуса

Если при объявлении поля класса не инициализируются,  то Java присваивает им исходные значения по умолчанию. Значение по умолчанию зависит от типа поля:

Тип поля

Тип поля

boolean

false

char

‘\u0000’

целое (byte, short, int, long)

0

с плавающей точкой

+0.0f или +0.0d

ссылка на объект

null

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

Момент инициализации переменной зависит от ее области видимости. Локальные переменные инициализируются  каждый раз, когда выполняется их объявление. Поля объектов и элементы массивов инициализируются  при создании объекта или массива оператором new — см. “Порядок вызова конструкторов”. Инициализация статических переменных класса происходит перед выполнением какого-либо кода, относящегося к данному классу.

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

5.9.1. Инициализация массивов

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

String[] dangers = { "Lions", "Tigers", "Bears" };

Это равносильно следующему фрагменту:

String[] dangers = new String[3];

dangers[0] = "Lions";

dangers[1] = "Tigers";

dangers[2] = "Bears";

Для инициализации многомерных массивов может использоваться вложение инициализаторов  отдельных массивов. Приведем объявление, в котором инициализируется  матрица размеров 4×4:

double[][] identityMatrix = {

{ 1.0, 0.0, 0.0, 0.0 },

{ 0.0, 1.0, 0.0, 0.0 },

{ 0.0, 0.0, 1.0, 0.0 },

{ 0.0, 0.0, 0.0, 1.0 },

};

Источник: Арнольд К., Гослинг Д. – Язык программирования Java (1997)

По теме:

  • Комментарии