Код
Java
#База знаний

Типы данных в Java: какие бывают, чем различаются и что такое ссылки и примитивы

Рассказываем, как джависту не запутаться во всех этих byte, short, boolean, char и String.

Основа любого языка программирования — данные и опе­рации с ними. Java не исключение. Это строго типизированный язык, поэтому типы данных значат в нём очень многое.

Java следит за тем, чтобы все переменные, выражения и значения соответствовали своему типу данных. Поэтому операции, которые допустимы для одного типа, нельзя провести с другим — тип переменной определяет, какие операции с ней можно выполнить.

Когда код на Java компилируется, машина проверяет соответствие типов операндов во всех методах, конструкторах и других операторах. Если в программе есть хотя бы одна недопустимая операция, компилятор не превратит её в байт-код. Поэтому контроль типов данных помогает уменьшить количество ошибок при написании программы.

В этой статье мы рассмотрим:

  • какие типы данных есть в Java;
  • чем различаются примитивные и ссылочные переменные;
  • какие у переменных бывают значения по умолчанию;
  • как упаковка и распаковка помогают превратить примитивные переменные в объект.

Какие типы данных есть в Java

В Java типы данных делят на две большие группы: примитивные и ссылочные. В состав примитивных типов (или просто примитивов) входят четыре подвида и восемь типов данных:

1) целые числа (byte, short, int, long);

2) числа с плавающей точкой (float, double);

3) логический (boolean);

4) символьный (char).

Ссылочные типы данных ещё называют ссылками. К ним относятся все классы, интерфейсы, массивы, а также тип данных String.

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

Примитивные переменныеСсылочные переменные
Хранят значениеХранят адрес объекта в памяти, на который ссылаются (отсюда и название).

Используются для доступа к объектам (его нельзя получить, если на объект нет ссылки)
Создаются присваиванием значенияСоздаются через конструкторы классов (присваивание только создаёт вторую ссылку на существующий объект)
Имеют строго заданный диапазон допустимых значенийПо умолчанию их значение — null
В аргументы методов попадают копии значения переменной (это передача по значению)В методы передаётся значение ссылки — операция выполняется над оригинальным объектом, на который ссылается переменная
Могут использоваться для ссылки на любой объект объявленного или совместимого типа

Вот пример использования примитивных и ссылочных типов данных:

int a = 5;
int b = a; //мы создали две переменных и два различных значения, которые содержат число 5

Cat barsik = new Cat(); //мы создали объект Cat, и переменной barsik присвоена ссылка на этот объект
Cat murka = barsik;     //создали две ссылки на один и тот же объект Cat

Значения переменных по умолчанию

Как мы уже отмечали, в зависимости от типа данных у каждой переменной есть значение по умолчанию. Оно присваивается при её создании.

В этом примере значения по умолчанию получат все переменные:

private int a; 
private double b;
private String text;

А в этом примере значения получают только переменные класса: когда мы создадим класс Cat, по умолчанию weight будет равен 0.0.

public class Cat {
   private double weight;

   public void setWeight(double weight) {
       this.weight = weight;
   }

   public double getWeight() {
       return weight;
   }
}

Но локальные переменные нужно инициировать сразу при создании. Если написать просто int sum; , компилятор выдаст ошибку: java: variable a might not have been initialized.

private static int sum(int[] prices) {
   int sum = 0;
   for (int price : prices) {
       sum += price;
   }
   return sum;
}

У примитивов есть строгие рамки допустимых значений по умолчанию и диапазоны значений — для удобства мы собрали их в таблицу.

Диапазоны значений примитивов в языке Java. Изображение: Skillbox Media

Как используют целочисленные переменные

Целочисленные типы данных различаются только диапазонами значений. Их основная задача — хранить информацию для вычислений.

Тип byte. Эти переменные используют, чтобы работать с потоком данных, который получили из файла или по сети.

//объявляем переменные с типом данных byte
byte a;
byte b;
byte c;

//инициализируем переменные значениями
a = 0;
b = 0;
c = -129; //это пример ошибки во вводе данных. Такое значение не входит в диапазон значений byte

//выводим данные из переменных
System.out.println(a);
System.out.println(b); 

Тип short. По сравнению с byte у него увеличенный, но всё же ограниченный диапазон значений. Применяют short редко — например, когда нужно экономить память.

//инициализируем переменную типа short
short  primerShort = 255; 

Тип int. В языке Java int — самый популярный тип целочисленных данных. При вычислениях в виртуальной машине остальные целочисленные типы (byte, short) занимают столько же памяти, сколько int.

//инициализируем переменную типа int
int closeToMiddle = 0;

Множество классов в Java обладают значениями типа int — например, длина массива внутри класса String выражается целочисленным значением int:

String[] strings = new String[50];
int arrayLength = strings.length;

Если переменная хранит количество элементов в коллекциях List, Set и Map, она тоже относится к типу int:

List<String> strings = new ArrayList<>();
int listSize = strings.size();

Тип возвращаемого значения подсказывает, сколько элементов можно хранить в списке или множестве. Максимум для int — 2 147 483 647.

Тип long применяют, когда нужно работать с большими целочисленными значениями.

//инициализируем переменные типа long
long c = -9223372036854; //при компиляции появится ошибка
long right = -9223372036854L;

По умолчанию компилятор воспринимает целое число как int, а 9 223 372 036 854 намного больше его максимального значения, поэтому в коде программы нужно явно указать тип long.

Зачем нужны числа с плавающей точкой

Тип данных double используют для работы с десятичными числами.

//инициализируем переменную типа double
double sample = 59.36;

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

Когда переменной присваивают тип float, язык Java воспринимает её как тип данных double. Чтобы этого не происходило, нужно добавлять в конце переменной символ f или F.

Даже если у переменных float и double будут одинаковые значения, язык Java обработает их по-разному, поэтому они будут занимать разный объём памяти.

//инициализация переменных типа float
float f = 1.457;  //Java воспримет эту переменную как тип данных double и выдаст ошибку компиляции

float floatNumber = 27.5f; //правильный способ

float otherFloat = (float) 78.64; //другой вариант инициализации переменной типа float во избежание путаницы с double 

Не стоит использовать float, когда в вычислениях нужна точность больше пяти знаков после запятой. Oracle пишет об этом в статье «Primitive Data Types».

Логический и символьный типы данных

Чтобы работать с логическими значениями, используют тип данных boolean — это его единственное применение. У такой переменной может быть только два значения: false (ложь) и true (истина).

//инициализируем переменные типа boolean
boolean isWorking = true;
boolean isAlive = false;

В Java boolean — отдельная переменная. Это не аналог 1 или 0, как, например, в JavaScript и PHP.

Тип данных char используют, чтобы хранить в переменных любые 16-разрядные символы Unicode. Но их нужно записывать строго в одинарные кавычки ' ', и только по одному.

Не стоит путать символьные и строковые переменные — 'ж' не равно "ж", потому что в двойных кавычках хранится тип данных String. А это уже не примитив.

Пример кода

//инициализируем переменные типа char
char symbol1 = 1078; //по индексу символа в таблице UTF-8
char symbol2 = 'ж'; //по значению символа
char symbol3 = '\u0436'; //через шестнадцатеричную форму Unicode (это всё ещё «ж»)

//вызываем вывод информации
System.out.println("symbol1 contains " + symbol1);
System.out.println("symbol2 contains " + symbol2);
System.out.println("symbol3 contains " + symbol3);

//во всех случаях будет выдан один и тот же символ — «ж»

Вывод в консоли

symbol1 contains ж
symbol2 contains ж
symbol3 contains ж 

Значения по умолчанию для ссылочных типов данных

В плане дефолтных значений ссылочные переменные проще примитивов. По умолчанию их значение — null: это означает отсутствие ссылки или то, что ссылка ни на что не указывает.

Но если вызвать метод объекта от переменной со значением null, это приведёт к ошибке NullPointerException:

Cat barsik = null;
barsik.meow(); //здесь появится ошибка NullPointerException, потому что у переменной barsik значение null

В ссылочные типы данных входит и String — это класс из стандартной библиотеки Java. Он не относится к примитивам, но его повсеместно используют, чтобы хранить текстовые данные.

Пример использования String:

//создаём строку двойными кавычками ""
String string = "This day was awesome";

//альтернативный способ создания строки — через конструктор
String string = new String ("This day was awesome");

Строчные переменные можно склеивать оператором +, который используют для конкатенации.

Пример кода

String stringFirst = "New adventures ";
String stringSecond= "are waiting ";
String stringThird = "for you";

String string = stringFirst + stringSecond + stringThird;
System.out.println(string);

Вывод в консоли

New adventures are waiting for you

Boxing и unboxing — как превратить примитив в объект

Иногда с примитивами приходится работать как с объектами — например, передавать им значение по ссылке или создавать список из чисел (а списки работают только с объектами).

Поэтому у каждого примитива есть соответствующий ему ссылочный тип — его называют классом-обёрткой. В таких классах хранятся методы для преобразования типов данных, а также другие константы и методы, которые применяются при работе с примитивами.

Тип данныхКласс-обёртка
byteByte
shortShort
intInteger
longLong
charCharacter
floatFloat
doubleDouble
booleanBoolean

Ссылочные типы данных (обёртки) пишут с прописной буквы, потому что это полноценные классы. А в Java названия всех классов должны начинаться с большой буквы — язык чувствителен к регистру.

Чтобы создать ссылку на примитивный тип данных, нужно использовать соответствующую обёртку:

//пример использования классов-оболочек (упаковка)
long g = 5509768L;
Long boxed;
boxed = new Long(g);       //обычное создание через конструктор
boxed = Long.valueOf (g);  //фабричный метод
boxed = g; 			//автоматическая упаковка. Компилятор заменит её на вызов Long.valueOf(g)

Если использовать valueOf, процесс упаковывания становится проще и быстрее, потому что он проводит кэширование и потребляет меньше памяти, а конструктор всегда создаёт новый объект.

//пример использования классов-оболочек (распаковка)
Long boxed = 200L;
long g;
g = boxed.longValue();         //явная распаковка
g = boxed;                     //автоматическая распаковка

Классы-обёртки полезны, когда нужно одновременно работать и с числами, и с объектами — например, в коллекциях.

В этой статье мы рассмотрели примитивные типы данных (byte, short, int, long, float, double, char и boolean), ссылочные типы данных (String и остальные). Вы узнали, чем они отличаются друг от друга и какие значения принимают по умолчанию.

В следующей статье мы расскажем, что можно делать с этими переменными с помощью арифметических и логических операторов языка Java.

Проверьте свой английский. Бесплатно ➞
Нескучные задания: small talk, поиск выдуманных слов — и не только. Подробный фидбэк от преподавателя + персональный план по повышению уровня.
Пройти тест
Понравилась статья?
Да

Пользуясь нашим сайтом, вы соглашаетесь с тем, что мы используем cookies 🍪

Ссылка скопирована