Привет! Во время прохождения JavaRush ты не раз сталкивался с примитивными типами. Расширение и сужение примитивных типов - 1Вот краткий список того, что мы о них знаем:
  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 88 (при использовании в массивах), 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 :) Механизм работы, как видишь, достаточно простой.