Преобразование и приведение примитивных типов

Author: Tatyana Milkina

В этом уроке мы поговорим с Вами о преобразовании примитивных типов в Java.

Когда будет происходить такое преобразование? Иногда возникают ситуации, когда необходимо переменной одного типа присвоить значение переменной другого типа. Например:

int i = 11;
byte b = 22;
i = b;

Конечно же в этом случае произойдёт преобразование типа - из типа byte число преобразуется в тип int. Либо обратный вариант - из типа int число преобразуется в тип byte:

int i = 11;
byte b = 22;
b = (byte)i;

Давайте посмотрим, что будет происходить в обоих из этих вариантов.

В первом случае мы преобразуем из типа byte в тип int. То есть из маленького типа в большой тип. Это будет безопасное преобразование, которое произойдёт без проблем. В двоичном виде это будет выглядеть таким образом:

Преобразование из типа byte в тип int

Значение типа byte сверху - это 8 бит. Значение типа int снизу - это будет 32 бита. Значение в типе int просто перезаписывается и в старших битах недостающие значения просто заменяться ноликами. Такой тип преобразования называется автоматическим преобразованием.

Но что будет во втором варианте, когда мы делаем обратное преобразование из типа int в тип byte?

int i = 11;
byte b = 22;
b = (byte)i;

Здесь мы указываем, что мы явно преобразуем к типу byte. Если этого не сделать, то произойдет ошибка компиляции. Такое преобразование не всегда является безопасным. Если преобразуется какое-то небольшое число, которое входит в диапазон типа int, тогда преобразование произойдёт без проблем. Но если преобразовывать число, которое не входят в диапазон типа byte, допустим число 320, то посмотрите, что здесь произойдёт:

Преобразование из типа int в тип byte

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

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

Итак, в Java существует два типа преобразований - автоматическое преобразование (неявное) и приведение типов (явное преобразование).

Два типа преобразования в Java фото

1. Автоматическое преобразование типов Java

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

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

Для автоматического преобразования типа должно выполняться два условия:

  • оба типа должны быть совместимы
  • длина целевого типа должна быть больше длины исходного типа

В этом случае происходит преобразование с расширением. 

Но что такое совместимые типы? Допустим, все числовые типы совместимы между собой. Но значение типа boolean и числовой тип, конечно же, несовместимы между собой. Также какое-то строковое число "Hello" мы не можем привести к числовому типу.

Следующая схема показывает расширяющее преобразование в Java:

Схема совместимых преобразований для примитивных типов в 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. Иногда этот вид преобразования называется сужающим преобразованием, поскольку значение явно сужается, чтобы уместиться в целевом типе данных.

Чтобы выполнить преобразование двух несовместимых типов данных, нужно воспользоваться приведением типов. Приведение - это всего лишь явное преобразование типов. Общая форма приведения типов имеет следующий вид:

(целевой_тип) значение

Где параметр целевой_тип обозначает тип, в который нужно преобразовать указанное значение.

Такое приведение еще называется явным, потому что здесь явно указывается, что мы приводим к типу 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

При приведении более емкого значения с плавающей точкой в целое число происходит усечение и отбрасывание старших битов:

double d = 389889877779.89;
short s = (short) d; //Результат будет -1

3. Автоматическое продвижение типов в выражениях

Помимо операций присваивания, определенное преобразование типов может выполняться и в выражениях. Допустим нам нужно сложить два числа - один типа int и второе значение типа byte. Вопрос -  какое значение у нас будет иметь сумма?

В языке Java действуют следующие правила:

  1. Если один операнд имеет тип double, другой тоже преобразуется к типу double.
  2. Иначе, если один операнд имеет тип float, другой тоже преобразуется к типу float.
  3. Иначе, если один операнд имеет тип long, другой тоже преобразуется к типу long.
  4. Иначе оба операнда преобразуются к типу int.
  5. В выражениях совмещенного присваивания (+=,-=, *=, /=) нет необходимости делать приведение. 

 Приведем пример:

При умножении переменной 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);
    }
}

 

Презентацию с видео можно скачать на Patreon.

Курс 'Java для начинающих' на Udemy Курс 'Java для начинающих' на Udemy
Читайте также:
Комментарии
BilaChayka
Jul 1, 2021
Подскажите, почему если приводить int = 323 в byte получается 67?
sysadmin
Jul 4, 2021
Тип byte содержит в себе один байт, а тип int - 4. Поэтому при таком преобразовании старшие биты просто отбрасываются.