Глава 5.4. Преобразования типов

5.4.1. Виды преобразований

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

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

  • Преобразование типа int в тип long требует во время выполнения программы расширения знака 32-битового целого значения до 64-битового целого. Потери информации при этом не происходит.
  • Преобразование типа float в тип long требует во время выполнения программы нетривиального преобразования 32-битового плавающего значения в 64-битовое целое. В зависимости от исходного значения может произойти или не произойти потеря информации.
  • Преобразование типа Thread в тип Object не требует никаких действий: поскольку класс Thread является потомком класса Object, любая ссылка на объект типа Thread автоматически является ссылкой на объект типа Object.
  • Преобразование типа Object в тип Thread требует проверки в период исполнения. Если преобразуемая ссылка действительно является ссылкой на объект типа Thread, то она возвращается как результат преобразования, в противном случае генерируется исключение.

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

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.

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

class Test {
  public static void main(String args[]) {
    int bigNumber = 1234567890;
    float approximate = bigNumber;
    System.out.println(approximate);
  }
}

выведет на экран строку 1234567936. Это связано с тем, что при преобразовании int в float результирующее значение равно 1.2345679E9 из-за того, что мантисса чисел типа float вмещает только 8 десятичных цифр (здесь для правильной работы следует использовать преобразование к типу double). Тем не менее, исполняющая система никогда не генерирует ошибок при выполнении перечисленных преобразований.

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.

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

short s = 123;
char c = s;		// генерирует ошибку компиляции

приведет к генерации ошибки, поскольку типы char и short несовместимы по присваиванию согласно данных выше определениям (первый реализован 16-битовыми словами без знака, а второй — со знаком).

5.4.2.2. Преобразование аргументов метода

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

class Test {
  static int m(byte a, int b) { return a + b; }
  static int m(short a, short b) { return a - b; }
  public static void main(String args[]) {
    System.out.println(m(1, 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.