→ Пошук по сайту       Увійти / Зареєструватися
Знання Програмування на Java Навчальний курс по JAVA

Лексика мови

 Java

Кодування

Технологія Java, як платформа, спочатку спроектована для Глобальної мережі Internet, повинна бути багатомовною, а значить, звичайний набір символів ASCII (American Standard Code for Information Interchange, Американський стандартний код обміну інформацією), що включає в себе лише латинський алфавіт, цифри і найпростіші спеціальні знаки (дужки, знаки пунктуації, арифметичні операції і т.д.), недостатній. Тому для запису тексту програми застосовується більш універсальне кодування Unicode.

Як відомо, Unicode представляє символи кодом з 2 байт, описуючи, таким чином, 65535 символів. Це дозволяє підтримувати практично всі поширені мови світу. Перші 128 символів збігаються з набором ASCII. Однак зрозуміло, що потрібен якийсь спеціальне позначення, щоб мати можливість задавати в програмі будь-який символ Unicode, адже ніяка клавіатура не дозволяє вводити більше 65 тисяч різних знаків. Ця конструкція представляє символ Unicode, використовуючи тільки символи ASCII. Наприклад, якщо в програму потрібно вставити знак з кодом 6917, необхідно його представити в шістнадцятковому форматі (1B05) і записати  u1B05, причому літера u повинна бути малої, а шістнадцяткові цифри A, B, C, D, E, F можна використовувати довільно, як великі, так і малі. Таким чином можна закодувати всі символи Unicode від \ u0000 до \ uFFFF. Літери російського алфавіту починаються з \ u0410 (тільки буква Е має код \ u0401) по \ u044F (код літери е \ u0451). В останніх версіях JDK до складу демонстраційних додатків і аплетів входить невелика програма SymbolTest, що дозволяє переглядати весь набір символів Unicode. Її аналог нескладно написати самостійно. Для перекодування великих текстів служить утиліта native2ascii, що також входить в JDK. Вона може працювати як в прямому режимі - переводити з різноманітних кодувань в Unicode, записаний ASCII-символами, так і в зворотному (опція-reverse) - з Unicode в стандартну кодування операційної системи.

У версіях мови Java до 1.1 застосовувався Unicode версії 1.1.5, в останньому випуску 1.4 використовується 3.0. Таким чином, Java стежить за розвитком стандарту і базується на сучасних версіях. Для будь-якої JDK точну версію Unicode, використовувану в ній, можна дізнатися з документації до класу Character. Офіційний web-сайт стандарту, де можна отримати додаткову інформацію, - http://www.unicode.org/.

Отже, використовуючи найпростішу кодування ASCII, можна ввести довільну послідовність символів Unicode. Далі буде показано, що Unicode використовується не для всіх лексем, а тільки для тих, для яких важлива підтримка багатьох мов, а саме: коментарі, ідентифікатори, символьні і рядкові літерали. Для запису інших лексем цілком достатньо ASCII-символів.

Аналіз програми

Компілятор, аналізуючи програму, відразу поділяє його на:

  • пробіли (white spaces);
  • коментарі (comments);
  • основні лексеми (tokens).

Пробіли

Пробілами в даному випадку називають всі символи, що розбивають текст програми на лексеми. Це як сам символ пробілу (space, \ u0020, десятковий код 32), так і знаки табуляції та переведення рядка. Вони використовуються для розділення лексем, а також для оформлення коду, щоб його було легше читати. Наприклад, наступну частину програми (обчислення коренів квадратного рівняння):

 double a = 1, b = 1, c = 6;
double D = b * b - 4 * a * c;
if (D> = 0) {
   double x1 = (-b + Math.sqrt (D)) / (2 * a);
   double x2 = (-b - Math.sqrt (D)) / (2 * a);
}

можна записати і в такому вигляді:

 double a = 1, b = 1, c = 6; double D = b * b-4 * a * c; if (D> = 0)
{Double x1 = (-b + Math.sqrt (D)) / (2 * a); double
x2 = (-b-Math.sqrt (D)) / (2 * a);}

В обох випадках компілятор згенерує абсолютно однаковий код. Єдине міркування, яким повинен керуватися розробник, - легкість читання та подальшої підтримки такого коду.

Для розбиття тексту на рядки в ASCII використовується два символи - "повернення каретки" (carriage return, CR, \ u000d, десятковий код 13) і символ нового рядка (linefeed, LF, \ u000a, десятковий код 10). Щоб не залежати від особливостей використовуваної платформи, в Java застосовується найбільш гнучкий підхід. Завершенням рядка вважається:

  • ASCII-символ LF, символ нового рядка;
  • ASCII-символ CR, "повернення каретки";
  • символ CR, за яким одразу ж слід символ LF.

Розбиття на рядки важливо для коректного розбиття на лексеми (як вже говорилося, завершення рядка також служить роздільником між лексемами), для правильної роботи з рядковими коментарями (див. наступну тему "Коментарі"), а також для виведення налагоджувальної інформації (при виведенні помилок компіляції і часу виконання вказується, на якому рядку вихідного коду вони виникли). Отже, пробілами в Java вважаються:

  • ASCII-символ SP, space, пробіл, \ u0020, десятковий код 32;
  • ASCII-символ HT, horizontal tab, символ горизонтальної табуляції, \ u0009, десятковий код 9;
  • ASCII-символ FF, form feed, символ перекладу сторінки (був введений для роботи з принтером), \ u000c, десятковий код 12;
  • завершення рядка.

Коментарі

Коментарі не впливають на результуючий бінарний код і використовуються тільки для введення пояснень до програми.

У Java коментарі бувають двох видів:

  • рядкові
  • блокові

Рядкові коментарі починаються з ASCII-символів / / і тривають до кінця поточного рядка. Як правило, вони використовуються для пояснення саме цього рядка, наприклад:

 int y = 1970; // рік народження 

Блокові коментарі розташовуються між ASCII-символами / * і * /, можуть займати будь-яку кількість рядків, наприклад:

  / * 
   Цей цикл не може починатися з нуля
   через особливості алгоритму
* / 
for (int i = 1; i <10; i + +) {
...
}

Часто блокові коментарі оформляють таким чином (кожний рядок починається з *):

 / *
   * Опис алгоритму роботи
   * Наступного циклу while
   * /
while (x> 0) {
   ...
}

Блоковий коментар не обов'язково повинен розташовуватися на декількох рядках, він може навіть перебувати в середині оператора:

 float s = 2 * Math.PI / * getRadius ()*/;
// Закоментований для налагодження

У цьому прикладі блочний коментар розбиває арифметичні операції. Вираз Math.PI надає значення константи PI, визначене в класі Math. Виклик методу getRadius () тепер закоментований і не буде зроблений, мінлива s завжди буде приймати значення 2 PI. Завершує рядок рядковий коментар.

Коментарі не можуть перебувати в символьних і рядкових літералах, ідентифікаторах (ці поняття докладно розглядаються далі в цій лекції). Наступний приклад містить випадки неправильного застосування коментарів:

 // У цьому прикладі текст / * ... * / стане просто
// Частиною рядка s
String s = "text / * just text * /";
/ *
   Наступний рядок стане причиною помилки
   при компіляції, так як коментар розбив
   ім'я методу getRadius ()
* /
circle.get / * comment * / Radius ();

А такий код можливий:

 // Коментар може розділяти виклики функцій:
circle. / * comment * / getRadius ();
// Коментар може замінювати пробіли:
int / * comment * / x = 1; 

В останньому рядку між назвою типу даних int і назвою змінної x обов'язково повинен бути пробіл або, як в даному прикладі, коментар.

Коментарі не можуть бути вкладеними. Символи / *, * /, // не мають ніякого особливого значення всередині вже відкритих коментарів, як малих, так і блокових. Таким чином, у прикладі

 / * Початок коментаря / * / // ** завершення: * /

описаний тільки один блоковий коментар. А в наступному прикладі (рядки коду пронумеровані для зручності)

 1. /*
2. comment
3. /*
4. more comments
5. */
6. finish
7. */ 

компілятор видасть помилку. Блоковий коментар почався в рядку 1 з комбінації символів / *. Друга відкриває комбінація / * на рядку 3 буде проігнорована, так як знаходиться вже всередині коментаря. Символи * / у рядку 5 завершать його, а рядок 7 породить помилку - спроба закрити коментар, який не був початий.

Будь-які коментарі повністю видаляються з програми під час компіляції, тому їх можна використовувати необмежено, не побоюючись, що це вплине на бінарний код. Основне їх призначення - зробити програму простою для розуміння, в тому числі і для інших розробників, яким доведеться в ній розбиратися з якої-небудь причини. Також коментарі найчастіше використовуються для тимчасового виключення частин коду, наприклад:

 int x = 2;
int y = 0;
/*
if (x> 0)
   y = y + x * 2;
else
   y =-y - x * 4;
*/
y = y * y; // + 2 * x;

У цьому прикладі закоментований вираз if-else і оператор складання +2 * x.

Як вже говорилося вище, коментарі можна писати символами Unicode, тобто будь-якою мовою, зручному розробнику.

Крім цього, існує особливий вид блокового коментарю - коментар розробника. Він застосовується для автоматичного створення документації коду. У стандартну поставку JDK, починаючи з версії 1.0, входить спеціальна утиліта javadoc. На вхід їй подається вихідний код класів, а на виході виходить зручна документація в HTML-форматі, яка описує всі класи, всі їх поля і методи. При цьому активно використовуються гіперпосилання, що істотно спрощує вивчення програми (наприклад, читаючи опис методу, можна за допомогою одного натискання миші перейти на опис типів, що використовуються в якості аргументів чи значення, що повертається). Однак зрозуміло, що однієї назви методу і перерахування його аргументів недостатньо для розуміння його роботи. Необхідні додаткові пояснення від розробника.

Коментар розробника записується так само, як і блоковий. Єдина відмінність в початковій комбінації символів - для документації коментар необхідно починати з / **.  Наприклад:

 /**
   * Обчислення модуля цілого числа.
   * Цей метод повертає
   * Абсолютне значення аргументу x.
   */
int getAbs (int x) {
   if (x> = 0)
      return x;
   else
      return-x;
}

Перше речення повинне містити коротке резюме всього коментаря. Надалі воно буде використано як пояснення цієї функції у списку всіх методів класу (нижче будуть описані всі конструкції мови, для яких застосовується коментар розробника).

Оскільки в результаті створюється HTML-документація, то і коментар необхідно писати за правилами HTML. Допускається застосування тегів, таких як <b> і <p>. Однак теги заголовків з <h1> по <h6> і <hr> використовувати не можна, так як вони активно застосовуються javadoc для створення структури документації.

Символ * на початку кожного рядка і попередні пробіли та знаки табуляції ігноруються. Їх можна не використовувати взагалі, але вони зручні, коли необхідно форматування, скажімо, в прикладах коду.

 /**
   * Перша речення - короткий
   * Опис методу.
   * <p>
   * Так оформляється приклад коду:
   * <blockquote>
   * <pre>
   * If (condition == true) {
   * X = getWidth ();
   * Y = x.getHeight ();
   *}
   * </ Pre> </ blockquote>
   * А так описується HTML-список:
   * <ul>
   * <li> Можна використовувати похилий шрифт
   * <i> Курсив </ i>,
   * <li> Або жирний <b> жирний </ b>.
   * </ Ul>
   * /
 public void calculate (int x, int y) {
   ...
}

З цього коментаря буде згенерований HTML-код, який виглядає приблизно так:

Перша пропозиція - короткий опис методу.

Так оформляється приклад коду:

 if (condition == true) {
   x = getWidth ();
   y = x.getHeight ();
}

А так описується HTML-список:

  • Можна використовувати похилий шрифт курсив,
  • або жирний жирний.

Нарешті, javadoc підтримує спеціальні теги. Вони починаються з символу @. Детальний опис цих тегів можна знайти в документації. Наприклад, можна використовувати тег @ see, щоб послатися на інший клас, поле або метод, або навіть на іншій Internet-сайт.

 /**
   * Короткий опис.
   *
   * Розгорнутий коментар.
   *
   * @ See java.lang.String
   * @ See java.lang.Math # PI
   * @ See <a href="java.sun.com"> Official
   * Java site </ a>
   */

Перше посилання вказує на клас String (java.lang - назва бібліотеки, в якій знаходиться цей клас), друга - на поле PI класу Math (символ # розділяє назву класу і його полів або методів), третя посилається на офіційний сайт Java.

Коментарі розробника може бути записані перед оголошенням класів, інтерфейсів, полів, методів і конструкторів. Якщо записати коментар / ** ... * / в іншій частині коду, то помилки не буде, але він не потрапить в документацію, що генерується javadoc. Крім того, можна описати пакет (так називаються бібліотеки, або модулі, в Java). Для цього необхідно створити спеціальний файл package.html, зберегти в ньому коментар і помістити його в каталог пакета. HTML-текст, що міститься між тегами <body> і </ body>, буде поміщений в документацію, а перше речення буде використовуватися для короткої характеристики цього пакета.

Лексеми

 Отже, ми розглянули пробіли (в широкому сенсі цього слова, тобто всі символи, що відповідають за форматування тексту програми) та коментарі, що застосовуються для введення пояснень до коду. З точки зору програміста вони застосовуються для того, щоб зробити програму більш читабельною і зрозумілою для подальшого розвитку.

З точки зору компілятора, а точніше його частини, що відповідає за лексичний розбір, основна роль пропусків і коментарів - служити роздільниками між лексемами, причому самі роздільники далі відкидаються і на скомпільований код не впливають. Наприклад, всі наступні приклади оголошення змінної еквівалентні:

 // Використовуємо пробіл як роздільник.
int x = 3;
// Тут роздільником є переведення рядка
int
x
=
3
;
// Тут поділяємо знаком табуляції
int x = 3;
/*
   * Єдиний принципово необхідний
   * Роздільник між назвою типу даних
   * Int і ім'ям змінної x тут описаний
   * Коментарем блокового типу.
   */
int /**/ x = 3;

Звичайно, лексеми дуже різноманітні, і саме вони визначають багато властивостей мови. Розглянемо всі їх види більш детально.

Види лексем

Нижче перераховані всі види лексем в Java:

  • ідентифікатори (identifiers);
  • ключові слова (key words);
  • літерали (literals);
  • роздільники (separators);
  • оператори (operators).

Розглянемо їх окремо.

Ідентифікатори

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

Ідентифікатор складається з букв і цифр. Ім'я не може починатися з цифри. Java-букви, що використовуються в ідентифікаторах, включають в себе ASCII-символи AZ (\ u0041-\ u005a), az (\ u0061-\ u007a), а також знаки підкреслення _ (ASCII underscore, \ u005f) і долара $ (\ u0024 ). Знак долара використовується тільки при автоматичній генерації коду (щоб виключити випадковий збіг імен), або при використанні будь-яких старих бібліотек, в яких допускалися імена з цим символом. Java-цифри включають у себе звичайні ASCII-цифри 0-9 (\ u0030-\ u0039).

Для ідентифікаторів не допускаються збіги з зарезервованими словами (це ключові слова, булевські літерали true і false і null-літерал null). Звичайно, якщо 2 ідентифікатора включають в себе різні літери, які однаково виглядають (наприклад, латинська і російська букви A), то вони вважаються різними.

У цій лекції вже застосовувалися наступні ідентифікатори:

  • Character, a, b, c, D, x1, x2, Math, sqrt, x,
  • y, i, s, PI, getRadius, circle, getAbs,
  • calculate, condition, getWidth, getHeight,
  • java, lang, String

Також допустимими є ідентифікатори:

  • Computer, COLOR_RED, _, aVeryLongNameOfTheMethod

Ключові слова

Ключові слова - це зарезервовані слова, що складаються з ASCII-символів і виконують різні завдання мови. Ось їх повний список (48 слів):

abstract           double             int                   strictfp

boolean           else               interface          super

break               extends           long                 switch

byte                 final            native              synchronized

case                 finally          new                 this

catch               float                package           throw

char                 for               private             throws

class                goto             protected        transient

const               if                public              try

continue          implements          return              void

default            import              short                volatile

do                   instanceof       static               while

Ключові слова goto і const зарезервовані, але не використовуються. Це зроблено для того, щоб компілятор міг правильно відреагувати на їх використання в інших мовах. Навпаки, обидва булевских літерала true, false і null-літерал null часто вважають ключовими словами (можливо, тому, що багато засобів розробки підсвічують їх таким же чином), проте це саме літерали.

Значення всіх ключових слів буде розглядатися в наступних лекціях.

Літерали

Літерали дозволяють задати в програмі значення для числових, символьних і рядкових виразів, а також null-літералов.Всього в Java визначено 6 видів літералів:

  • цілочисельний (integer);
  • дробовий (floating-point);
  • булевський (boolean);
  • символьний (character);
  • строковий (string);
  • null-літерал (null-literal).

Розглянемо їх окремо.

Цілочисельні літерали

Цілочисельні літерали дозволяють задавати цілочисельні значення в десятковому, вісімковому і шістнадцятковому вигляді. Десятерічний формат традиційний і нічим не відрізняється від правил, прийнятих в інших мовах. Значення в вісімковому вигляді починаються з нуля, і, звичайно, використання цифр 8 і 9 заборонено. Запис шістнадцяткових чисел починається з 0x або 0X (цифра 0 і латинська ASCII-буква X в довільному регістрі). Таким чином, нуль можна записати трьома різними способами:

 0
00
0x0 

Як звичайно, для запису цифр 10-15 в шістнадцятковому форматі використовуються букви A, B, C, D, E, F, прописні або рядкові. Приклади таких літералів:

 0xaBcDeF, 0xCafe, 0xDEC 

Типи даних розглядаються нижче, однак тут необхідно згадати два цілочисельних типи int і long довжиною 4 і 8 байт, відповідно (або 32 і 64 біта, відповідно). Обидва ці типи знакові, тобто тип int зберігає значення від -231 до 231-1, або від -2.147.483.648 до 2.147.483.647. За замовчуванням цілочисельний літерал має тип int, а значить, у програмі допустимо використовувати літерали тільки від 0 до 2147483648, інакше виникне помилка компіляції. При цьому літерал 2147483648 можна використовувати тільки як аргумент унарні оператора -:

 int x = -2147483648; \\ вірно
int y = 5-2147483648; \\ тут виникне помилка компіляції

Відповідно, допустимі літерали в вісімковій записи повинні бути від 00 до 017 777 777 777 (= 231-1), з унарним оператором - припустимо також -020000000000 (= -231). Аналогічно для шістнадцяткового формату - від 0x0 до 0x7fffffff (= 231-1), а також-0x80000000 (= -231).

Тип long має довжину 64 біта, а значить, дозволяє зберігати значення від -263 до 263-1. Щоб ввести такий літерал, необхідно в кінці поставити латинську букву L або l, тоді все значення буде трактуватися як long. Аналогічно можна виписати максимальні допустимі значення для них:

 9223372036854775807L
0777777777777777777777L
0x7fffffffffffffffL
// Найбільші негативні значення:
-9223372036854775808L
-01000000000000000000000L
-0x8000000000000000L

Інші приклади цілочисельних літералів типу long:

 0L, 123l, 0xC0B0L 

Дробові літерали

Дробові літерали представляють собою числа з плаваючою десятковою крапкою. Правила запису таких чисел такі ж, як і в більшості сучасних мов програмування.

Приклади:

 3.14
2.
.5
7e10
3.1E-20

Таким чином, дробовий літерал складається з наступних складових частин:

  • ціла частина;
  • десяткова точка (використовується ASCII-символ крапка);
  • дробова частина;
  • порядок (складається з латинської ASCII-букви E в довільному регістрі і цілого числа з опціональним знаком + або -);
  • закінчення-вказівник типу.

Ціла та дробова частини записуються десятковими цифрами, а вказівник типу (аналог покажчика L або l для цілочисельних літералів типу long) має два можливих значення - латинська ASCII-буква D (для типу double) або F (для типу float) в довільному регістрі. Вони будуть детально розглянуті нижче.

Необхідними частинами є:

  • хоча б одна цифра в цілій або дробової частини;
  • десяткова крапка або показник ступеня, або покажчик типу.

Всі інші частини необов'язкові. Таким чином, "мінімальні" дробові літерали можуть бути записані, наприклад, так:

 1.
.1
1e1
1f

У Java є два дробових типу, згадані вище, - float і double. Їх довжина - 4 і 8 байт або 32 і 64 біта, відповідно. Дробний літерал має тип float, якщо він закінчується на латинську букву F в довільному регістрі. В іншому випадку він розглядається як значення типу double і може включати в себе закінчення D або d, як ознака типу double (використовується тільки для наочності).

 // Float-літерали:
1f, 3.14F, 0f, 1e +5 F
// Double-літерали:
0., 3.14d, 1e-4, 31.34E45D

У Java дробові числа 32-бітного типу float і 64-бітного типу double зберігаються в пам'яті в бінарному вигляді в форматі, стандартизованому специфікацією IEEE 754 (повна назва - IEEE Standard for Binary Floating-Point Arithmetic, ANSI / IEEE Standard 754-1985 (IEEE , New York)). У цій специфікації описані не тільки кінцеві дробові величини, але й ще кілька особливих значень, а саме:

  • позитивна і негативна нескінченності (positive / negative infinity);
  • значення "не число", Not-a-Number, скорочено NaN;
  • позитивний і негативний нулі.

Для цих значень немає спеціальних позначень. Щоб отримати такі величини, необхідно або провести арифметичну операцію (наприклад, результатом поділу нуль на нуль 0.0/0.0 є NaN), або звернутися до констант в класах Float і Double, а саме POSITIVE_INFINITY, NEGATIVE_INFINITY і NaN. Більш докладно робота з цими особливими значеннями розглядається в наступній лекції.

Типи даних накладають обмеження на можливі значення літералів, як і для цілочисельних типів. Максимальне позитивне кінцеве значення дробового літерала:

  • для float: 3.40282347e +38 f
  • для double: 1.79769313486231570e +308

Крім того, для дробових величин стає важливим ще одне граничне значення - мінімальне позитивне ненульове значення:

  • для float: 1.40239846e-45f
  • для double: 4.94065645841246544e-324

Спроба вказати літерал із занадто великим абсолютним значенням (наприклад, 1e40F) призведе до помилки компіляції. Така величина повинна представлятися нескінченністю. Аналогічно, вказівку літерала із занадто малим ненульовим значенням (наприклад, 1e-350) також приводить до помилки. Це значення повинно бути округлене до нуля. Однак якщо округлення призводить не до нуля, то компілятор зробить його сам:

 // Помилка, вираз має бути округлена до 0
0.00000000000000000000000000000000000000000001f
// Помилки немає, компілятор сам округлює до 1
1.00000000000000000000000000000000000000000001f

Стандартних можливостей вводити дробові значення не в десятковій системі в Java немає, проте класи Float і Double надають багато допоміжних методів, в тому числі і для такого завдання.

 Логічні літерали

Логічні літерали мають два можливих значення - true і false. Ці два зарезервованих слова не є ключовими, але також не можуть використовуватися в якості ідентифікатора.

Символьні літерали

Символьні літерали описують один символ з набору Unicode, укладений в одиночні лапки, або апострофи (ASCII-символ single quote, \ u0027). Наприклад:

' A' // латинська літера а
'' // Пробіл
'K' // грецька буква каппа

Також допускається спеціальний запис для опису символу через його код (див. тему "Кодування"). Приклади:

 '\ U0041' // латинська буква A
'\ U0410' // російська буква А
'\ U0391' // грецька буква A

Символьний літерал повинен містити строго один символ, або спеціальну послідовність, що починається з \. Для запису спеціальних символів (невідображених і службових, таких як ", ', \) використовуються наступні позначення:

 \ B \ u0008 backspace BS - забій
\ T \ u0009 horizontal tab HT - табуляція
\ N \ u000a linefeed LF - кінець рядка
\ F \ u000c form feed FF - кінець сторінки
\ R \ u000d carriage return CR - 
  •             повернення каретки
 \"\ U0022 double quote" - лапки
\'\ U0027 single quote' - одинарна лапка
\\\ U005c backslash \ - зворотна коса риска
\ Шістнадцятковий код від \ u0000 до \ u00ff символу 
  •                    в шістнадцятковому форматі.

Перша колонка описує стандартні позначення спеціальних символів, які використовуються в Java-програмах. Друга колонка представляє їх в стандартному вигляді Unicode-символів. Третя колонка містить англійські і російські опису. Використання \ в комбінації з іншими символами призведе до помилки компіляції.

Підтримка введення символів через вісімковий код забезпечується для сумісності з С. Наприклад:

 '\ 101' // Еквівалентно '\ u0041'

Проте таким чином можна поставити лише символи від \ u0000 до \ u00ff (тобто з кодом від 0 до 255), тому Unicode-послідовності краще.

Оскільки обробка Unicode-послідовностей (\ uhhhh) проводиться раніше лексичного аналізу, то наступний приклад є помилкою:

 '\ U000a' // символ кінця рядка 

Компілятор спочатку перетворить \ u000a на символ кінця рядка і лапки опиняться на різних рядках коду, що є помилкою. Необхідно використовувати спеціальну послідовність:

 '\ N' / правильне позначення кінця рядка 

Аналогічно і для символу \ u000d (повернення каретки) необхідно використовувати позначення \ r.

Спеціальні символи можна використовувати у складі як символьних, так і рядкових літералів.

Стрічкові літерали

Стрічкові літерали складаються з набору символів і записуються в подвійних лапках. Довжина може бути нульовою або скільки завгодно великою. Будь-який символ може бути представлений спеціальною послідовністю, що починається з \ (див. "Символьні літерали").

  "" // Літерал нульової довжини 
"\" "// Літерал, що складається з одного символу"
"Простий текст" // літерал довжини 13

Стрічковий літерал не можна розбивати на кілька рядків у коді програми. Якщо потрібно текстове значення, що складається з декількох рядків, то необхідно скористатися спеціальними символами \ n і / або \ r. Якщо ж текст просто занадто довгий, щоб вміститися на одному рядку коду, можна використовувати оператор конкатенації рядків +. Приклади строкових літералів:

 // Вираз-константа, складений з двох
// Літералів
"Довгий текст" +
"З перенесенням"
/*
   * Стрічковий літерал, що містить текст
   * З двох рядків:
   * Hello, world!
   * Hello!
   * /
"Hello, world! \R\ nHello!"

На рядкові літерали поширюються ті ж правила, що і на символьні щодо використання символів нового рядка \ u000a і \ u000d.

Кожен строковий літерал є екземпляром класу String. Це визначає деякі незвичайні властивості строкових літералів, які будуть розглянуті в наступній лекції.

Null-літерал

Null-літерал може приймати всього одне значення: null. Це літерал вказівного типу, причому це посилання нікуди не посилається, об'єкт відсутній. Зрозуміло, його можна застосовувати до посилань будь-якого об'єктного типу даних. Типи даних докладно розглядаються в наступній лекції.

Розділювачі

Роздільники - це спеціальні символи, які використовуються в службових цілях мови. Призначення кожного з них буде розглянуто по ходу викладу курсу. Ось їх повний список:

() [] {};. ,

Оператори

Оператори використовуються в різних операціях - арифметичних, логічних, бітових, операціях порівняння і присвоювання. Наступні 37 лексем (усі складаються тільки з ASCII-символів) є операторами мови Java:

 => <! ~? :
== <=> =! = & & | | + + -
+ - * / & | ^% <<>>>>>
+ = -= *= / = & = | = ^ =% = <<=>> =>>> = 

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

Приклад програми

На закінчення для прикладу наведемо просту програму (традиційне Hello, world!), А потім класифікуємо і підрахуємо використовувані лексеми:

 public class Demo {
/**
   * Основний метод, з якого починається
   * Виконання будь-Java програми.
   */
     public static void main (String args [])
     {
        System.out.println ("Hello, world!");
     }
}

Отже, у наведеній програмі є один коментар розробника, 7 ідентифікаторів, 5 ключових слів, 1 строковий літерал, 13 роздільників і жодного оператора. Цей текст можна зберегти у файлі Demo.java, скомпілювати і запустити. Результатом роботи буде, як очевидно:

He llo, world!

Доповнення. Робота з операторами

Розглянемо деякі деталі використання операторів в Java. Тут будуть описані подробиці, що стосуються роботи самих операторів. У наступній лекції детально розглядаються особливості, що виникають при використанні різних типів даних (наприклад, значення операції 1 / 2 дорівнює 0, а 1 / 2. Одно 0.5).

Оператори присвоювання і порівняння

По-перше, звичайно ж, різняться оператор присвоювання = і оператор порівняння ==.

 x = 1; // присвоюємо змінної x значення 1
x == 1 // порівнюємо значення змінної x з одиницею

Оператор порівняння завжди повертає булеве значення true або false. Оператор присвоювання повертає значення правого операнда. Тому звичайна помилка в мові С, коли ці оператори плутають:

 // Приклад викличе помилку компілятора
if (x = 0) {// тут повинен застосовуватися оператор  порівняння ==
...
}

в Java легко усувається. Оскільки вираз x = 0 має числове значення 0, а не булеве (і тим більше не сприймається як завжди істинне), то компілятор повідомляє про помилку (необхідно писати x == 0).

Умова "не дорівнює" записується як! =. Наприклад:

 if (x! = 0) {
   float f = 1. / x;
}

Поєднання будь-якого оператора з оператором присвоєння = (див. нижній рядок в повному переліку в розділі "Оператори") використовується при зміні значення змінної. Наприклад, наступні два рядки еквівалентні:

 x = x + 1;
x + = 1;

Арифметичні операції

Поряд з чотирма звичайними арифметичними операціями +, -, *, /, існує оператор отримання залишку від ділення %, який може бути застосований як до цілочисловим аргументів, так і до дробовим.

Робота з цілочисельними аргументами підкоряється простим правилам. Якщо ділиться значення a на значення b, то вираз (a / b) * b + (a% b) повинна в точності дорівнювати a. Тут, звичайно, оператор ділення цілих чисел / завжди повертає ціле число. Наприклад:

 9 / 5 повертає 1
9 / (-5) повертає -1
(-9) / 5 повертає -1
(-9) / (-5) Повертає 1

Залишок може бути позитивним, тільки якщо ділене було позитивним. Відповідно, залишок може бути негативним тільки в разі негативного діленого.

 9% 5 повертає 4
9% (-5) повертає 4
(-9)% 5 повертає -4
(-9)% (-5) Повертає -4

Спроба отримати залишок від ділення на 0 призводить до помилки.

Ділення з залишком для дробових чисел може бути вироблено за двома різними алгоритмами. Один з них повторює правила для цілих чисел, і саме він представлений оператором%. Якщо в розглянутому прикладі поділу 9 на 5 перейти до дробовим числах, значення залишку у всіх варіантах не зміниться (воно буде також дробовим, звичайно).

 9.0% 5.0 повертає 4.0
9.0% (-5.0) повертає 4.0
(-9.0)% 5.0 повертає -4.0
(-9.0)% (-5.0) Повертає -4.0

Однак стандарт IEEE 754 визначає інші правила. Такий спосіб представлений методом стандартного класу Math.IEEEremainder (double f1, double f2). Результат цього методу - значення, яке дорівнює f1-f2 * n, де n - ціле число, найближчим до значення f1/f2, а якщо два цілих числа однаково близькі до цього відношення, то вибирається парне. За цим правилом значення залишку буде іншим:

 Math.IEEEremainder (9.0, 5.0) повертає -1.0
Math.IEEEremainder (9.0, -5.0) повертає -1.0
Math.IEEEremainder (-9.0, 5.0) повертає 1.0
Math.IEEEremainder (-9.0, -5.0) повертає 1.0

Унарні оператори інкрементаціі + + і декрементаціі -, як завжди, можна використовувати як справа, так і зліва.

 int x = 1;
int y = + + x;

У цьому прикладі оператор + + стоїть перед змінної x, це означає, що спочатку відбудеться інкрементація, а потім значення x буде використано для ініціалізації y. У результаті після виконання цих рядків значення x і y будуть рівні 2.

 int x = 1;
int y = x + +;

А в цьому прикладі спочатку значення x буде використано для ініціалізації y, і лише потім відбудеться інкрементація. У результаті значення x буде дорівнює 2, а y буде дорівнювати 1.

Логічні оператори

Логічні оператори "і" та "або" (& і |) можна використовувати у двох варіантах. Це пов'язано з тим, що, як легко переконатися, для кожного оператора можливі випадки, коли значення першого операнда відразу визначає значення всього логічного виразу. Якщо другим операндом є значення деякої функції, то з'являється вибір - викликати її чи ні, причому це рішення може позначитися як на швидкості, так і на функціональності програми.

Перший варіант операторів (&, |) завжди обчислює обидва операнда, другий же - (& &, | |) не буде продовжувати обчислення, якщо значення виразу вже очевидно. Наприклад:

 int x = 1;
(X> 0) | calculate (x) // в такому виразі відбудеться виклик Calculate
(X> 0) | | calculate (x) / / а в цьому - ні

Логічний оператор заперечення "не" записується як! і, звичайно, має тільки один варіант використання. Цей оператор змінює булеве значення на протилежне.

 int x = 1;
x> 0 / / вираз істинний
! (X> 0) / / вираз хибний

Оператор з умовою ? : Складається з трьох частин - умови і двох виразів. Спочатку обчислюється умова (булеве вираз), а на підставі результату значення всього оператора визначається першим виразом у разі отримання істини і другим - якщо умова хибна. Наприклад, так можна обчислити модуль числа x:

 x> 0? x:-x

Бітові операції

Перш ніж переходити до двійкового операціями, необхідно уточнити, яким саме чином цілі числа представляються в двійковому вигляді. Звичайно, для невід'ємних величин це практично очевидно:

 0 0
1 січня
Лютого 1910
11 березня
4 100
5 101

і так далі. Однак як представляються негативні числа? По-перше, вводять поняття знакового біта. Перший біт починає відповідати за знак, а саме 0 означає позитивне число, 1 - негативне. Але не слід думати, що інші біти залишаються незмінними. Наприклад, якщо розглянути 8-бітове уявлення:

 -1 Один мільйон тисячі / / це НЕВІРНО!
-2 10000010 / / це НЕВІРНО!
-3 10000011 / / це НЕВІРНО!

Такий підхід є невірним! Зокрема, ми отримуємо відразу два подання нуля - 00000000 і 100000000, що нераціонально. Правильний алгоритм можна уявити собі так. Щоб отримати значення -1, треба з 0 відняти 1:

  00000000
- 00000001
------------
- 11111111

Отже, -1 у двійковому вигляді представляється як 11111111. Продовжуємо застосовувати той же алгоритм (віднімаємо 1):

 0 00000000
-1 11111111
-2 11111110
-3 11111101

і так далі до значення 10000000, яке являє собою найбільшу за модулем від'ємне число. Для 8-бітового уявлення найбільший позитивний число 01111111 (= 127), а найменший негативний 10000000 (=- 128). Оскільки всього 8 біт визначає 28 = 256 значень, причому одне з них відводиться для нуля, то стає ясно, чому найбільші за модулем позитивні і негативні значення розрізняються на одиницю, а не збігаються.

Як відомо, бітові операції "і", "або", "виключає або" беруть два аргументи і виконують логічну дію попарно над відповідними бітами аргументів. При цьому використовуються ті ж позначення, що і для логічних операторів, але, звичайно, тільки в першому (одиночному) варіанті. Наприклад, обчислимо вираз 5 & 6:

  00000101
& 00000110
-------------
  00000100
   / / Число 5 у двійковому вигляді
   / / Число 6 у двійковому вигляді
   / / Виконали операцію "і" попарно над бітами
   / / У кожній позиції

Тобто вираз 5 & 6 дорівнює 4.

Виняток становить лише оператор "не" або "NOT", який для побітових операцій записується як ~ (для логічних було !). Цей оператор змінює кожен біт в числі на протилежний. Наприклад, ~ (-1) = 0. Можна легко встановити загальне правило для отримання бітового представлення негативних чисел:

Якщо n - ціле позитивне число, то-n в бітовому представленні дорівнює ~ (n-1).

Нарешті, залишилося розглянути лише оператори побітового зсуву. У Java є один оператор зсуву вліво і два варіанти зсуву вправо. Така відмінність пов'язана з наявністю знакового біта.

При зсуві вліво оператором <<всі біти числа зміщуються на вказану кількість позицій вліво, причому звільнилися праворуч позиції заповнюються нулями. Ця операція аналогічна множенню на 2n і діє цілком передбачувано, як при позитивних, так і при негативних аргументах.

Розглянемо приклади застосування операторів зсуву для значень типу int, тобто 32-бітових чисел. Нехай позитивним аргументом буде число 20, а негативним -21.

 / / Зрушення вліво для позитивного числа 20
20 <<00 = 00000000000000000000000000010100 = 20
20 <<01 = 00000000000000000000000000101000 = 40
20 <<02 = 00000000000000000000000001010000 = 80
20 <<03 = 00000000000000000000000010100000 = 160
20 <<04 = 00000000000000000000000101000000 = 320
...
20 <<25 = 00101000000000000000000000000000 = 671088640
20 <<26 = 01010000000000000000000000000000 = 1342177280
20 <<27 = 10100000000000000000000000000000 = -1610612736
20 <<28 = 01000000000000000000000000000000 = 1073741824
20 <<29 = 10000000000000000000000000000000 = -2147483648
20 <<30 = 00000000000000000000000000000000 = 0
20 <<31 = 00000000000000000000000000000000 = 0
/ / Зрушення вліво для негативного числа -21
-21 <<00 = 11111111111111111111111111101011 = -21
-21 <<01 = 11111111111111111111111111010110 = -42
-21 <<02 = 11111111111111111111111110101100 = -84
-21 <<03 = 11111111111111111111111101011000 = -168
-21 <<04 = 11111111111111111111111010110000 = -336
-21 <<05 = 11111111111111111111110101100000 = -672
...
-21 <<25 = 11010110000000000000000000000000 = -704643072
-21 <<26 = 10101100000000000000000000000000 = -1409286144
-21 <<27 = 01011000000000000000000000000000 = 1476395008
-21 <<28 = 10110000000000000000000000000000 = -1342177280
-21 <<29 = 01100000000000000000000000000000 = 1610612736
-21 <<30 = 11000000000000000000000000000000 = -1073741824
-21 <<31 = 10000000000000000000000000000000 = -2147483648

Як видно з прикладу, несподіванки виникають тоді, коли значущі біти починають займати першу позицію і впливати на знак результату.

При зсуві вправо всі біти аргументу зміщуються на вказану кількість позицій, відповідно, вправо. Однак постає питання - яким значенням заповнювати позиції ліворуч, що звільняються і що відповідає за знак. Є два варіанти. Оператор>> використовує для заповнення цих позицій значення знакового біта, тобто результат завжди має той же знак, що і початкове значення. Другий оператор>>> заповнює їх нулями, тобто результат завжди позитивний.

 // Зрушення вправо для позитивного числа 20
// Оператор>>
20>> 00 = 00000000000000000000000000010100 = 20
20>> 01 = 00000000000000000000000000001010 = 10
20>> 02 = 00000000000000000000000000000101 = 5
20>> 03 = 00000000000000000000000000000010 = 2
20>> 04 = 00000000000000000000000000000001 = 1
20>> 05 = 00000000000000000000000000000000 = 0
// Оператор>>>
20>>> 00 = 00000000000000000000000000010100 = 20
20>>> 01 = 00000000000000000000000000001010 = 10
20>>> 02 = 00000000000000000000000000000101 = 5
20>>> 03 = 00000000000000000000000000000010 = 2
20>>> 04 = 00000000000000000000000000000001 = 1
20>>> 05 = 00000000000000000000000000000000 = 0

Очевидно, що для позитивного аргументу оператори>> і>>> працюють абсолютно однаково. Подальший зрушення на більшу кількість позицій буде також давати нульовий результат.

 // Зрушення вправо для негативного числа -21
// Оператор>>
-21>> 00 = 11111111111111111111111111101011 = -21
-21>> 01 = 11111111111111111111111111110101 = -11
-21>> 02 = 11111111111111111111111111111010 = -6
-21>> 03 = 11111111111111111111111111111101 = -3
-21>> 04 = 11111111111111111111111111111110 = -2
-21>> 05 = 11111111111111111111111111111111 = -1
// Оператор>>>
-21>>> 00 = 11111111111111111111111111101011 = -21
-21>>> 01 = 01111111111111111111111111110101 = 2147483637
-21>>> 02 = 00111111111111111111111111111010 = 1073741818
-21>>> 03 = 00011111111111111111111111111101 = 536870909
-21>>> 04 = 00001111111111111111111111111110 = 268435454
-21>>> 05 = 00000111111111111111111111111111 = 134217727
...
-21>>> 24 = 00000000000000000000000011111111 = 255
-21>>> 25 = 00000000000000000000000001111111 = 127
-21>>> 26 = 00000000000000000000000000111111 = 63
-21>>> 27 = 00000000000000000000000000011111 = 31
-21>>> 28 = 00000000000000000000000000001111 = 15
-21>>> 29 = 00000000000000000000000000000111 = 7
-21>>> 30 = 00000000000000000000000000000011 = 3
-21>>> 31 = 00000000000000000000000000000001 = 1

Як видно з прикладів, ці операції аналогічні поділу на 2n. Причому, якщо для позитивних аргументів із зростанням n результат закономірно прагне до 0, то для негативних граничним значенням є -1.

Висновок

У цій лекції були розглянуті основи лексичного аналізу програм Java. Для їх запису застосовується універсальне кодування Unicode, що дозволяє використовувати будь-яку мову крім традиційного англійського. Ще раз нагадаємо, що використання Unicode можливо і необхідно в наступних конструкціях:

  • коментарі;
  • ідентифікатори;
  • символьні і рядкові літерали.

Інші ж (пробіли, ключові слова, числові, булевські і null-літерали, роздільники й оператори) легко записуються із застосуванням лише ASCII-символів. У той же час будь-Unicode-символ також можна задати у вигляді спеціальної послідовності ASCII-символів.

Підчасаналізукомпіляторвиділяєзтекступрограми<пробіли> (булирозглянутівсісимволи, якірозглядаютьсяякпробіли) такоментарі, якіповністювидаляютьсязкоду(булирозглянутівсівидикоментарів, зокремакоментаррозробника). Пробіли і всі види коментарів служать для розбиття тексту програми на лексеми. Були розглянуті всі види лексем, у тому числі всі види літералів.

У доповненні були розглянуті особливості застосування різних операторів.

загрузка...
Теми розділу
Сторінки, близькі за змістом