Преобразование и приведение примитивных типов
Иногда возникают ситуации, когда необходимо переменной одного типа присвоить значение переменной другого типа. Например:
int i = 11; byte b = 22; i = b;
В Java существует два типа преобразований — автоматическое преобразование (неявное) и приведение типов (явное преобразование).
1. Автоматическое преобразование типов Java
Рассмотрим сначала автоматическое преобразование. Если оба типа совместимы, их преобразование будет выполнено в Java автоматически. Например, значение типа byte всегда можно присвоить переменной типа int, как это показано в предыдущем примере.
Для автоматического преобразования типа должно выполняться два условия:
- оба типа должны быть совместимы
- длина целевого типа должна быть больше длины исходного типа
В этом случае происходит преобразование с расширением.
Следующая схема показывает расширяющее преобразование в Java:
Сплошные линии обозначают преобразования, выполняемые без потери данных. Штриховые линии говорят о том, что при преобразовании может произойти потеря точности.
Например, тип данных int всегда достаточно велик, чтобы хранить все допустимые значения типа byte , поэтому никакие операторы явного приведения типов в данном случае не требуются. С точки зрения расширяющего преобразования числовые типы данных, в том числе целочисленные и с плавающей точкой, совместимы друг с другом. В то же время не существует автоматических преобразований числовых типов в тип char или boolean . Типы char и boolean также не совместимы друг с другом.
Стоит немного пояснить почему, к примеру тип byte не преобразуется автоматически (не явно) в тип char, хотя тип byte имеет ширину 8 бит, а char — 16. То же самое касается и преобразования типа short в char . Это происходит потому, что byte и short знаковые типы данных, а char беззнаковый. Поэтому в данном случае требуется использовать явное приведение типов, поскольку компилятору надо явно указать, что вы знаете чего хотите и как будет обрабатываться знаковый бит типов byte и short при преобразовании к типу char .
Поведение величины типа char в большинстве случаев совпадает с поведением величины целого типа, следовательно, значение типа char можно использовать везде, где требуются значения int или long . Однако напомним, что тип char не имеет знака, поэтому он ведет себя отлично от типа short , несмотря на то что диапазон обоих типов равен 16 бит.
2. Приведение типов Java
Несмотря на все удобство автоматического преобразования типов, оно не в состоянии удовлетворить все насущные потребности. Например, что делать, если значение типа int нужно присвоить переменной типа byte ? Это преобразование не будет выполняться автоматически, поскольку длина типа byte меньше, чем у типа int . Иногда этот вид преобразования называется сужающим преобразованием, поскольку значение явно сужается, чтобы уместиться в целевом типе данных.
Чтобы выполнить преобразование двух несовместимых типов данных, нужно воспользоваться приведением типов. Приведение — это всего лишь явное преобразование типов. Общая форма приведения типов имеет следующий вид:
(целевой_тип) значение
где параметр целевой_тип обозначает тип, в который нужно преобразовать указанное значение.
Например, в следующем фрагменте кода тип int приводится к типу byte :
int i = 11; byte b = 22; b = (byte) i;
Рассмотрим пример преобразования значений с плавающей точкой в целые числа. В этом примере дробная часть значения с плавающей точкой просто отбрасывается (операция усечения):
double d = 3.89; int a = (int) d; //Результат будет 3
При приведении более емкого целого типа к менее емкому старшие биты просто отбрасываются:
int i = 323; byte b = (byte) i; //Результат будет 67
При приведении более емкого значения с плавающей точкой в целое число происходит усечение и отбрасывание старших битов:
double d = 389889877779.89; short s = (short) d; //Результат будет -1
3. Автоматическое продвижение типов в выражениях
Помимо операций присваивания, определенное преобразование типов может выполняться и в выражениях.
В языке Java действуют следующие правила:
- Если один операнд имеет тип double , другой тоже преобразуется к типу double .
- Иначе, если один операнд имеет тип float , другой тоже преобразуется к типу float .
- Иначе, если один операнд имеет тип long , другой тоже преобразуется к типу long .
- Иначе оба операнда преобразуются к типу int .
- В выражениях совмещенного присваивания (+=,-=,*=,/=) нет необходимости делать приведение.
При умножении переменной b1 ( byte ) на 2 ( int ) результат будет типа int . Поэтому при попытке присвоить результат в переменную b2 ( byte ) возникнет ошибка компиляции. Но при использовании совмещенной операции присваивания (*=), такой проблемы не возникнет:
byte b1 = 1; byte b2 = 2 * b1; //Ошибка компиляции int i1 = 2 * b1; b2 *= 2;
В следующем примере тоже возникнет ошибка компиляции — несмотря на то, что складываются числа типа byte , результатом операции будет тип int , а не short .
Java/Приведение типов
Допустим, вам необходимо перевести short в int или наоборот, в Java вы можете это сделать двумя путями: явным и неявным:
int a = 3; short b = 7; a = b; //Неявное приведение a = (int) b; //Явное приведение b = a; //Вот так нельзя! b = (short) a; //Явное приведение
Явное приведение типов сообщает компилятору или человеку читающему код, что вы знаете о том что это не тот тип, но уверены в том что у вас всё получится.
Неявное же приведение типов компилятор замечает, и если программист переводит более большой тип в меньший, он будет ругаться.
Также при неявном приведении дробного в целочисленный или наоборот компилятор тоже будет ругаться.
И пример логики приведения:
int a = 1611; double b = 71.92; long c = 7129391011L; byte d = 3; d = c; //Ошибка компиляции System.out.println((double) a) //Вывод: 1611.0 System.out.println((int) b) //Вывод: 71
Что такое явные и неявные приведения с чем связано их наличие java
Каждый базовый тип данных занимает определенное количество байт памяти. Это накладывает ограничение на операции, в которые вовлечены различные типы данных. Рассмотрим следующий пример:
int a = 4; byte b = a; // ! Ошибка
В данном коде мы столкнемся с ошибкой. Хотя и тип byte, и тип int представляют целые числа. Более того, значение переменной a, которое присваивается переменной типа byte, вполне укладывается в диапазон значений для типа byte (от -128 до 127). Тем не менее мы сталкиваемся с ошибкой на этапе компиляции. Поскольку в данном случае мы пытаемся присвоить некоторые данные, которые занимают 4 байта, переменной, которая занимает всего один байт.
Тем не менее в программе может потребоваться, чтобы подобное преобразование было выполнено. В этом случае необходимо использовать операцию преобразования типов (операция () ):
int a = 4; byte b = (byte)a; // преобразование типов: от типа int к типу byte System.out.println(b); // 4
Операция преобразования типов предполагает указание в скобках того типа, к которому надо преобразовать значение. Например, в случае операции (byte)a , идет преобразование данных типа int в тип byte. В итоге мы получим значение типа byte.
Явные и неявные преобразования
Когда в одной операции вовлечены данные разных типов, не всегда необходимо использовать операцию преобразования типов. Некоторые виды преобразований выполняются неявно, автоматически.
Автоматические преобразования
Стрелками на рисунке показано, какие преобразования типов могут выполняться автоматически. Пунктирными стрелками показаны автоматические преобразования с потерей точности.
Автоматически без каких-либо проблем производятся расширяющие преобразования (widening) — они расширяют представление объекта в памяти. Например:
byte b = 7; int d = b; // преобразование от byte к int
В данном случае значение типа byte, которое занимает в памяти 1 байт, расширяется до типа int, которое занимает 4 байта.
Расширяющие автоматические преобразования представлены следующими цепочками:
byte -> short -> int -> long
short -> float -> double
Автоматические преобразования с потерей точности
Некоторые преобразования могут производиться автоматически между типами данных одинаковой разрядности или даже от типа данных с большей разрядностью к типа с меньшей разрядностью. Это следующие цепочки преобразований: int -> float , long -> float и long -> double . Они производятся без ошибок, но при преобразовании мы можем столкнуться с потерей информации.
int a = 2147483647; float b = a; // от типа int к типу float System.out.println(b); // 2.14748365E9
Явные преобразования
Во всех остальных преобразованиях примитивных типов явным образом применяется операция преобразования типов. Обычно это сужающие преобразования (narrowing) от типа с большей разрядностью к типу с меньшей разрядностью:
long a = 4; int b = (int) a;
Потеря данных при преобразовании
При применении явных преобразований мы можем столкнуться с потерей данных. Например, в следующем коде у нас не возникнет никаких проблем:
int a = 5; byte b = (byte) a; System.out.println(b); // 5
Число 5 вполне укладывается в диапазон значений типа byte, поэтому после преобразования переменная b будет равна 5. Но что будет в следующем случае:
int a = 258; byte b = (byte) a; System.out.println(b); // 2
Результатом будет число 2. В данном случае число 258 вне диапазона для типа byte (от -128 до 127), поэтому произойдет усечение значения. Почему результатом будет именно число 2?
Число a, которое равно 258, в двоичном системе будет равно 00000000 00000000 00000001 00000010 . Значения типа byte занимают в памяти только 8 бит. Поэтому двоичное представление числа int усекается до 8 правых разрядов, то есть 00000010 , что в десятичной системе дает число 2.
Усечение рациональных чисел до целых
При преобразовании значений с плавающей точкой к целочисленным значениям, происходит усечение дробной части:
double a = 56.9898; int b = (int)a;
Здесь значение числа b будет равно 56, несмотря на то, что число 57 было бы ближе к 56.9898. Чтобы избежать подобных казусов, надо применять функцию округления, которая есть в математической библиотеке Java:
double a = 56.9898; int b = (int)Math.round(a);
Преобразования при операциях
Нередки ситуации, когда приходится применять различные операции, например, сложение и произведение, над значениями разных типов. Здесь также действуют некоторые правила:
- если один из операндов операции относится к типу double , то и второй операнд преобразуется к типу double
- если предыдущее условие не соблюдено, а один из операндов операции относится к типу float , то и второй операнд преобразуется к типу float
- если предыдущие условия не соблюдены, один из операндов операции относится к типу long , то и второй операнд преобразуется к типу long
- иначе все операнды операции преобразуются к типу int
int a = 3; double b = 4.6; double c = a+b;
Так как в операции участвует значение типа double, то и другое значение приводится к типу double и сумма двух значений a+b будет представлять тип double.
byte a = 3; short b = 4; byte c = (byte)(a+b);
Две переменных типа byte и short (не double, float или long), поэтому при сложении они преобразуются к типу int , и их сумма a+b представляет значение типа int. Поэтому если затем мы присваиваем эту сумму переменной типа byte, то нам опять надо сделать преобразование типов к byte.
Если в операциях участвуют данные типа char, то они преобразуются в int:
int d = 'a' + 5; System.out.println(d); // 102
Явные и неявные приведения типов в Java
В Java существует много типов данных. В большинстве случаев при кодировании необходимо изменить тип данных, чтобы понять обработку переменной, и это называется приведением типа.
Что такое приведение типов в Java?
Приведение типов в Java – это присвоение значения одного примитивного типа данных другому. Вы должны знать о совместимости этого типа данных. Если они совместимы, тогда Java выполнит преобразование, известное как автоматическое преобразование типов, а если нет, то их необходимо явно преобразовать.
В Java есть два типа приведения:
- Расширение приведения (автоматически) – преобразование меньшего типа данных в больший размер типа. byte -> short -> char -> int -> long -> float -> double
- Сужение приведения (вручную) – преобразование данных большего размера в тип меньшего размера. double -> float -> long -> int -> char -> short -> byte
Неявное
Тип приведения, когда два типа данных автоматически конвертируются. Также известно как неявное преобразование. Происходит, когда два типа данных совместимы, а также когда мы присваиваем значение меньшего типа данных большему типу данных.
Например, числовые типы данных совместимы друг с другом, но автоматическое преобразование из числового типа в тип char или boolean не поддерживается.
Кроме того, char и boolean не совместимы друг с другом. Теперь давайте напишем логику для неявного приведения типов, чтобы понять, как это работает.
public class Conversion < public static void main(String[] args) < int i = 200; //automatic type conversion long l = i; //automatic type conversion float f = l; System.out.println("Int value "+i); System.out.println("Long value "+l); System.out.println("Float value "+f); >>
Int value 200 Long value 200 Float value 200.0
Явное
В этом случае, если вы хотите присвоить значение большего типа данных меньшему типу данных, вы можете выполнить явное приведение или сужение типов. Это полезно для несовместимых типов данных, где автоматическое преобразование невозможно.
Давайте разберемся с этим на примере.
//Java program to illustrate explicit type conversion public class Narrowing < public static void main(String[] args) < double d = 200.06; //explicit type casting long l = (long)d; //explicit type casting int i = (int)l; System.out.println("Double Data type value "+d); //fractional part lost System.out.println("Long Data type value "+l); //fractional part lost System.out.println("Int Data type value "+i); >>
Double Data type value 200.06 Long Data type value 200 Int Data type value 200
Явное приведение в выражениях
Когда вы вычисляете выражения, выходные данные автоматически обновляются до большего типа данных операнда. Но если вы сохраните этот результат в каком-либо меньшем типе данных, он генерирует ошибку времени компиляции, из-за которой нам нужно набрать приведение вывода.
//Java program to illustrate type casting int to byte public class ExplicitTest < public static void main(String args[]) < byte b = 70; //type casting int to byte b = (byte)(b * 2); System.out.println(b); >>
Примечание: в случае одиночных операндов результат конвертируется в int, а затем соответственно преобразуется в тип.