Вступление
Знаете ли вы, что следующее является допустимым выражением Java?\u0069\u006E\u0074 \u0069 \u003D \u0038\u003B
Вы можете попробовать скопировать и вставить его в основной метод любого класса и скомпилировать. Если вы затем добавите следующий оператор
System.out.println(i);
и после компиляции запустите этот класс, код напечатает число 8!
А знаете ли вы, что этот комментарий вместо этого вызывает синтаксическую ошибку во время компиляции?
/*
* The file will be generated inside the C:\users\claudio folder
*/
Тем не менее, комментарии не должны приводить к синтаксическим ошибкам. Фактически, программисты часто комментируют фрагменты кода, чтобы компилятор их игнорировал... так что же происходит?
Для того, чтобы узнать почему это происходит, потратьте несколько минут на небольшой обзор основ Java о примитивном типе char.
Примитивный тип данных char
Как всем известно, charэто один из восьми примитивных типов Java. Это позволяет нам хранить по одному символу. Ниже приведен простой пример, в котором значение символа присваивается типу char:char aCharacter = 'a';
На самом деле этот тип данных используется нечасто, потому что в большинстве случаев программистам нужны последовательности символов и поэтому они предпочитают строки. Каждое буквальное значение символа должно быть заключено между двумя одинарными кавычками, чтобы не путать с двойными кавычками, используемыми для строковых литералов. Объявление строки:
String s = "Java melius semper quam latinam linguam est";
Есть три способа присвоить литералу значение типа char, и все три требуют включения значения в одинарные кавычки:
- используя один печатный символ на клавиатуре (например '&').
- используя формат Unicode с шестнадцатеричной нотацией (например, '\u0061', который эквивалентен десятичному числу 97 и идентифицирует символ 'a').
- используя специальный escape-символ (например, '\n'который указывает символ перевода строки).
Печатаемые символы клавиатуры
Мы можем назначить любой символ, найденный на нашей клавиатуре, charпеременной, при условии, что наши системные настройки поддерживают требуемый символ и что этот символ доступен для печати (например, клавиши «Canc» и «Enter» не печатаются). В любом случае литерал, присваиваемый примитивному типу char, всегда заключен между двумя одинарными кавычками. Вот некоторые примеры:char aUppercase = 'A';
char minus = '-';
char at = '@';
Тип данных charхранится в 2 байтах (16 бит), а диапазон состоит только из положительных чисел от 0 до 65 535. Фактически, существует «отображение», которое связывает определенный символ с каждым числом. Это отображение (или кодирование) определяется стандартом Unicode (более подробно описанным в следующем разделе).
Формат Unicode (шестнадцатеричное представление)
Мы сказали, что примитивный тип charхранится в 16 битах и может определять до 65 536 различных символов. Кодирование Unicode занимается стандартизацией всех символов (а также символов, смайликов, идеограмм и т. д.), существующих на этой планете. Unicode - это расширение кодировки, известной как UTF-8, которая, в свою очередь, основана на старом 8-битном расширенном стандарте ASCII, который, в свою очередь, содержит самый старый стандарт, ASCII code (аббревиатура от American Standard Code for Information Interchange).Мы можем напрямую присвоить Unicode charзначение в шестнадцатеричном формате, используя 4 цифры, которые однозначно идентифицируют данный символ, добавляя к нему префикс \u(всегда в нижнем регистре). Например:
char phiCharacter = '\u03A6'; // Capital Greek letter Φ
char nonIdentifiedUnicodeCharacter = '\uABC8';
В данном случае мы говорим о литерале в формате Unicode (или литерале в шестнадцатеричном формате). Фактически, при использовании 4 цифр в шестнадцатеричном формате охватывается ровно 65 536 символов.
Java 15 поддерживает Unicode версии 13.0, которая содержит намного больше символов, чем 65 536 символов. Сегодня стандарт Unicode сильно изменился и теперь позволяет нам представлять потенциально более миллиона символов, хотя уже присвоено только 143 859 чисел конкретным символам. Но стандарт постоянно развивается. В любом случае, для присвоения значений Unicode, выходящих за пределы 16-битного диапазона типа char, мы обычно используем классы вроде String и Character, но поскольку это очень редкий случай и не интересен для целей этой статьи, мы не будем об этом говорить.
Специальные escape-символы
В charтипе также можно хранить специальные escape-символы, то есть последовательности символов, которые вызывают определенное поведение при печати:- \bэквивалентно backspace, отмене слева (эквивалентно клавише Delete).
- \nэквивалентно переводу строки (эквивалентно клавише Ente).
- \\равняется только одному \ (только потому, что символ \ используется для escape-символов).
- \tэквивалентно горизонтальной табуляции (эквивалентно клавише TAB).
- \' эквивалентно одинарной кавычке (одинарная кавычка ограничивает литерал символа).
- \" эквивалентно двойной кавычке (двойная кавычка ограничивает литерал строки).
- \rпредставляет собой возврат каретки (специальный символ, который перемещает курсор в начало строки).
- \fпредставляет собой подачу страницы (неиспользуемый специальный символ, представляющий курсор, перемещающийся на следующую страницу документа).
System.out.println('"');
что эквивалентно следующему коду:
char doubleQuotes = '"';
System.out.println(doubleQuotes);
правильно и напечатает символ двойной кавычки:
"
Если бы мы попытались не использовать escape-символ для одиночных кавычек, например, со следующим утверждением:
System.out.println(''');
мы получим следующие ошибки времени компиляции, поскольку компилятор не сможет различить разделители символов:
error: empty character literal
System.out.println(''');
^
error: unclosed character literal
System.out.println(''');
^
2 errors
Поскольку разделители строковых литералов представлены в двойных кавычках, ситуация обратная. Фактически, внутри строки можно заключить одинарные кавычки:
System.out.println("'IQ'");
который напечатает:
'IQ'
С другой стороны, мы должны использовать \" escape-символ, чтобы использовать двойные кавычки в строке. Итак, следующее утверждение:
System.out.println(""IQ"");
вызовет следующие ошибки компиляции:
error: ')' expected
System.out.println(""IQ"");
^
error: ';' expected
System.out.println(""IQ"");
^
2 errors
Вместо этого верна следующая инструкция:
System.out.println("\"IQ\"");
и напечатает:
"IQ"
Написание Java кода в формате Unicode
Литеральный формат Unicode также можно использовать для замены любой строки нашего кода. Фактически, компилятор сначала преобразует формат Unicode в символ, а затем оценивает синтаксис. Например, мы могли бы переписать следующий оператор:int i = 8;
следующим образом:
\u0069\u006E\u0074 \u0069 \u003D \u0038\u003B
Фактически, если мы добавим к предыдущей строке следующий оператор:
System.out.println("i = " + i);
он напечатает:
i = 8
Несомненно, это бесполезный способ написания нашего кода. Но может быть полезно знать эту функцию, поскольку она позволяет нам понять некоторые ошибки, которые (редко) случаются.
Формат Unicode для escape-символов
Тот факт, что компилятор преобразует шестнадцатеричный формат Unicode перед оценкой кода, имеет некоторые последствия и оправдывает существование escape-символов. Например, давайте рассмотрим символ перевода строки, который можно представить с помощью escape-символа \n. Теоретически перевод строки связан в кодировке Unicode с десятичным числом 10 (что соответствует шестнадцатеричному числу A). Но, если мы попытаемся определить его в формате Unicode:char lineFeed = '\u000A';
мы получим следующую ошибку времени компиляции:
error: illegal line end in character literal
char lineFeed = '\u000A';
^
1 error
В реальности, компилятор преобразует предыдущий код в следующий перед его оценкой:
char lineFeed = '
';
Формат Unicode был преобразован в символ новой строки, и предыдущий синтаксис не является допустимым синтаксисом для компилятора Java.
Аналогично, символ одинарной кавычки ', который соответствует десятичному числу 39 (эквивалентно шестнадцатеричному числу 27) и который мы можем представить с помощью escape-символа \', не может быть представлен в формате Unicode:
char singleQuote = '\u0027';
Также в этом случае компилятор преобразует предыдущий код следующим образом:
char singleQuote = ''';
что приведет к следующим ошибкам времени компиляции:
error: empty character literal
char singleQuote = '\u0027';
^
error: unclosed character literal
char singleQuote = '\u0027';
^
2 errors
Первая ошибка связана с тем, что первая пара кавычек не содержит символа, а вторая ошибка указывает на то, что указание третьей одинарной кавычки является незакрытым символьным литералом.
Также есть проблемы с символом возврата каретки, представленным шестнадцатеричным числом D (соответствующим десятичному числу 13) и уже представленным с помощью escape-символа \r. Фактически, если мы напишем:
char carriageReturn = '\u000d';
мы получим следующую ошибку времени компиляции:
error: illegal line end in character literal
char carriageReturn = '\u000d';
^
1 error
Фактически, компилятор преобразовал число в формате Unicode в возврат каретки, вернув курсор в начало строки, и то, что должно было быть второй одинарной кавычкой, стало первой.
Что касается символа ,, представленного десятичным числом 92 (соответствующего шестнадцатеричному числу 5C) и представленного escape-символом \, если мы напишем:
char backSlash = '\u005C';
мы получим следующую ошибку времени компиляции:
error: unclosed character literal
char backSlash = '\u005C';
^
1 error
Это потому, что предыдущий код будет преобразован в следующий:
char backSlash = '\';
и поэтому пара символов ' рассматривается как escape-символ, соответствующий одинарной кавычке, и поэтому в буквальном закрытии отсутствует другая одинарная кавычка.
С другой стороны, если мы рассмотрим символ ", представленный шестнадцатеричным числом 22 (соответствующий десятичному числу 34) и представленный escape-символом ", если мы напишем:
char quotationMark = '\u0022';
проблем не будет. Но если мы используем этот символ внутри строки:
String quotationMarkString = "\u0022";
мы получим следующую ошибку времени компиляции:
error: unclosed string literal
String quotationMarkString = "\u0022";
^
1 error
поскольку предыдущий код будет преобразован в следующий:
String quotationMarkString = """;
Тайна ошибки комментария
Еще более странная ситуация возникает при использовании однострочных комментариев для форматов Unicode, таких как возврат каретки или перевод строки. Например, несмотря на то, что оба следующих оператора закомментированы, могут возникнуть ошибки во время компиляции!// char lineFeed = '\u000A';
// char carriageReturn = '\u000d';
Это связано с тем, что компилятор всегда преобразует шестнадцатеричные форматы с помощью символов перевода строки и возврата каретки, которые несовместимы с однострочными комментариями; они печатают символы вне комментария!
Чтобы разрешить ситуацию, используйте обозначение многострочного комментария, например:
/* char lineFeed = '\u000A';
char carriageReturn = '\u000d'; */
Другая ошибка, из-за которой программист может потерять много времени, - это использование последовательности \uв комментарии. Например, со следующим комментарием мы получим ошибку времени компиляции:
/*
* The file will be generated inside the C:\users\claudio folder
*/
Если компилятор не находит допустимую последовательность из 4 шестнадцатеричных символов после \u, он выведет следующую ошибку:
error: illegal unicode escape
* The file will be generated inside the C:\users\claudio folder
^
1 error
Выводы
В этой статье мы увидели, что использование типа charв Java скрывает некоторые действительно удивительные особые случаи. В частности, мы увидели, что можно писать код Java, используя формат Unicode. Это связано с тем, что компилятор сначала преобразует формат Unicode в символ, а затем оценивает синтаксис. Это означает, что программисты могут находить синтаксические ошибки там, где они никогда не ожидали, особенно в комментариях.Примечание автора: эта статья представляет собой короткий отрывок из раздела 3.3.5 «Примитивные символьные типы данных» тома 1 моей книги «Java для пришельцев». Для получения дополнительной информации посетите сайт книги (вы можете загрузить раздел 3.3.5 из области «Примеры»).
Источник статьи: https://habr.com/ru/post/564218/