WDH Java - Перетворення типів

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

Перетворення типу Т1 на тип T2 дозволяє виразу типу T1 трактуватися в період компіляції як вираз типу T2. В одних випадках це суто синтаксична конструкція, що не впливає на код, що генерується, в інших перетворення типу вимагає додаткових дій у період виконання зі зміни значення виразу або додаткових перевірок правильності застосовуваного перетворення. Приклади:

Далі в цьому розділі наведено класифікацію всіх можливих перетворень типів, а потім класифікацію контекстів, в якій вони можуть використовуватися.

5.4.1.1. Розширювальні перетворення чисел

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

  • перетворенняbyte вshort,int,long,float таdouble ;
  • перетворенняshort вint,long,float таdouble ;
  • перетворенняchar вint,long,float таdouble ;
  • перетворенняint вlong,float таdouble ;
  • перетворенняlong вfloat таdouble ;
  • перетворенняfloat вdouble.

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

виведе на екран рядок 1234567936. Це з тим, що з перетворенняint вfloat результуюче значення дорівнює 1.2345679E9 через те, що мантиса чисел типуfloat містить лише 8 десяткових цифр (тут для правильної роботи слід використовувати перетворення до типуdouble(7)). Проте виконуюча система ніколи не генерує помилок при виконанні перерахованих перетворень.

5.4.1.2. Звуження перетворення чисел

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

  • перетворенняbyte вchar ;
  • перетворенняshort вbyte таchar ;
  • перетворенняint вbyte,short таchar ;
  • перетворенняlong вbyte,short,int таchar ;
  • перетворенняfloat вbyte,short,int,long таchar ;
  • перетворенняdouble вbyte,short,int,long,float таchar ;

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

5.4.1.3.Розширювальні перетворення посилань

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

  • перетворення будь-якого класу чи інтерфейсу на його предка (зокрема, у тип Object);
  • перетворення класу на інтерфейс, який він реалізує;
  • перетворення будь-якого масиву на тип Object або тип Cloneable;
  • перетворення масиву типу S[] в масив типу T[], якщо S і T¦ посилальні типи, і перетворення S T є розширюючим;
  • перетворення нульового типу в будь-який тип посилань.

5.4.1.4. Звуження перетворення посилань

Звуження перетворення посилань - це перетворення похідних посилальних типів в типи їх нащадків. Ці перетворення вимагають перевірки своєї легітимності на етапі виконання та можуть генерувати виняток ClassCastException. Такими перетвореннями в Java є:

  • перетворення будь-якого класу на його нащадка (зокрема, перетворення типу Object на будь-який інший клас);
  • перетворення класу на інтерфейс, коли клас не є фінальним і не реалізує даний інтерфейс (зокрема, перетворення типу Object на будь-який інтерфейс);
  • перетворення типу Object у будь-який масив;
  • перетворення будь-якого інтерфейсу на клас, який не є фінальним;
  • перетворення будь-якого інтерфейсу на клас, який є фінальним і реалізує даний інтерфейс;
  • перетворення інтерфейсу J в інтерфейс K, коли J не є нащадком K, і не існує методу, декларованого і в J, і K з однаковою сигнатурою, але різними типами результату;
  • перетворення масиву типу S[]масив типу T[], якщо S і T ¦ посилальні типи, і перетворення S T є звужуючим.

5.4.1.5. Перетворення на рядки

Будь-який вираз у Java, включаючиnull, може бути перетворено на тип String.

5.4.1.6. Неприпустимі перетворення

Наступні перетворення типів Java заборонені:

  • перетворення будь-якого типу посилань на будь-який примітивний тип;
  • перетворення будь-якого примітивного типу в будь-який тип посилань, крім типу String;
  • перетворення нульового типу у будь-який примітивний тип;
  • перетворення на нульовий тип або типboolean ;
  • перетворення типуboolean будь-який інший тип, крім типу String;
  • перетворення одного класу в інший, якщо жоден з них не є предком іншого (крім перетворення на тип String);
  • перетворення класу на інтерфейс, якщо клас є фінальним і не реалізує даний інтерфейс;
  • перетворення класу на масив, якщо клас відмінний від Object;
  • перетворення інтерфейс на клас, який є фінальним і не реалізує даний інтерфейс (крім перетворення на тип String);
  • перетворення інтерфейсу J в інтерфейс K, якщо існує метод, декларований і J, і K з однаковою сигнатурою, але різними типами результату;
  • перетворення масиву на клас, відмінний від Object і String;
  • перетворення масиву на інтерфейс, відмінний від Cloneable;
  • перетворення масиву типу S[] масив типу T[], якщо перетворення S T є забороненим

5.4.2. Контексти перетворень

5.4.2.1. Перетворення під час присвоєння

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

  • змінна має типbyte,short абоchar ;
  • значенням виразу є константа типуint, яка потрапляє до діапазону можливих значень змінної.

Наприклад, оператор byte x = 123; допустимо, оскільки константа 123 (має типint ) лежить у діапазоні допустимих значень типуbyte.

Якщо тип виразу не може бути перетворений на тип змінної, то компілятор генерує помилку. В інших випадках ми говоримо, що тип виразу сумісний щодо присвоєння з типом змінної. Так, наступний фрагмент

призведе до генерації помилки, оскільки типиchar таshort несумісні з присвоєння згідно з даними вище визначеннями (перший реалізований 16-бітовими словами без знака, а другий зі знаком).

5.4.2.2. Перетворення аргументів методу

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

Тут клас Test містить два однойменних методи, які відрізняються лише типами параметрів. Якби звужувальні перетворення аргументів були Java дозволені, то виконуючої системі довелося б визначати, якого з цих методів належить виклик m(1, 2) . Щоб уникнути подібних двозначностей, розробники мови вирішили проблему радикально: вони заборонили подібнівиклики методів. У цій ситуації для виклику, наприклад, першого методу ми маємо явно вказати тип першого операнда (другий за умовчанням має типint ), саме m((byte)1, 2) .

5.4.2.3. Перетворення на рядок

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

5.4.2.4. Явне перетворення типу

Явне перетворення типу відбувається, коли до операнда явно застосовується операція приведення типу (type cast). У цій ситуації можуть застосовуватися всі описані вище види перетворень типів, крім перетворення на рядок. Спроба явного перетворення типу, зазначена вище, як заборонена, викликає помилку компіляції. Крім того, на етапі виконання можлива генерація виключення ClassCastException, якщо задане перетворення є неприпустимим.

5.4.3. Перетворення типів числових операндів

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

Перед виконанням унарної операції:

  • якщо операнд має типbyte,short абоchar, він перетворюється на типint ;
  • в інших випадках його тип не змінюється.

Перед виконанням бінарної операції:

  • якщо один із операндів типуdouble, то другий також перетворюється на типdouble ;
  • в іншому випадку, якщо один з операндів типуfloat, то другий також перетворюється на типfloat ;
  • в іншому випадку, якщо один з операндів типуlong, то другий також перетворюється на типlong ;
  • в іншому випадку, обидва операнди перетворюються до типуint.