1. Приведение типов

Приведение типов в Java

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

Можно присваивать друг другу переменные разных типов. При этом значение, взятое из переменной одного типа, будет преобразовано в значение другого типа и присвоено второй переменной. В связи с этим можно выделить два вида преобразования типов: расширение и сужение.

Расширение типа похоже на перекладывание из маленькой корзинки в большую — операция проходит незаметно и безболезненно. Сужение типа — это перекладывание из большой корзинки в маленькую: места может не хватить, и что-то придётся выбросить.

Вот типы, отсортированные по размеру «корзинки»:

Приведение типов в Java 2


2. Расширение типов

Часто может возникнуть необходимость присвоить переменной одного числового типа значение переменной другого числового типа. Как же это сделать?

В Java есть 4 целочисленных типа:

Тип Размер
byte 1 байт
short 2 байта
int 4 байта
long 8 байт

Переменной большего размера всегда можно присваивать переменные меньшего размера.

Переменной типа long спокойно можно присваивать переменные типа int, short и byte. Переменной типа int можно присваивать переменные типа short и byte. Ну и переменной типа short можно присваивать переменные типа byte.

Примеры:

Код Описание
byte a = 5;
short b = a;
int c = a + b;
long d = c * c;
Этот код отлично скомпилируется.

Такое преобразование, от типа меньшего размера к большему, называется расширением типа.

А что насчет вещественных чисел?

С ними все аналогично — размер имеет значение:

Тип Размер
float 4 байта
double 8 байт

Переменной типа double можно без проблем присвоить переменную типа float. А вот с целочисленными типами интереснее.

Переменной типа float можно присвоить переменную любого целочисленного типа. Даже типа long, длина которого 8 байт. А переменной типа double можно присвоить вообще что угодно: переменную любого целочисленного типа и переменную типа float:

Код Примечание
long a = 1234567890;
float b = a;
double c = a;

b == 1.23456794E9
c == 1.23456789E9

Обратите внимание, что преобразование к вещественному типу может привести к потере точности из-за нехватки значащих цифр.

При преобразовании из целых чисел в дробные могут отбрасываться самые младшие части числа. Но т.к. смысл дробного числа в том, чтобы хранить приблизительное значение, такое присваивание разрешается.


3. Сужение типов

А что насчет остальных вариантов? Что делать, если нужно переменной типа int присвоить значение переменной типа long?

Представьте, что переменная — это корзина. У нас есть корзины разных размеров: 1, 2, 4 и 8 байт. При перекладывании пирожков из меньшей корзины в большую проблем не будет. А вот при перекладывании из большей в меньшую часть пирожков может потеряться.

Это преобразование — от типа большего размера к меньшему — называют сужением типа. При таком присваивании часть числа может просто не поместиться в новую переменную и «остаться за бортом».

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

В таких ситуациях Java-компилятор требует от программиста указывать оператор преобразования типа. Выглядит в общем виде он так:

(тип) выражение

Примеры:

Код Описание
long a = 1;
int b = (int) a;
short c = (short) b;
byte d = (byte) c;
Каждый раз нужно явно указывать оператор преобразования типа

В данном случае a равно 1, и это кажется излишним. А что если бы a было больше?

Код Описание
long a = 1000000;
int b = (int) a;
short c = (short) b;
byte d = (byte) c;
a == 1000000
b == 1000000
c == 16960
d == 64

Миллион отлично помещается и в тип long, и в тип int. А вот при присваивании миллиона переменной типа short два первых байта были отброшены, и остались только два последних байта. А при присваивании типу byte вообще остался один последний байт.

Устройство чисел в памяти:

Тип Двоичная запись Десятичная запись
int 0b00000000000011110100001001000000 1,000,000
short 0b0100001001000000 16,960
byte 0b01000000 64

Тип char

Тип char, как и тип short, занимает два байта, но для их преобразования в друг друга всегда нужно использовать оператор приведения типа. Все дело в том, что тип short знаковый, и может содержать значения от -32,768 до +32,767, а тип char беззнаковый, и может содержать значения от 0 до 65,535.

В char нельзя сохранить отрицательные числа, которые могут храниться в short. А в short нельзя сохранить числа больше 32,767, которые могут храниться в char.


4. Тип выражения

А что делать, если в одном выражении используются переменные разных типов? Логичный ответ – их сначала нужно преобразовать к общему типу. Но какому?

Конечно же, к большему.

В Java всегда происходит преобразование к типу большей размерности. Грубо говоря, сначала происходит расширение типа одного из участников операции, а уже затем операция со значениями одинаковых типов.

Если в выражении участвуют типы int и long, значение типа int будет преобразовано к типу long и только затем будет участвовать в операции:

Код Описание
int a = 1;
long b = 2;
long c = a + b;
a будет расширена до типа long и только затем произойдет сложение.

Числа с плавающей точкой

Если в выражении участвуют целое число и число с плавающей точкой (float/double), целое число будет преобразовано в число с плавающей точкой (float/double), и только потом будет выполнена операция над ними.

Если в операции участвуют float и double, то float будет преобразован к double. Что, собственно говоря, ожидаемо.

Сюрприз

Типы byte, short, char всегда преобразовываются в тип int при взаимодействии между собой. Не зря же тип int считается стандартным целочисленным типом.

Если умножить byte на short, будет int. Если умножить byte на byte, будет int. Даже если сложить byte и byte, будет int.

Тому есть несколько причин. Примеры:

Код Описание
byte a = 110;
byte b = 120;
byte c = a * b;   // ошибка
110 * 120 будет 13,200, что несколько больше, чем максимальное значение типа byte: 127
byte a = 110;
byte b = 120;
byte c = a + b;   // ошибка
110 + 120 будет 230, что тоже несколько больше, чем максимальное значение типа byte: 127

В общем случае при умножении числа длиной в 8 бит (1 байт) на число длиной в 8 бит (1 байт), мы получим число длиной 16 бит (2 байта)

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

Примеры:

Код Описание
byte a = 110;
byte b = 120;
byte c = (byte) (a * b);
выражение byte * byte будет иметь тип int
byte a = 110;
byte b = 120;
byte c = (byte) (a + b);
выражение byte + byte будет иметь тип int
byte a = 1;
byte b = (byte) (a + 1);
выражение byte + int будет иметь тип int
единица – это литерал типа int.

5. Важный нюанс

Операция приведения типа имеет довольно высокий приоритет.

Поэтому если в выражении есть, допустим, сложение и операция приведения типа, она будет выполнена до сложения.

Пример:

Код Описание
byte a = 1;
byte b = 2;
byte c = (byte) a * b;
Оператор приведения типа будет применен только к переменной a, которая и так имеет тип byte. Код не скомпилируется.
byte a = 1;
byte b = 2;
byte c = (byte) (a * b);
Вот так правильно.

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