JavaRush/Java блог/Java Developer/Приведение (преобразование) примитивных типов в Java
Автор
Aditi Nawghare
Инженер-программист в Siemens

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

Статья из группы Java Developer
участников
Привет! Во время прохождения JavaRush ты не раз сталкивался с примитивными типами. Вот краткий список того, что мы о них знаем:
  1. Они не являются объектами и представляют собой значение, хранящееся в памяти
  2. Примитивные типы бывают нескольких видов:
    • Целые числа — byte, short, int, long
    • Числа с плавающей точкой (дробные) — float и double
    • Логический — boolean
    • Символьный (для обозначения букв и цифр) — char
  3. Каждый из них имеет свой диапазон значений:
Примитивный тип Размер в памяти Диапазон значений
byte 8 бит от -128 до 127
short 16 бит до -32768 до 32767
char 16 бит от 0 до 65536
int 32 бита от -2147483648 до 2147483647
long 64 бита от -9223372036854775808 до 9223372036854775807
float 32 бита от (2 в степени -149) до ((2-2 в степени -23)*2 в степени 127)
double 64 бита от (-2 в степени 63) до ((2 в степени 63) - 1)
boolean 8 (при использовании в массивах), 32 (при использовании не в массивах) true или false
Но, помимо значений, типы отличаются еще и размером в памяти. int занимает больше, чем byte. А long — больше, чем short. Объем занимаемой примитивами памяти можно сравнить с матрешками: Расширение и сужение примитивных типов - 2 Внутри матрешки есть свободное место. Чем больше матрешка — тем больше места. Внутрь большой матрешки long мы легко можем положить меньшую по размеру int. Она легко уместится, и ничего делать дополнительно не нужно. В Java при работе с примитивами это называется автоматическим преобразованием. По-другому его называют расширением. Вот простой пример расширения:
public class Main {

   public static void main(String[] args) {

       int bigNumber = 10000000;

       byte littleNumber = 16;

       bigNumber = littleNumber;
       System.out.println(bigNumber);
   }
}
Здесь мы присваиваем значение byte в переменную int. Присваивание прошло успешно и безо всяких проблем: значение, хранящееся в byte, занимает меньший объем в памяти, чем “влезает” в int. “Маленькая матрешка” (значение byte) легко влезает в “большую матрешку” (переменную int). Другое дело, когда ты пытаешься сделать наоборот — положить значение большого размера в переменную, которая на такие размеры не рассчитана. С настоящими матрешками такой номер в принципе не пройдет, а в Java — пройдет, но с нюансами. Давай попробуем положить значение int в переменную short:
public static void main(String[] args) {

   int bigNumber = 10000000;

   short littleNumber = 1000;

   littleNumber = bigNumber;//ошибка!
   System.out.println(bigNumber);
}
Ошибка! Компилятор понимает, что ты пытаешься сделать что-то нестандартное, и засунуть большую матрешку (int) внутрь маленькой (short). Ошибка компиляции в данном случае — предупреждение от компилятора: “Эй, ты точно уверен, что хочешь это сделать?” Если ты уверен, говоришь об этом компилятору: “Все ок, я знаю, что делаю!” Этот процесс называется явным преобразованием типов, или сужением. Чтобы сделать сужение, тебе необходимо явно указать тип, к которому ты хочешь привести свое значение. Иными словами, ответить компилятору на его вопрос: “Ну и в какую из этих маленьких матрешек ты хочешь засунуть эту большую матрешку?” В нашем случае это будет выглядеть так:
public static void main(String[] args) {

   int bigNumber = 10000000;

   short littleNumber = 1000;

   littleNumber = (short) bigNumber;
   System.out.println(littleNumber);
}
Мы явно указали, что хотим уместить значение int в переменную short и берем ответственность на себя. Компилятор, видя явное указание на более узкий тип, проводит преобразование. Каков же будет результат? Вывод в консоль: -27008 Немного неожиданно. Почему именно такой? На самом деле все просто. У нас было изначальное значение — 10000000 Оно хранилось в переменной int, которая занимала 32 бита, и в двоичной форме оно выглядело так: Расширение и сужение примитивных типов - 3 Мы записываем это значение в переменную short, но она может хранить только 16 бит! Соответственно, только первые 16 бит нашего числа и будут туда перемещены, остальные — отбросятся. В итоге в переменную short попадет значение Расширение и сужение примитивных типов - 4, которое в десятичной форме как раз равно -27008 Именно поэтому компилятор “просил подтверждения” в форме явного приведения к конкретному типу. Во-первых, оно показывает, что ты берешь ответственность за результат на себя, а во-вторых, указывает компилятору сколько места выделить при приведении типов. Ведь если бы мы в последнем примере приводили int к типу byte, а не к short, в нашем распоряжении было бы только 8 бит, а не 16, и результат был бы уже другим. Для дробных типов (float и double) сужение происходит по-своему. Если попытаться привести такое число к целочисленному типу, у него будет отброшена дробная часть.
public static void main(String[] args) {

   double d = 2.7;

   long x = (int) d;
   System.out.println(x);
}
Вывод в консоль: 2

Тип данных char

Ты уже знаешь, что тип char используется для отображения отдельных символов.
public static void main(String[] args) {

   char c = '!';
   char z = 'z';
   char i = '8';

}
Но у него есть ряд особенностей, которые важно понимать. Давай еще раз посмотрим в таблицу с диапазонами значений:
Примитивный тип Размер в памяти Диапазон значений
byte 8 бит от -128 до 127
short 16 бит от -32768 до 32767
char 16 бит от 0 до 65536
int 32 бита от -2147483648 до 2147483647
long 64 бита от -9223372036854775808 до 9223372036854775807
float 32 бита от (2 в степени -149) до ((2-2 в степени -23)*2 в степени 127)
double 64 бита от (-2 в степени 63) до ((2 в степени 63)-1)
boolean 8 (при использовании в массивах), 32 (при использовании не в массивах) true или false
Для типа char указан числовой диапазон — от 0 до 65536. Но что это значит? Ведь char — это не только цифры, но и буквы, знаки препинания… Дело в том, что значения char хранятся в Java в формате Юникода. Мы уже сталкивались с Юникодом в одной из прошлых лекций. Ты, наверное, помнишь, что Unicode — это стандарт кодирования символов, включающий в себя знаки почти всех письменных языков мира. Иными словами, это список специальных кодов, в котором найдется код почти для любого символа из любого языка. Общая таблица Юникодов очень большая, и, конечно, ее не нужно учить наизусть. Вот, например, ее кусочек: Расширение и сужение примитивных типов - 5 Главное — понимать принцип хранения значений char, и помнить, что зная код конкретного символа всегда можно получить его в программе. Давай попробуем это сделать с каким-нибудь случайным числом:
public static void main(String[] args) {

   int x = 32816;

   char c = (char) x ;
   System.out.println(c);
}
Вывод в консоль: 耰 Именно в таком формате в Java хранятся символы char. Каждому символу соответствует число — числовой код размером 16 бит, или два байта. Юникоду 32816 соответствует иероглиф 耰. Обрати внимание вот на какой момент. В этом примере мы использовали переменную int. Она занимает в памяти 32 бита, в то время как char16. Здесь мы выбрали int, потому что нужное нам число 32816 находится за пределами диапазона short. Хотя размер char, как и short, равен 16 битам, но в диапазоне char нет отрицательных чисел, поэтому “положительный” диапазон char в два раза больше (65536 вместо 32767 у short). Мы можем использовать int, пока наш код укладывается в диапазон до 65536. Но если создать число int >65536, оно будет занимать больше 16 битов. И при сужении типов:
char c = (char) x;
лишние биты будут отброшены, и результат будет весьма неожиданным.

Особенности сложения char и целых чисел

Давай рассмотрим вот такой необычный пример:
public class Main {

   public static void main(String[] args) {

      char c = '1';

      int i = 1;

       System.out.println(i+c);
   }
}
Вывод в консоль: 50 O_О Где логика? 1+1, откуда взялось 50?! Ты уже знаешь, что значения char хранятся в памяти как числа в диапазоне от 0 до 65536, обозначающие Юникод нашего символа. Расширение и сужение примитивных типов - 6 Так вот. Когда мы производим сложение char и какого-то целочисленного типа, char преобразуется к числу, которое соответствует ему в Юникоде. Когда в нашем коде мы складывали 1 и ‘1’ — символ ‘1’ преобразовался к своему коду, который равен 49 (можешь проверить в таблице выше). Поэтому результат и стал равен 50. Давай еще раз возьмем для примера нашего старого друга — , и попробуем сложить его с каким-нибудь числом.
public static void main(String[] args) {

   char c = '耰';
   int x = 200;

   System.out.println(c + x);
}
Вывод в консоль: 33016 Мы уже выяснили, что соответствует коду 32816. А при сложении этого числа и 200 мы получаем как раз наш результат — 33016 :) Механизм работы, как видишь, достаточно простой.
Комментарии (150)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
25 декабря 2022, 21:28
Открою вам страшную тайну... -27008 - это в бинарном представлении 11111111111111111001011010000000 а число 1001011010000000, которое вы в статье отразили картинкой - это честные 38528
Dimir11
Уровень 38
16 апреля 2023, 14:03
-27008 это как раз таки ваше число "1001011010000000", добавьте к нему 0b и парочку старших битов (16 единичек), на выходе получите 0b1111_1111_1111_1111_1001_0110_1000_0000, почему их 16, а не, скажем, 11, предлагаю вам выяснить самостоятельно.
WriturX [Andrij] Backend Developer
27 января 2022, 14:38
Есть кому интересно 耰 -- Мучить, терзать, нервировать. Совпадение? Не думаю...
Dim
Уровень 38
30 апреля 2022, 06:49
Откуда данные? вообще то гугл перевёл как "борона"
zotov_l88
Уровень 1
25 января 2022, 14:36
Можете объяснить, почему лонг в дабл преобразуется автоматически, а наоборот - нет и нужно кастить. Но для обоих типов, при этом, выделяется 64 бита.
Екатерина Backend Developer
6 февраля 2022, 20:10
Потому что дабл - это число с плавающей точкой. Преобразуя его в целое, пусть и такого же размера, происходит отбрасывание части значения после запятой
Artem Sokolov Android Developer в Oracle
6 января 2022, 07:00
Жаль, не рассказали о примерах в жизни где это может понадобиться.
Александр Technical Lead в МДР
31 января 2022, 08:25
Ну к примеру вам надо вывести на экран или в массив весь алфавит. Просто задаете диапазон, с кода первого символа до кода последнего и циклом перебираете.
AmpCult Backend Developer
18 марта 2021, 13:38
Диапазон char должен быть от 0 до 65535, а значит общее количество значений 65536 = 2^16 или 2 байта, но в первой таблице указан диапазон от 0 до 65536. Можно пояснительную бригаду? Буду очень благодарен.
fog
Уровень 18
5 апреля 2021, 14:09
Просто в статье небольшая ошибка... JLS -> 4.2.1. Integral Types and Values
Никита Java Developer в Совкомбанк
10 марта 2021, 12:45
Разве недостаточно выделить под булевые значения 1 бит? Всего же 2 значения - 1 и 0
Александр Евтеев Java Developer в Лига цифровой эконом
17 марта 2021, 16:21
Just me
Уровень 41
23 февраля 2021, 16:22
Помогите понять, какие именно первые 16 бит были взяты из числа типа int при приведении к типу short ?
IwanIV
Уровень 41
25 февраля 2021, 20:59
как я понял, считают справа на лево..
AmpCult Backend Developer
18 марта 2021, 14:17
Слева на право обрезалось 16 ячеек из 32. Это видно на рисунке ниже. Одна ячейка соответствует 1 биту, соответственно было int 32 бита а стало char 16 бит.
Евгений
Уровень 38
1 февраля 2021, 17:08
Во второй лекции 10-го уровня указан совершенно другой диапазон значений и размеров в байтах. Как так вышло?
Flexo Bending Unit #3370318
5 февраля 2021, 07:12
в той лекции размер в байтах, тут в битах. мы сейчас живём в 8-битовой парадигме байта, так что всё сходится диапазон значений там опять же указан в логарифмической форме, а здесь - в степенной. но они эквивалентны.
Евгений
Уровень 38
5 февраля 2021, 08:43
Спасибо тебе, добрый человек
Константин
Уровень 22
15 января 2021, 15:16
видимо должен быть "char"
Pavlo Plynko Java-разработчик в CodeGym Expert
4 марта 2021, 10:35
да нет, int
Alexander G.
Уровень 22
27 июня 2021, 05:14
да нет, char
Pavlo Plynko Java-разработчик в CodeGym Expert
5 июля 2021, 14:26
"Для представления типа char можно использовать и char" ??? 🤣
6 января 2021, 09:29
Очень удивлен, что логической переменной присвоили целых 8 бит или байт, а если не в массиве (не удивлюсь, если массивный тру не будет равен обычному 😁) то целый инт! Хотелось бы понять зачем сделали так, ведь булеан — эта основа всего программирования и делать его больше одного бита лично я не вижу смысл. Тру 1, фальш 0. Откуда 8 и 32?
Илья
Уровень 17
25 марта 2021, 13:39
Потери памяти, но экономия на приведении. К отдельному биту сложно обращаться, а int считается наиболее полноценным целочисленным типом, к нему всё приводится. Вот и получается, что для экономии на скорости потратились на память.