Преобразование и приведение примитивных типов
В этом уроке мы поговорим с вами о преобразовании примитивных типов в Java.
1. Что такое преобразование типов
Когда будет происходить такое преобразование? Иногда возникают ситуации, когда необходимо переменной одного типа присвоить значение переменной другого типа. Например:
int i = 11;
byte b = 22;
i = b; Конечно же в этом случае произойдёт преобразование типа - из типа byte число преобразуется в тип int. Либо обратный вариант - из типа int число преобразуется в тип byte:
int i = 11;
byte b = 22;
b = (byte)i; Давайте посмотрим, что будет происходить в обоих из этих вариантов.
В первом случае мы преобразуем из типа byte в тип int. То есть из маленького типа в большой тип. Это будет безопасное преобразование, которое произойдёт без проблем. В двоичном виде это будет выглядеть таким образом:

Значение типа byte сверху - это 8 бит. Значение типа int снизу - это будет 32 бита. Значение в типе int просто перезаписывается и в старших битах недостающие значения просто заменяться ноликами. Такой тип преобразования называется автоматическим преобразованием.
Но что будет во втором варианте, когда мы делаем обратное преобразование из типа int в тип byte?
int i = 11;
byte b = 22;
b = (byte)i; Здесь мы указываем, что мы явно преобразуем к типу byte. Если этого не сделать, то произойдет ошибка компиляции. Такое преобразование не всегда является безопасным. Если преобразуется какое-то небольшое число, которое входит в диапазон типа int, тогда преобразование произойдёт без проблем. Но если преобразовывать число, которое не входят в диапазон типа byte, допустим число 320, то посмотрите, что здесь произойдёт:

Те значения, которые не поместятся в byte, просто отбросятся и в результате получится какое-то другое число. Здесь даже не произойдёт округление. В результате приведения числа 320 в byte получится число 64.
Такое преобразование называется приведением и такое преобразование не является безопасным. Именно поэтому в скобочках мы указываем, что мы приводим к типу byte. Таким образом мы говорим компилятору, что да мы понимаем, что здесь возможна потеря данных.
Итак, в Java существует два типа преобразований - автоматическое преобразование (неявное) и приведение типов (явное преобразование).

2. Автоматическое преобразование типов Java
Рассмотрим сначала автоматическое преобразование. Если оба типа совместимы, их преобразование будет выполнено в Java автоматически. Например, значение типа byte всегда можно присвоить переменной типа int, как это показано в предыдущем примере.
Такое преобразование является безопасным и ещё оно называется неявное преобразование. Неявное, потому что мы явно не указываем, что необходимо преобразовать число. Ещё автоматическое преобразование называется расширяющим преобразованием, потому что мы расширяем наше значение из маленького к большому.
Для автоматического преобразования типа должно выполняться два условия:
- оба типа должны быть совместимы
- длина целевого типа должна быть больше длины исходного типа
В этом случае происходит преобразование с расширением.
Но что такое совместимые типы? Допустим, все числовые типы совместимы между собой. Но значение типа boolean и числовой тип, конечно же, несовместимы между собой. Также какое-то строковое число "Hello" мы не можем привести к числовому типу.
Следующая схема показывает расширяющее преобразование в 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 бит.
3. Приведение типов Java
Несмотря на все удобство автоматического преобразования типов, оно не в состоянии удовлетворить все насущные потребности. Например, что делать, если значение типа int нужно присвоить переменной типа byte? Это преобразование не будет выполняться автоматически, поскольку длина типа byte меньше, чем у типа int. Иногда этот вид преобразования называется сужающим преобразованием, поскольку значение явно сужается, чтобы уместиться в целевом типе данных.
Чтобы выполнить преобразование двух несовместимых типов данных, нужно воспользоваться приведением типов. Приведение - это всего лишь явное преобразование типов. Общая форма приведения типов имеет следующий вид:
(целевой_тип) значение Где параметр целевой_тип обозначает тип, в который нужно преобразовать указанное значение.
Такое приведение еще называется явным, потому что здесь явно указывается, что мы приводим к типу byte, либо к какому-то другому типу. И оно является небезопасным.
Например, в следующем фрагменте кода тип 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 При приведении значения с плавающей точкой к более узкому целому типу преобразование происходит в два шага. Сначала дробная часть отбрасывается, а число приводится к int; если оно не помещается в диапазон int, результат ограничивается значением Integer.MAX_VALUE или Integer.MIN_VALUE (это называется насыщением). Затем это значение int приводится к целевому типу отбрасыванием старших битов:
double d = 389889877779.89;
short s = (short) d; //Результат будет -1 Здесь число 389889877779.89 слишком велико для int, поэтому сначала оно превращается в Integer.MAX_VALUE (2147483647), а затем при сужении до short остаются только младшие 16 бит, что даёт −1.
4. Автоматическое продвижение типов в выражениях
Помимо операций присваивания, определенное преобразование типов может выполняться и в выражениях. Допустим нам нужно сложить два числа - один типа int и второе значение типа byte. Вопрос - какое значение у нас будет иметь сумма?
В языке 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.
public class IntegerExample1 {
public static void main(String[] args) {
byte b1 = 50, b2 = -99;
short k = b1 + b2; //ошибка компиляции
System.out.println("k=" + k);
}
} Следующий пример аналогичен предыдущему, но используется операция совмещенного присваивания, в которой приведение происходит автоматически:
public class IntegerExample2 {
public static void main(String[] args) {
byte b1 = 50, b2 = -99;
b1 += b2;
System.out.println("b1=" + b1);
}
} Часто задаваемые вопросы
Чем расширяющее преобразование отличается от сужающего?
Расширяющее (автоматическое, неявное) преобразование идёт от меньшего типа к большему — например, из byte в int. Оно безопасно и выполняется компилятором само, без потери данных. Сужающее (явное) преобразование идёт от большего типа к меньшему — например, из int в byte. Оно небезопасно: данные могут потеряться, поэтому его нужно указывать явно через приведение в скобках — (byte).
Почему при сложении двух byte результат имеет тип int?
В арифметических выражениях Java автоматически продвигает операнды к типу int, если они меньше int (то есть byte или short). Поэтому byte + byte даёт int, а не byte. Из-за этого код вроде byte b = b1 + b2; не скомпилируется — результат типа int нельзя присвоить в byte без явного приведения. При этом составные операторы (+=, *=) выполняют приведение автоматически, поэтому b1 += b2; работает.
Зачем при приведении типов указывать тип в скобках?
Скобки с типом — например, (byte) — это явное указание компилятору, что вы сознательно выполняете небезопасное сужающее преобразование и понимаете, что возможна потеря данных. Без этих скобок сужающее преобразование вызовет ошибку компиляции. Таким образом язык защищает вас от случайной потери данных: расширение происходит само, а сужение нужно подтвердить вручную.
Почему byte не преобразуется в char автоматически, хотя char шире?
Хотя char занимает 16 бит, а byte — всего 8, автоматического преобразования нет, потому что byte и short — знаковые типы, а char — беззнаковый. Компилятору нужно явное указание, как обрабатывать знаковый бит при таком преобразовании, поэтому требуется явное приведение. По той же причине нет автоматического преобразования числовых типов в char или boolean.
Презентацию с видео можно скачать на Patreon.
Видео объяснение
Предпочитаете видеоформат? Посмотрите этот урок с примерами и объяснениями.
Комментарии