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

Типи даних

 Java

Вступ

Java є суворо типізовані мовою. Це означає, що будь-яка змінна і будь-який вираз мають відомий тип ще на момент компіляції. Таке суворе правило дозволяє виявляти багато помилок вже під час компіляції. Компілятор, знайшовши помилку, вказує точне місце (рядок) і причину її виникнення, а динамічні "баги" (від англійського bugs) необхідно спочатку виявити за допомогою тестування (що може зажадати значних зусиль), а потім знайти місце в коді, яке їх породило . Тому чітке розуміння моделі типів даних в Java дуже допомагає в написанні якісних програм.

Всі типи даних поділяються на дві групи. Першу складають 8 простих, або примітивних (від англійського primitive), типів даних. Вони поділяються на три підгрупи:

цілочисельні

  • byte
  • short
  • int
  • long
  • char (також є цілочисловим типом)

дробові

  • float
  • double

булеві

  • boolean

Другу групу складають об'єктні, або вказівні (від англійського reference), типи даних. Це всі класи, інтерфейси та масиви. У стандартних бібліотеках перших версій Java перебувало кілька сот класів і інтерфейсів, зараз їх вже тисячі. Крім стандартних, написано багато класів та інтерфейсів, складаючі будь-яку Java-програму.

Ілюструвати логіку роботи з типами даних простіше всього на прикладі змінних.

Змінні

Змінні використовуються в програмі для зберігання даних. Будь-яка змінна має три базових характеристики:

  • ім'я;
  • тип;
  • значення.

Ім'я унікально ідентифікує змінну і дозволяє звертатися до неї в програмі. Тип описує, які величини може зберігати змінна. Значення - поточна величина, що зберігається у змінній на даний момент.

Робота зі змінною завжди починається з її оголошення (declaration). Звичайно, вона має включати в себе ім'я оголошуваної змінної. Як було сказано, в Java будь-яка змінна має строгий тип, який також задається при оголошенні і ніколи не змінюється. Значення може бути вказано відразу (це називається ініціалізацією), а в більшості випадків завдання початкової величини можна і відкласти.

Деякі приклади оголошення змінних примітивного типу int з ініціалізацією і без таких:

 int a;
int b = 0, c = 3 +2;
int d = b + c;
int e = a = 5;

З прикладів видно, що ініціалізатором може бути не тільки константа, але і арифметичне вираз. Іноді цей вираз може бути обчислено під час компіляції (таке як 3 +2), тоді компілятор зразу записує результат. Іноді ця дія відкладається на момент виконання програми (наприклад, b + c). В останньому випадку декільком змінним присвоюється одне і те ж значення, проте оголошується лише перша з них (в даному прикладі е), інші вже повинні існувати.

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

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

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

Найпростіший приклад оголошення final-змінної:

final double pi = 3.1415; 

Примітивні і вказівні типи даних

Тепер на прикладі змінних можна проілюструвати відмінність між примітивними і вказівними типами даних. Розглянемо приклад, коли оголошуються дві змінні одного типу, прирівнюються один одному, а потім значення однієї з них змінюється. Що станеться з другої змінної?

Візьмемо простий тип int:

 int a = 5; // оголошуємо першу змінну і інициализуємо її
int b = a; // оголошуємо другу змінну і прирівнюємо її до першої
a = 3; // змінюємо значення першої
print (b); // перевіряємо значення другої

Тут і далі ми вважаємо, що функція print (...) дозволяє нам деяким (неважливо, яким саме) способом дізнатися значення її аргументу (як правило, для цього використовують функцію зі стандартної бібліотеки System.out.println (...), яка виводить значення на системну консоль).

У результаті ми побачимо, що значення змінної b не змінилося, воно залишилося рівним 5. Це означає, що змінні простого типу зберігають безпосередньо свої значення і при прирівнювання двох змінних відбувається копіювання даного значення. Щоб ще раз підкреслити цю особливість, наведемо ще один приклад:

 byte b = 3;
int a = b;

У даному прикладі відбувається перетворення типів (вона детально розглядається у відповідній лекції). Для нас зараз важливо констатувати, що змінна b зберігає значення 3 типи byte, а змінна a - значення 3 типу int. Це два різних значення, і у другому рядку при присвоєнні сталося копіювання.

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

 class Point {
   int x, y;
}

Тепер складемо приклад, аналогічний наведеному вище для int-змінних, вважаючи, що вираз new Point (3,5) створює новий об'єкт-точку з координатами (3,5).

 Point p1 = new Point (3,5);
Point p2 = p1;
p1.x = 7;
print (p2.x);

У третьому рядку ми змінили горизонтальну координату точки, на яку посилалася змінна p1, і тепер нас цікавить, як це позначилося на точці, на яку посилається змінна p2. Провівши такий експеримент, можна переконатися, що цього разу ми побачимо оновлене значення. Тобто об'єктні змінні після прирівнювання залишаються "пов'язаними" один з одним, зміни однієї позначаються на інший.

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

 Point p1 = new Point (3,5);
Point p2 = p1;
p1 = new Point (7,9);
print (p2.x);

У цьому прикладі ми одержимо 3, тобто після третього рядка змінні p1 і p2 посилаються на різні об'єкти і тому мають різні значення.

Тепер легко зрозуміти сенс літерала null. Таке значення може прийняти змінна будь-якого вказівного типу. Це означає, що її посилання нікуди не вказує, об'єкт відсутній. Відповідно, будь-яка спроба звернутися до об'єкта через таку змінну (наприклад, викликати метод або взяти значення поля) призведе до помилки.

Також значення null можна передати в якості будь-якого об'єктного аргументу при виклику функцій (хоча на практиці багато методів вважають таке значення некоректним).

Пам'ять в Java з точки зору програміста представляється не нулями та одиницями або набором байтів, а як певна віртуальний простір, в якому існують об'єкти. І доступ до пам'яті здійснюється не за фізичним адресою або вказівником, а лише через посилання на об'єкти. Посилання повертається при створенні об'єкта і далі може бути збережене у змінну, передане в якості аргументу і т.д. Як вже говорилося, допускається наявність декількох посилань на один об'єкт. Можлива і протилежна ситуація - коли на якийсь об'єкт не існує жодного посилання. Такий об'єкт уже недоступний програмі і є "сміттям", тобто без толку займає апаратні ресурси. Для їхнього звільнення не потрібно ніяких зусиль. До складу будь-віртуальної машини обов'язково входить автоматичний прибиральник сміття garbage collector - фоновий процес, який якраз і займається знищенням непотрібних об'єктів.

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

Тепер розглянемо примітивні і посилальні типи даних більш докладно.

Примітивні типи

Як вже говорилося, існує 8 простих типів даних, які діляться на цілочисельні (integer), дробові (floating-point) і булеві (boolean).

Цілочисельні типи

Цілочисельні типи - це byte, short, int, long, також до них відносять і char. Перші чотири типи мають довжину 1, 2, 4 і 8 байт відповідно, довжина char - 2 байти, це безпосередньо випливає з того, що всі символи Java описуються стандартом Unicode. Довжини типів наведені тільки для оцінки областей значення. Як вже говорилося, пам'ять в Java представляється віртуальною і обчислити, скільки фізичних ресурсів займе та чи інша змінна, так прямолінійно не вийде.

4 основних типи є знаковими. char доданий до цілочисловим типам даних, так як з точки зору JVM символ і його код - поняття взаємооднозначної. Звичайно, код символу завжди позитивний, тому char - єдиний беззнакових тип. Ініціювати його можна як символьним, так і цілочисловим літералом. У всьому іншому char - повноцінний числовий тип даних, який може брати участь, наприклад, в арифметичних діях, операціях порівняння і т.п. У таблиці 4.1 зведені дані по всім розібраним типами:

Таблиця 4.1

Назва типу

Довжина (байти)

Область значень

Byte

1

-128 .. 127

Short

2

-32.768 .. 32.767

Int

4

-2.147.483.648 .. 2.147.483.647

Long

8

-9.223.372.036.854.775.808 .. 9.223.372.036.854.775.807 (примерно 1019)

Char

2

'\u0000' .. '\uffff', или 0 .. 65.535

Зверніть увагу, що int вміщує приблизно 2 мільярди, а тому підходить у багатьох випадках, коли не потрібні надвеликі числа. Щоб уявити собі розміри типу long, зазначимо, що саме він використовується в Java для відліку часу. Як і в багатьох мовах, час відраховується від 1 січня 1970 року в мілісекундах. Так ось, місткість long дозволяє відраховувати час протягом мільйонів століть (!), Причому як у майбутнє, так і в минуле.

Чому були виділені саме ці два типи, int і long? Справа в тому, що цілочисельні літерали мають тип int за замовчуванням, або тип long, якщо стоїть буква L або l. Саме тому коректним літералом вважається тільки таке число, яке вкладається в 4 або 8 байт, відповідно. Інакше компілятор визнає це помилкою. Таким чином, наступні літерали є коректними:

 1
-2147483648
2147483648L
0L
111111111111111111

Над цілочисельними аргументами можна проводити наступні операції:

  • операції порівняння (повертають булево значення)
    • <, <=,>,> =
    • ==,! =
  • числові операції (повертають числове значення)
    • унарні операції + і -
    • арифметичні операції +, -, *, /,%
    • операції інкремента і декремента (в префіксной і постфіксній формі): + + і -
    • операції бітового зсуву <<,>>,>>>
    • бітові операції ~, &, |, ^
  • оператор з умовою? :
  • оператор приведення типів
  • оператор конкатенації з рядком +

Оператори порівняння цілком очевидні і окремо ми їх розглядати не будемо. Їх результат завжди булевого типу (true або false).

Робота числових операторів також зрозуміла, до того ж пояснювалася в попередній лекції. Єдине уточнення можна зробити щодо операторів + і -, які можуть бути як бінарними (мати два операнди), так і унарними (мати один операнд). Бінарні операнди є операторами додавання і віднімання, відповідно. Унарний оператор + повертає значення, рівне аргументу (+ x завжди дорівнює x). Унарний оператор -, застосований до значення x, повертає результат, рівний 0-x. Несподіваний ефект має місце в тому випадку, якщо аргумент дорівнює найменшому можливому значенню примітивного типу.

 int x =- 2147483648; / / найменше можливе значення типу int
int y =- x;

Тепер значення змінної y насправді рівне не 2147483648, оскільки таке число не вкладається в область значень типу int, а в точності дорівнює значенню x! Іншими словами, в цьому прикладі вираз-x == x істинно!

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

 int x = 300000;
print (x * x);

Результатом такого прикладу буде:

 -194313216

Повертаючись до інвертування числа -2147483648, ми бачимо, що математичний результат дорівнює в точності +231, або, в двійковому форматі, 1000 0000 0000 0000 0000 0000 0000 0000 (одиниця і 31 нуль). Але тип int розглядає першу одиницю як знаковий біт, і результат виходить рівним -2147483648.

Таким чином, явне виписування в коді літералів, які занадто великі для використовуваних типів, призводить до помилки компіляції (див. лекцію 3). Якщо ж переповнення виникає в результаті виконання операції, "зайві" біти просто відкидаються.

Підкреслимо, що вираз типу -5 не є цілочисловим літералом. Насправді воно складається з літерала 5 і оператора -. Нагадаємо, що деякі літерали (наприклад, 2147483648) можуть зустрічатися тільки в поєднанні з унарним оператором -.

Крім того, числові операції в Java володіють ще однією особливістю. Хоча цілочисельні типи мають довжину 8, 16, 32 і 64 біта, обчислення проводяться лише з 32-х і 64-х бітною точністю. А це означає, що перед обчисленнями може знадобитися перетворити тип одного або декількох операндів.

Якщо хоча б один аргумент операції має тип long, то всі аргументи зводяться до цього типу і результат операції також буде типу long. Обчислення буде зроблено з точністю в 64 біта, а більш старші біти, якщо такі з'являються в результаті, відкидаються.

Якщо ж аргументів типу long немає, то обчислення проводиться з точністю в 32 біта, і всі аргументи перетворюються на int (це відноситься до byte, short, char). Результат також має тип int. Всі біти старше 32-го ігноруються.

Ніякого способу дізнатися, чи відбулося переповнення, немає. Розширимо розглянутий приклад:

 int i = 300000;
print (i * i); / / множення з точністю 32 біта
long m = i;
print (m * m); / / множення з точністю 64 біта
print (1 / (mi)); / / спробуємо отримати різниця значень int і long

Результатом такого прикладу буде:

-194 313216
9000 0000000

Потім ми отримаємо помилку ділення на нуль, оскільки змінні i та m хоч і різних типів, але зберігають однакове математичне значення і їх різниця дорівнює нулю. Перше множення проводилося з точністю в 32 біта, більш старші біти були відкинуті. Друге - з точністю в 64 біта, відповідь не спотворився.

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

 double x = 1 / 2;

змінній x буде присвоєно значення 0, а не 0.5, як можна було б очікувати. Докладно операції з дробовими аргументами розглядаються нижче, але щоб отримати значення 0.5, достатньо написати 1. / 2 (нині перший аргумент дробовий і результат не буде заокруглений).

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

 print (1000 * 60 * 60 * 24 * 7);
   // Обчислення для тижня
print (1000 * 60 * 60 * 24 * 30);
   // Обчислення для місяця

Необхідно перемножити кількість мілісекунд в одній секунді (1000), секунд - у хвилині (60), хвилин - в годині (60), годин - в дні (24) і днів - у тижні і місяці (7 і 30, відповідно). Одержуємо:

 604800000
-1702967296

Очевидно, у другому обчисленні сталося переповнення. Досить зробити останній аргумент величиною типу long:

 print (1000 * 60 * 60 * 24 * 30L);
   // Обчислення для місяця

Отримуємо правильний результат:

 2592000000

Подібні обчислення розумно переводити на 64-бітну точність не на останній операції, а заздалегідь, щоб уникнути переповнення.

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

 // Приклад викличе помилку компіляції
int x = 1;
byte b = x;

Хоча для програміста і очевидно, що змінна b повинна отримати значення 1, що легко вкладається в тип byte, проте компілятор не може обчислювати значення змінної x при обробці другого рядка, він знає лише, що її тип - int.

А от менш очевидний приклад:

 // Приклад викличе помилку компіляції
byte b = 1;
byte c = b +1;

І тут компілятор не зможе успішно завершити роботу. При операції додавання значення змінної b буде перетворено в тип int і таким же буде результат складання, а значить, його не можна так просто присвоїти змінної типу byte.

Аналогічно:

 // Приклад викличе помилку компіляції
int x = 2;
long y = 3;
int z = x + y;

Тут результат складання буде вжеbyte b = 1;

byte c = (byte)-b;
// Приклад викличе помилку компіляції
byte b = 5;
byte c =- b;

Навіть унарний оператор "-" проводить обчислення з точністю не менше 32 біт.

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

Отже, всі числові оператори повертають результат типу int або long. Однак існує два винятки.

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

b yte x = 5;
byte y1 = x + +;
   // На момент початку виконання x дорівнює 5
byte y2 = x -;
   // На момент початку виконання x дорівнює 6
byte y3 = + + x;
   // На момент початку виконання x дорівнює 5
byte y4 =-- x;
   // На момент початку виконання x дорівнює 6
print (y1);
print (y2);
print (y3);
print (y4);

У результаті отримуємо:

 5
6
6
5

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

 byte x =- 128;
print (-x);
byte y = 127;
print (+ + y); 

Результатом буде:

 128
-128

Цей приклад ілюструє питання перетворення типів при обчисленнях і випадки переповнення.

Другим винятком є оператор з умовою « ? : » . Якщо другий і третій операнди мають однаковий тип, то і результат операції буде такого ж типу.

byte x = 2;

byte y = 3;
byte z = (x> y)? x: y;
   // Вірно, x і y однакового типу
byte abs = (x> 0)? x:-x;
   // Невірно!

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

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

 int x = 1;
print ("x =" + x);

Результатом буде:

 x = 1

Зверніть увагу на наступний приклад:

 print (1 +2 + "text");
print ("text" +1 +2);

Його результатом буде:

 3text
text12

Окремо розглянемо роботу з типом char. Значення цього типу можуть повноцінно брати участь в числових операціях:

ch ar c1 = 10;
char c2 = 'A';
   // Латинська буква A (\ u0041, код 65)
int i = c1 + c2-'B ';

Змінна i отримає значення 9.

Розглянемо наступний приклад:

 char c = 'A';
print (c);
print (c +1);
print ("c =" + c);
print ('c'+'='+ с);

Його результатом буде:

 A
66
c = A
225

У першому випадку в метод print було передано значення типу char, тому відобразився символ. У другому випадку був переданий результат складання, тобто число, і саме число з'явилося на екрані. Далі при додаванні з рядком тип char був перетворений в текст у вигляді символу. Нарешті в останньому рядку відбулося складання трьох чисел: 'c' (код 99), '=' (код 61) і змінної c (тобто код 'A' - 65).

Для кожного примітивного типу існують спеціальні допоміжні класи-обгортки (wrapper classes). Для типів byte, short, int, long, char це Byte, Short, Integer, Long, Character. Ці класи містять багато корисних методів для роботи з цілочисельними значеннями. Наприклад, перетворення з тексту на число. Крім того, є клас Math, який хоч і призначений в основному для роботи з дробовими числами, але також надає деякі можливості й для цілих.

На закінчення підкреслимо, що єдині операції з цілими числами, при яких Java генерує помилки, - це поділ на нуль (оператори / і % ).

Дробові типи

Дробові типи - це float і double. Їх довжина - 4 і 8 байт, відповідно. Обидва типи знакові. Нижче в таблиці зведені їх характеристики:

Таблиця 4.2. Дробові типи даних.

Назва типу

Довжина (байт)

Область значень

Float

4

3.40282347e+38f ; 1.40239846e-45f

Double

8

1.79769313486231570e+308 ; 4.94065645841246544e-324

Для цілочисельних типів область значень задавалася верхньою і нижньою межами, вельми близькими по модулю. Для дробових типів додається ще одне обмеження - наскільки можна наблизитися до нуля, іншими словами - яке найменше позитивне ненульове значення. Таким чином, не можна задати літерал свідомо більший, ніж дозволяє відповідний тип даних, це призведе до помилки overflow. І не можна задати літерал, значення якого за модулем занадто мало для такого типу, компілятор згенерує помилку underflow.

 // Приклад викличе помилку компіляції
float f = 1e40f;
// Значення занадто велике, overflow
double d = 1e-350;
// Значення занадто мало, underflow 

       Нагадаємо, що коли наприкінці літерала стоїть буква F або f, то літерал розглядається як значення типу float. За замовчуванням дробовий літерал має тип double, при бажанні це можна підкреслити буквою D або d.

Над дробовими аргументами можна проводити наступні операції:

  • операції порівняння (повертають булево значення)
    • <, <=,>,> =
    • ==,! =
  • числові операції (повертають числове значення)
    • унарні операції + і -
    • арифметичні операції +, -, *, /,%
    • операції інкремента і декремента (в префіксной і постфіксній формі): + + і -
  • оператор з умовою « ? : »
  • оператор приведення типів
  • оператор конкатенації з рядком +

Практично всі оператори діють за тими ж принципами, що передбачені для цілочисельних операторів (оператор ділення із залишком % розглядалося в попередній лекції, а оператори + + і - також збільшують або зменшують значення змінної на одиницю). Уточнимо лише, що оператори порівняння коректно працюють і у разі порівняння цілочисельних значень з дробовими. Таким чином, в основному необхідно розглянути питання переповнення і перетворення типів при обчисленнях.

Для дробових обчислень з'являється вже два типи переповнення - overflow і underflow. Тим не менш, Java і тут ніяк не повідомляє про виникнення подібних ситуацій. Немає ні помилок, ні інших способів виявити їх. Більш того, навіть поділ на нуль не призводить до некоректної ситуації. А значить, дробові обчислення взагалі не породжують ніяких помилок.

Така свобода пов'язана з наявністю спеціальних значень дробового типу. Вони визначаються специфікацією IEEE 754 і вже перераховувалися в лекції 3:

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

Всі ці значення представлені як для типу float, так і для double.

Позитивну і негативну нескінченності можна отримати наступним чином:

 1f/0f // позитивна нескінченність,
         // Тип float
-1d/0d // Негативна нескінченність,
         // Тип double

Також у класах Float і Double визначені константи POSITIVE_INFINITY і NEGATIVE_INFINITY. Як видно з прикладу, такі величини виходять при діленні кінцевих величин на нуль.

Значення NaN можна отримати, наприклад, в результаті таких дій:

 0.0/0.0 // розподіл нуль на нуль
(1.0/0.0) * 0.0 // множення нескінченності на нуль

Ця величина також представлена константами NaN в класах Float і Double.

Величини позитивний і негативний нуль записуються очевидним чином:

 0.0 // дробовий літерал зі значенням
       // Позитивного нуля
+0.0 // Унарна операція +, її значення -
       // Додатний нуль
-0.0 // Унарна операція -, її значення -
       // Негативний нуль

Всі дробові значення строго впорядковані. Негативна нескінченність менше будь-якого іншого дробового значення, позитивна - більше. Значення +0.0 і -0.0 вважаються рівними, тобто вираз 0.0 == -0.0 істинно, а 0.0 > -0.0 - помилково. Однак інші оператори розрізняють їх, наприклад, вираз 1.0/0.0 дає позитивну нескінченність, а 1.0/-0.0 - негативну.

Єдине виключення - значення NaN. Якщо хоча б один з аргументів операції порівняння дорівнює NaN, то результат свідомо буде false (для оператора ! = Відповідно завжди true). Таким чином, єдине значення x, при якому вираз x! = x істинно, - саме NaN.

Повертаємося до питання переповнення в числових операціях. Якщо одержуване значення занадто велике за модулем (overflow), то результатом буде безкінечність відповідного знака.

 print (1e20f * 1e20f);
print (-1e200 * 1e200);

У результаті отримуємо:

 Infinity
-Infinity

Якщо результат, навпаки, виходить занадто малий (underflow), то він просто округляється до нуля. Так само чинять і в тому випадку, коли кількість десяткових знаків перевищує допустимий:

 print (1e-40f/1e10f); // underflow для float
print (-1e-300/1e100); // underflow для double
float f = 1e-6f;
print (f);
f + = 0.002f;
print (f);
f + = 3;
print (f);
f + = 4000;
print (f);

Результатом буде:

 0.0
-0.0
1.0E-6
0.002001
3.002001
4003.002

Як видно, в останньому рядку був втрачений 6-й розряд після десяткової точки.

Інший приклад (із специфікації мови Java):

 double d = 1e-305 * Math.PI;
print (d);
for (int i = 0; i <4; i + +)
print (d / = 100000);

Результатом буде:

3. 141592653589793E-305
3.1415926535898E-310
3.141592653E-315
3.142E-320
0.0 

Таким чином, як і для цілочисельних значень, явне виписування в коді літералів, які занадто великі (overflow) або занадто малі (underflow) для використовуваних типів, призводить до помилки компіляції (див. лекцію 3). Якщо ж переповнення виникає в результаті виконання операції, то повертається одне із спеціальних значень.

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

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

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

Ще раз розглянемо простий приклад:

 print (1 / 2);
print (1 / 2.);

Результатом буде:

 0
0.5

Досить одного дробового аргументу, щоб результат операції також мав дробовий тип.

Більш складний приклад:

 int x = 3;
int y = 5;
print (x / y);
print ((double) x / y);
print (1.0 * x / y);

Результатом буде:

 0
0.6
0.6

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

Приведення типів докладно розглядається в іншій лекції, проте звернемо тут увагу на кілька моментів.

По-перше, при приведенні дробових значень до цілих типів дробова частина просто відкидається. Наприклад, число 3.84 буде перетворено в ціле 3, а -3.84 перетвориться на -3. Для математичного округлення необхідно скористатися методом класу Math.round (...).

По-друге, при приведенні значень int до типу float і при приведенні значень типу long до типу float і double можливі втрати точності, незважаючи на те, що ці дробові типи вміщають набагато більші числа, ніж відповідні цілі. Розглянемо наступний приклад:

 long l = 111111111111L;
float f = l;
l = (long) f;
print (l);

Результатом буде:

 111111110656

Тип float не зміг зберегти всі значущі розряди, хоча перетворення від long до float відбулося без спеціального оператора на відміну від зворотного переходу.

Для кожного примітивного типу існують спеціальні допоміжні класи-обгортки (wrapper classes). Для типів float і double це Float і Double. Ці класи містять багато корисних методи для роботи з дробовими значеннями. Наприклад, перетворення з тексту на число.

Крім того, клас Math надає велику кількість методів для операцій над дробовими значеннями, наприклад, витяг квадратного кореня, зведення в будь-яку ступінь, тригонометричні та інші. Також у цьому класі визначено константи PI і основа натурального логарифма E.

Логічний тип

Логічний тип представлений лише одним типом boolean, який може зберігати всього два можливі значення - true і false. Величини саме цього типу виходять в результаті операцій порівняння.

Над булевими аргументами можна проводити наступні операції:

  • операції порівняння (повертають булево значення):
    • ==,! =
  • логічні операції (повертають булево значення):
    • !
    • &, |, ^
    • & &, | |
  • оператор з умовою « ? : »
  • оператор конкатенації з рядком +

Логічні оператори & & і | | обговорювалися в попередній лекції. У оператора з умовою « ? : » Першим аргументом може бути тільки значення типу boolean. Також допускається, щоб другий і третій аргументи одночасно мали логічний тип.

Операція конкатенації з рядком перетворює булеву величину в текст "true" або "false" залежно від значення.

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

Ніяке число не може бути інтерпретовано як логічне вираження. Якщо передбачається, що ненульове значення еквівалентно істині (за правилами мови С), то необхідно записати x! = 0. Вказівні величини можна перетворювати в boolean виразом ref! = Null.

Вказівні типи

Отже, вираз вказівного типу має значення або null, або посилання, що вказує на деякий об'єкт у віртуальній пам'яті JVM.

Об'єкти і правила роботи з ними

Об'єкт (object) - це екземпляр деякого класу, або примірник масиву. Масиви будуть детально розглядатися у відповідній лекції. Клас - це опис об'єктів однакової структури, і якщо в програмі такий клас використовується, то опис присутній в єдиному екземплярі. Об'єктів цього класу може не бути зовсім, а може бути створено як завгодно багато.

Об'єкти завжди створюються з використанням ключового слова new, причому одне слово new породжує суворо один об'єкт (або зовсім ні одного, якщо відбувається помилка). Після ключового слова вказується ім'я класу, від якого ми збираємося породити об'єкт. Створення об'єкта завжди відбувається через виклик одного з конструкторів класу (їх може бути декілька), тому на закінчення ставляться дужки, в яких перераховані значення аргументів, переданих обраному конструктору. У наведених вище прикладах, коли створювалися об'єкти типу Point, вираз new Point (3,5) означало звернення до конструктора класу Point, у якого є два аргументи типу int. До речі, обов'язкове оголошення такого конструктора у спрощеному оголошенні класу було відсутнє. Оголошення класів розглядається в наступних лекціях, проте наведемо правильне визначення Point:

 class Point {
   int x, y;
   /**
      * Конструктор приймає 2 аргументи,
      * Якими ініціалізує поля об'єкта.
      * /
   Point (int newx, int newy) {
      x = newx;
      y = newy;
   }
}

Якщо конструктор відпрацював успішно, то вираз new повертає посилання на створений об'єкт. Це посилання можна зберегти в змінній, передати в якості аргументу на якийсь метод або використовувати інший спосіб. JVM завжди займається підрахунком збережених посилань на кожен об'єкт. Як тільки виявляється, що посилань більше немає, такий об'єкт призначається для знищення збиральником сміття (garbage collector). Відновити посилання на такий "втрачений" об'єкт неможливо.

 Point p = new Point (1,2);
   // Створили об'єкт, отримали на нього посилання
Point p1 = p;
   // Тепер є 2 посилання на точку (1,2)
p = new Point (3,4);
   // Залишилося одне посилання на точку (1,2)
p1 = null;

Посилань на об'єкт-точку (1,2) більше немає, доступ до нього загублений і він незабаром буде знищений збиральником сміття.

Будь-який об'єкт породжується тільки із застосуванням ключового слова new. Єдине виключення - екземпляри класу String. Записуючи будь строковий літерал, ми автоматично породжуємо об'єкт цього класу. Оператор конкатенації +, результатом якого є рядок, також неявно породжує об'єкти без використання ключового слова new.

Розглянемо приклад:

"Abc" + "def"

При виконанні цього виразу буде створено три об'єкти класу String. Два об'єкти породжуються строковими літералами, третій буде представляти результат конкатенації.

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

Крім того, у версії Java 1.1 була введена технологія reflection, яка дозволяє звертатися до класів, методів і полях, використовуючи лише їх ім'я в текстовому вигляді. З її допомогою також можна створити об'єкт без ключового слова new, однак ця технологія досить специфічна, застосовується в окремих випадках, а крім того, досить проста і тому в даному курсі не розглядається. Все ж наведемо приклад її застосування:

 Point p = null;
try {
  // У наступному рядку, використовуючи лише
  // Текстове ім'я класу Point, породжується
  // Об'єкт без застосування ключового слова new
p = (Point) Class.forName ("Point"). newInstance ();
} Catch (Exception e) {/ / обробка помилок
   System.out.println (e);
}

Об'єкт завжди "пам'ятає", від якого класу він був породжений. З іншого боку, як вже вказувалося, можна посилатися на об'єкт, використовуючи посилання іншого типу. Наведемо приклад, який будемо ще багато разів використовувати. Спочатку опишемо два класи, Parent і його спадкоємець Child:

// Огол ошуємо клас Parent
class Parent {
}
// Оголошуємо клас Child і успадковуємо
// Його від класу Parent
class Child extends Parent {
} 

Поки що нам не потрібно визначати будь-які поля або методи. Далі оголосимо змінну одного типу і проініціалізіруем її значенням іншого типу:

 Parent p = new Child ();

Тепер змінна типу Parent вказує на об'єкт, породжений від класу Child.

Над посилальними значеннями можна проводити наступні операції:

  • звернення до полів і методів об'єкта
  • оператор instanceof (повертає логічне значення)
  • операції порівняння == і! = (повертають булево значення)
  • оператор приведення типів
  • оператор з умовою? :
  • оператор конкатенації з рядком +

Звернення до полів і методів об'єкта можна назвати основною операцією над посилальними величинами. Здійснюється вона за допомогою символу. (Крапка). Приклади її застосування будуть приводитися.

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

 Parent p = new Child ();
// Перевіряємо змінну p типу Parent
// На сумісність з типом Child
print (p instanceof Child);

Результатом буде true. Таким чином, оператор instanceof спирається не на тип посилання, а на властивості об'єкта, на який вона посилається. Але цей оператор повертає істинне значення не тільки для того типу, від якого був породжений об'єкт. Додамо до вже оголошених класів ще один:

 // Оголошуємо новий клас і успадковуємо
// Його від класу Child
class ChildOfChild extends Child {}

Тепер заведемо змінну нового типу:

 Parent p = new ChildOfChild ();
print (p instanceof Child);

У першому рядку оголошується змінна типу Parent, яка ініціалізується посиланням на об'єкт, породжений від ChildOfChild. У другому рядку оператор instanceof аналізує сумісність посилання типу Parent з класом Child, причому задіяний об'єкт не породжений ні від першого, ні від другого класу. Тим не менш, оператор поверне true, оскільки клас, від якого цей об'єкт породжений, успадковується від Child.

Додамо ще один клас:

 class Child2 extends Parent {
}

І знову оголосимо змінну типу Parent:

 Parent p = new Child ();
print (p instanceof Child);
print (p instanceof Child2);

Змінна p має тип Parent, а значить, може посилатися на об'єкти типу Child або Child2. Оператор instanceof допомагає розібратися в ситуації:

 true
false

Для посилання, рівного null, оператор instanceof завжди поверне значення false.

З вивченням властивостей об'єктної моделі Java ми будемо повертатися до алгоритму роботи оператора instanceof.

Оператори порівняння == і ! = Перевіряють рівність (або нерівність) об'єктних величин саме по посиланню. Однак часто потрібне альтернативне порівняння - за значенням. Порівняння за значенням має справу з поняттям стан об'єкта. Сам зміст цього виразу розглядається в ООП, що ж стосується реалізації в Java, то стан об'єкту зберігається в його полях. При порівнянні за посиланням ні тип об'єкта, ні значення його полів не враховуються, true повертається тільки в тому випадку, якщо обидві посилання вказують на один і той самий об'єкт.

 Point p1 = new Point (2,3);
Point p2 = p1;
Point p3 = new Point (2,3);
print (p1 == p2);
print (p1 == p3);

Результатом буде:

 true
false

Перше порівняння виявилося істинним, так як змінна p2 посилається на той самий об'єкт, що і p1. Друге ж порівняння помилково, незважаючи на те, що змінна p3 посилається на об'єкт-точку з точно такими ж координатами. Однак це інший об'єкт, який був породжений іншим виразом new.

Якщо один з аргументів оператора == дорівнює null, а інший - ні, то значення такого виразу буде false. Якщо ж обидва операнда null, то результат буде true.

Для коректного порівняння за значенням існує спеціальний метод equals, який буде розглянуто пізніше. Наприклад, рядки треба порівнювати наступним чином:

 String s = "abc";
s = s +1;
print (s.equals ("abc1"));

Операція з умовою « ? : » працює як завжди і може приймати другий і третій аргументи, якщо вони обидва одночасно посилального типу. Результат такого оператора також буде мати об'єктний тип.

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

Клас Object

У Java множинне успадкування відсутнє. Кожен клас може мати тільки одного з батьків. Таким чином, ми можемо простежити ланцюжок успадкування від будь-якого класу, піднімаючись все вище. Існує клас, на якому такий ланцюжок завжди закінчується, це клас Object. Саме від нього успадковуються всі класи, в оголошенні яких явно не зазначено іншого батьківський клас. А значить, будь-який клас безпосередньо, або через своїх батьків, є спадкоємцем Object. Звідси випливає, що методи цього класу є у будь-якого об'єкта (поля у Object відсутні), а тому вони становлять особливий інтерес.

Розглянемо основні з них.

getClass ()

Цей метод повертає об'єкт класу Class, який описує клас, від якого був породжений цей об'єкт. Клас Class буде розглянуто нижче. У нього є метод getName (), який повертає ім'я класу:

 String s = "abc";
Class cl = s.getClass ();
print (cl.getName ());

Результатом буде рядок:

 java.lang.String

На відміну від оператора instanceof, метод getClass () завжди повертає точно той клас, від якого був породжений об'єкт.

equals ()

Цей метод має один аргумент типу Object і повертає boolean. Як вже говорилося, equals () служить для порівняння об'єктів за значенням, а не по посиланню. Порівнюється стан об'єкта, у якого викликається цей метод, з передаються аргументом.

 Point p1 = new Point (2,3);
Point p2 = new Point (2,3);
print (p1.equals (p2));

Результатом буде true.

Оскільки сам Object не має полів, а значить, і стану, у цьому класі метод equals повертає результат порівняння за посиланням. Однак при написанні нового класу можна перевизначити цей метод і описати правильний алгоритм порівняння за значенням (що і зроблено в більшості стандартних класів). Відповідно, в клас Point також необхідно додати перевизначення методів порівняння:

 public boolean equals (Object o) {
   // Спочатку необхідно переконатися, що
   // Переданий об'єкт сумісний з типом
   // Point
   if (o instanceof Point) {
      // Типи сумісні, можна провести
      // Перетворення
      Point p = (Point) o;
      // Повертаємо результат порівняння
      // Координат
      return p.x == x & & p.y == y;
   }
   // Якщо об'єкт не сумісний з Point,
   // Повертаємо false
   return false;
}

hashCode ()

Даний метод повертає значення int. Мета hashCode () - представити будь-який об'єкт цілим числом. Особливо ефективно це використовується в хеш-таблицях (в Java є стандартна реалізація такого зберігання даних, вона буде розглянута пізніше). Звичайно, не можна вимагати, щоб різні об'єкти повертали різні хеш-коди, але, принаймні, необхідно, щоб об'єкти, рівні за значенням (метод equals () повертає true), повертали однакові хеш-коди.

У класі Object цей метод реалізований на рівні JVM. Сама віртуальна машина генерує число хеш-кодів, засновуючись на розташуванні об'єкта в пам'яті.

toString ()

Цей метод дозволяє отримати текстовий опис будь-якого об'єкта. Створюючи новий клас, даний метод можна перевизначити і повертати більш докладний опис. Для класу Object і його спадкоємців, не перевизначені toString (), метод повертає такий вираз:

 getClass (). getName ()+"@"+ hashCode ()

Метод getName () класу Class вже наводився в приклад, а хеш-код ще додатково обробляється спеціальною функцією для подання в шістнадцятковому форматі.

Наприклад:

 print (new Object ());

Результатом буде:

 java.lang.Object @ 92d342

У результаті цей метод дозволяє по текстового опису зрозуміти, від якого класу був породжений об'єкт і, завдяки хеш-кодом, розрізняти різні об'єкти, створені від одного класу.

Саме цей метод викликається при конвертації об'єкта в текст, коли він приймає в якості аргументу оператору конкатенації рядків.

finalize ()

Даний метод викликається при знищенні об'єкта автоматичним збиральником сміття (garbage collector). У класі Object він нічого не робить, однак у класі-спадкоємця дозволяє описати всі дії, необхідні для коректного видалення об'єкта, такі як закриття сполук з БД, мережевих з'єднань, зняття блокувань на файли і т.д. У звичайному режимі безпосередньо цей метод викликати не потрібно, він відпрацює автоматично. Якщо необхідно, можна звернутися до нього явним чином.

У методі finalize () потрібно описувати тільки додаткові дії, пов'язані з логікою роботи програми. Все необхідне для видалення об'єкта JVM зробить сама.

Клас String

Як вже вказувалося, клас String займає в Java особливе положення. Примірники тільки цього класу можна створювати без використання ключового слова new. Кожен строковий літерал породжує примірник String, і це єдиний літерал (крім null), що має об'єктний тип.

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

Ще однією важливою властивістю даного класу є незмінюваність. Це означає, що, породивши об'єкт, що містить якесь значення-рядок, ми вже не можемо змінити дане значення - необхідно створити новий об'єкт.

 String s = "a";
s = "b";

У другому рядку змінна змінила своє значення, але тільки створивши новий об'єкт класу String.

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

По-перше, якщо використовується кілька літералів з однаковим значенням, для них буде створено один і той самий об'єкт.

 String s1 = "abc";
String s2 = "abc";
String s3 = "a" + "bc";
print (s1 == s2);
print (s1 == s3);

Результатом буде:

 true
true

Тобто у випадку, коли рядок конструюється з констант, відомих уже на момент компіляції, оптимізатор також підставляє один і той самий об'єкт.

Якщо ж рядок створюється виразом, який може бути обчислено тільки під час виконання програми, то воно буде породжувати новий об'єкт:

 String s1 = "abc";
String s2 = "ab";
print (s1 == (s2 + "c"));

Результатом буде false, тому що компілятор не може передбачити результат складання значення змінної з константою.

У класі String визначено метод intern (), який повертає один і той самий об'єкт-рядок для всіх примірників, рівних за значенням. Тобто, якщо для посилань s1 і s2 вірно вираз s1.equals (s2), то вірно і s1.intern () == s2.intern ().

Зрозуміло, в класі перевизначені методи equals () і hashCode (). Метод toString () також перевизначений і повертає він сам об'єкт-рядок, тобто для будь-якого посилання s типу String, що не дорівнює null, вірно вираз s == s.toString ().

Клас Class

Нарешті, останній клас, який буде розглянуто в цій лекції.

Клас Class є метаклассом для всіх класів Java. Коли JVM завантажує файл. Class, який описує певний тип, в пам'яті створюється об'єкт класу Class, який буде зберігати цей опис.

Наприклад, якщо в програмі є рядок

 Point p = new Point (1,2);

то це означає, що в системі створено наступні об'єкти:

  1. об'єкт типу Point, що описує точку (1,2);
  2. об'єкт класу Class, описує клас Point;
  3. об'єкт класу Class, описує клас Object. Оскільки клас Point успадковується від Object, його опис також необхідно;
  4. об'єкт класу Class, описує клас Class. Це звичайний Java-клас, який повинен бути завантажений за загальними правилами.

Одне із застосувань класу Class вже було розглянуто - використання методу getClass () класу Object. Якщо продовжити останній приклад з точкою:

 Class cl = p.getClass ();
   // Це об'єкт № 2 зі списку
Class cl2 = cl.getClass ();
   // Це об'єкт № 4 зі списку
Class cl3 = cl2.getClass ();
   // Знову об'єкт № 4

Вираз cl2 == cl3 вірно.

Інше застосування класу Class також наводилося в прикладі застосування технології reflection.

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

Висновок

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

  • оголошення типів;
  • створення об'єктів;
  • при оголошенні полів - тип поля;
  • при оголошенні методів - вхідні параметри, значення, що повертається;
  • при оголошенні конструкторів - вхідні параметри;
  • оператор приведення типів;
  • оператор instanceof;
  • оголошення локальних змінних;
  • багато інших - обробка помилок, import-вирази і т.д.

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

В обговоренні майбутньої версії Java 1.5 згадуються темплейти (templates), які суттєво розширять поняття типу даних, якщо дійсно увійдуть в стандарт мови.

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

Всі типи в Java діляться на дві групи - фіксовані прості, або примітивні типи (8 типів) і численна група об'єктних типів (класів). Примітивні типи дійсно є сховищами даних свого типу. Вказівні змінні зберігають посилання на деякий об'єкт сумісного типу. Вони також можуть приймати значення null, не вказуючи ні на який об'єкт. JVM підраховує кількість посилань на кожен об'єкт і активізує механізм автоматичного збирання сміття для видалення невживаних об'єктів.

Були розглянуті змінні. Вони характеризуються трьома основними параметрами - ім'я, тип та значення. Будь-яка змінна повинна бути оголошена і при цьому може бути ініціалізований. Можливе використання модифікатора final.

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

Потім вивчалися об'єкти, способи їх створення і оператори, що здійснюють над ними різні дії, зокрема принцип роботи оператора instanceof. Далі були розглянуті найголовніші класи в Java - Object, Class, String.

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