Примитивные типы в Java: Не такие уж они и примитивные

Разработку приложений можно рассматривать как работу с некоторыми данными, а точнее — их хранение и обработку. Сегодня хотелось бы затронуть первый ключевой аспект. Как данные хранятся в Java? Тут у нас есть два возможные формата: ссылочный и примитивный тип данных. Давайте поговорим о видах примитивных типов и возможностях работы с ними (как ни крути, это фундамент наших знаний языка программирования). Примитивные типы данных Java — это основа, на которой держится всё. Нет, я нисколько не преувеличиваю. У Oracle примитивам посвящён отдельный Tutorial: Primitive Data TypesНемного истории. Вначале был ноль. Но ноль — это скучно. И тогда появился bit (бит). Почему его так назвали? Назвали его так от сокращения «binary digit» (двоичное число). То есть у него есть только два значения. А так как был ноль, то логично, что теперь стало или 0 или 1. И стало жить веселее. Биты начали собираться в стаи. И эти стаи стали называть byte (байт). В современном мире byte = 2 в третьей степени, т.е. 8. Но, оказывается, так было не всегда. Существует множество догадок, легенд и слухов, откуда пошло название byte. Кто-то считает, что всё дело в кодировках того времени, а кто-то считает, что так было выгоднее считать информацию. Байт — это наименьшая адресуемая часть памяти. Именно байты имеют уникальные адреса в памяти. Есть легенда о том, что ByTe это сокращение от Binary Term — машинное слово. Машинное слово – если говорить просто, это количество данных, которые процессор может обработать за одну операцию. Раньше размер машинного слова совпадал с наименьшей адресуемой памятью. В Java, переменные могут хранить только значение байтов. Как я и говорил выше, в Java существует два вида переменных:
- примитивные типы java, хранят непосредственно значение байтов данных (подробнее типы этих примитивов мы разберем немного ниже);
- ссылочный тип, хранит байты адреса объекта в Heap, то есть через эти переменные мы получаем доступ непосредственно к самому объекту(такой себе пульт от объекта)
Java byte
Итак, история подарила нам байт – минимальный объём памяти, который мы можем использовать. И состоит он из 8 бит. Самый маленький целый тип данных в java – byte. Это знаковый 8-битовый тип. Что это значит? Давайте считать. 2 ^ 8 будет 256. Но что же делать, если мы хотим отрицательное число? И решили разработчики Java, что двоичный код «10000000» будет обозначать -128, то есть старший бит (самый левый бит) будет обозначать, отрицательное ли число. Двоичное «0111 1111» равняется 127. То есть 128 никак не обозначить, т.к. это будет -128. Полный расчёт приведён в этом ответе: Why is the range of bytes -128 to 127 in Java? Чтобы понять как получаются числа, стоит посмотреть на картинку:

Соответственно, чтобы вычислить размер 2^(8-1) = 128. Значит минимальная граница (а она с минусом) будет -128. А максимальная 128 – 1 (вычитаем ноль). То есть максимум будет 127. На самом деле, с типом byte работаем мы не так часто на «высоком уровне». В основном это обработка «сырых» данных. Например, при работе с передачей данных по сети, когда данные это набор 0 и 1, переданных через какой-то канал связи. Или при чтении данных из файлов. Так же могут быть использованы при работе со строкам и кодировками. Пример кода:
public static void main(String []args) < byte value = 2; byte shortByteValue = 0b10; // 2 System.out.println(shortByteValue); // Начиная с JDK7 мы можем разделять литералы подчёркиваниями byte minByteValue = (byte) 0B1000_0000; // -128 byte maxByteValue = (byte) 0b0111_1111; // 127 byte minusByteValue = (byte) 0b1111_1111; // -128 + 127 System.out.println(minusByteValue); System.out.println(minByteValue + " to " + maxByteValue); >
Кстати, не стоит думать, что использование типа byte будет снижать потребление памяти. В основном byte используется для уменьшения расхода памяти при хранении данных в массивах (например, хранение данных, полученных по сети в некотором буфере, который будет реализован в виде массива байт). А вот при операциях над данными использование byte не оправдает ваши ожидания. Связано это с реализацией Java Virtual Machine (JVM). Так как большинство систем 32 или 64 разрядные, то byte и short при вычислениях будут приведены к 32-битному int, о котором мы поговорим дальше. Так проще производить вычисления. Подробнее см. Is addition of byte converts to int because of java language rules or because of jvm?. В ответе даны так же ссылки на JLS (Java Language Specification). Кроме того, использование byte в неправильном месте может привести к неловким моментам:
public static void main(String []args) < for (byte i = 1; i >
Тут будет зацикливание. Потому что значение счётчика дойдёт до максимума (127), произойдёт переполнение и значение станет -128. И мы никогда не выйдем из цикла.
short
Лимит значений из byte довольно мал. Поэтому, для следующего типа данных решили увеличить количество бит вдвое. То есть теперь не 8 бит, а 16. То есть 2 байта. Значения можно посчитать так же. 2^(16-1) = 2 ^ 15 = 32768. Значит, диапазон от -32768 до 32767. Используют его совсем редко для каких-либо специальных случаев. Как говорит нам документация языка Java: «you can use a short to save memory in large arrays».
int
Вот мы и добрались до самого частоиспользуемого типа. Занимает он 32 бита, или 4 байта. В общем, мы продолжаем удваивать. Диапазон значений от -2^31 до 2^31 – 1.
Максимальное значение int
Максимальное значение int 2147483648 – 1, что совсем не мало. Как выше было указано, для оптимизации вычислений, т.к. современным компьютерам с учетом их разрядности удобнее считать, данные могут быть неявно преобразованы к int. Вот простой пример:
byte a = 1; byte b = 2; byte result = a + b;
Такой безобидный код, а мы получим ошибку: «error: incompatible types: possible lossy conversion from int to byte». Придётся исправить на byte result = (byte)(a + b); И ещё один безобидный пример. Что будет если запустим следующий код?
int value = 4; System.out.println(8/value); System.out.println(9/value); System.out.println(10/value); System.out.println(11/value);
А мы получим вывод
2 2 2 2
*звуки паники* Дело обстоит в том, что при работе с int значениями остаток отбрасывается, оставляя только целую часть(в таких случая лучше уж использовать double).
long
Продолжаем удваивать. 32 умножаем на 2 и получаем 64 бита. По традиции, это 4 * 2, то есть 8 байт. Диапазон значений от -2^63 до 2^63 – 1. Более чем достаточно. Данный тип позволяет считать большие-большие числа. Часто используется при работе со временем. Или с большими расстояниями, например. Для обозначения того, что число это long после числа ставят литерал L – Long. Пример:
long longValue = 4; longValue = 1l; // Не ошибка, но плохо читается longValue = 2L; // Идеально
Хочется забежать вперёд. Далее мы будем рассматривать тот факт, что для примитивов есть соответствующие обёртки, которые дают возможность работать с примитивами как с объектами. Но есть интересная особенность. Вот пример: На том же Tutorialspoint online compiler можете проверить такой вот код:
public class HelloWorld < public static void main(String []args) < printLong(4); >public static void printLong(long longValue) < System.out.println(longValue); >>
Данный код работает без ошибок, всё хорошо. Но стоит в методе printLong заменить тип с long на Long (т.е. тип становится не примитивным, а объектным), как становится джаве непонятно, какой параметр мы передаём. Она начинает считать, что передаётся int и будет ошибка. Поэтому, в случае с методом необходимо будет явно указывать 4L. Очень часто long используется как ID при работе с базами данных.
Java float и Java double
Данные типы называются типами с плавающей точкой. То есть это не целочисленные типы. Тип float является 32битным (как int), а double называется типом с двойной точностью, поэтому он 64битный (умножаем на 2, всё как мы любим). Пример:
public static void main(String []args) < // float floatValue = 2.3; lossy conversion from double to float float floatValue = 2.3F; floatValue = 2.3f; double doubleValue = 2.3; System.out.println(floatValue); double cinema = 7D; >
А вот пример разницы значений (из-за точности типов):
public static void main(String []args)
Данные примитивные типы используются в математике, например. Вот доказательство, константа для вычисления числа PI. Ну и вообще можно посмотреть API класса Math. Вот что ещё должно быть важно и интересно: даже в документации сказано: «This data type should never be used for precise values, such as currency. For that, you will need to use the java.math.BigDecimal class instead.Numbers and Strings covers BigDecimal and other useful classes provided by the Java platform.». То есть деньги в float и double не надо вычислять. Пример про точность на примере работы в NASA: Java BigDecimal, Dealing with high precision calculations Ну и чтобы самим прочувствовать:
public static void main(String []args)
Выполните этот пример, а потом добавьте 0 перед цифрами 5 и 4. И вы увидите весь ужас) Есть интересный доклад на русском про float и double в тему: https://youtu.be/1RCn5ruN1fk Примеры работы с BigDecimal можно увидеть здесь: Make cents with BigDecimal Кстати, float и double могут вернуть не только число. Например, пример ниже вернёт Infinity (т.е. бесконечность):
public static void main(String []args) < double positive_infinity = 12.0 / 0; System.out.println(positive_infinity); >
А этот вернёт NAN:
public static void main(String []args) < double positive_infinity = 12.0 / 0; double negative_infinity = -15.0 / 0; System.out.println(positive_infinity + negative_infinity); >
Про бесконечность понятно. А что такое NaN? Это Not a number, то есть результат не может быть высчитан и не является числом. Вот пример: Мы хотим вычислить квадратный корень из -4. Квадратный корень из 4 это 2. То есть 2 надо возвести квадрат и тогда мы получим 4. А что надо возвести в квадрат, чтобы получить -4? Не получится, т.к. если положительное число будет, то оно и останется. А если было отрицательное, то минус на минус даст плюс. То есть это не вычисляемо.
public static void main(String []args) < double sqrt = Math.sqrt(-4); System.out.println(sqrt + 1); if (Double.isNaN(sqrt)) < System.out.println("So sad"); >System.out.println(Double.NaN == sqrt); >
Вот ещё отличный обзор на тему чисел с плавающей точкой: Где ваша точка?
Java boolean
Следующий тип – булевский (логический тип). Он может принимать значения только true или false, которые являются ключевыми словами. Используется в логических операциях, таких как циклы while, и в ветвлении при помощи if, switch. Что тут можно интересного узнать? Ну, например, теоретически, нам достаточно 1 бита информации, 0 или 1, то есть true или false. Но на самом деле Boolean будет занимать больше памяти и это будет зависеть от конкретной реализации JVM. Обычно на это тратится столько же, сколько на int. Как вариант – использовать BitSet. Вот краткое описание из книги «Основы Java»: BitSet
Java char
- Таблица Unicode символов
- Таблица символов ASCII

Пример в студию:
public static void main(String[] args) < char symbol = '\u0066'; // Unicode symbol = 102; // ASCII System.out.println(symbol); >
Кстати, char, являясь по своей сути всё таки числом, поддерживает математические действия, такие как сумма. А иногда это может привести к забавным последствиям:
public class HelloWorld < public static void main(String []args)< String costForPrint = "5$"; System.out.println("Цена только для вас " + + costForPrint.charAt(0) + getCurrencyName(costForPrint.charAt(1))); >public static String getCurrencyName(char symbol) < if (symbol == '$') < return " долларов"; >else < throw new UnsupportedOperationException("Not implemented yet"); >> >
Настоятельно советую проверить в онлайн IDE от tutorialspoint. Когда я увидел этот пазлер на одной из конференций мне это подняло настроение. Надеюсь, Вам пример тоже понравится) UPDATED: Это было на Joker 2017, доклад: «Java Puzzlers NG S03 — Откуда вы все лезете-то?!».
Литералы
- Десятеричная система: 10
- Шестнадцатеричная система: 0x1F4, начинается с 0x
- Восьмеричная система: 010, начинается с нуля.
- Двоичная система (начиная с Java7): 0b101, начинается с 0b
int costInDollars = 08;
Эта строчка кода не скомпилируется:
error: integer number too large: 08
Кажется, что за бред. А теперь вспомним про двоичную и восьмеричную системы. В двоичной системе нет двойки, т.к. есть два значения (начиная с 0). А восьмеричной системе есть 8 значений, начиная с нуля. То есть самого значения 8 нет. Поэтому и ошибка, которая на первый взгляд кажется абсурдной. И чтобы вспомнить вот «вдогонку» правила перевода значений:

Классы-обертки

Примитивы в Java имеют свои классы-обертки, чтобы можно было работать с ними как с объектами. То есть, для каждого примитивного типа существует, соответствующий ему ссылочный тип. Классы-обертки являются immutable (неизменяемыми): это означает, что после создания объекта его состояние — значение поля value — не может быть изменено. Классы-обертки задекларированы как final: объекты, так сказать, read-only. Также хотелось бы упомянуть, что от этих классов невозможно наследоваться. Java автоматически делает преобразования между примитивными типами и их обертками:
Integer x = 9; // autoboxing int n = new Integer(3); // unboxing
Процесс преобразования примитивных типов в ссылочные (int->Integer) называется autoboxing (автоупаковкой), а обратный ему — unboxing (автораспаковкой). Эти классы дают возможность сохранять внутри объекта примитив, а сам объект будет вести себя как Object (ну как любой другой объект). При всём этом мы получаем большое количество разношерстных, полезных статических методов, как например — сравнение чисел, перевод символа в регистр, определение того, является ли символ буквой или числом, поиск минимального числа и т.п. Предоставляемый набор функционала зависит лишь от самой обертки. Пример собственной реализации обёртки для int:
public class CustomerInt < private final int value; public CustomerInt(int value) < this.value = value; >public int getValue() < return value; >>
В основном пакете, java.lang, уже есть реализации классы Boolean, Byte, Short, Character, Integer, Float, Long, Double, и нам не нужно ничего городить своего, а только переиспользовать готовое. К примеру, такие классы дают нам возможность создать, скажем, List , ведь List должен содержать только объекты, чем примитивы не являются. Для преобразования значения примитивного типа есть статические методы valueOf, например, Integer.valueOf(4) вернёт объект типа Integer. Для обратного преобразования есть методы intValue(), longValue() и т. п. Компилятор вставляет вызовы valueOf и *Value самостоятельно, это и есть суть autoboxing и autounboxing. Как выглядит пример автоупаковки и автораспаковки, представленный выше, на самом деле:
Integer x = Integer.valueOf(9); int n = new Integer(3).intValue();
Подробнее про автоупаковку и автораспаковку можно почитать вот в этой статье.
Приведение типов
При работе с примитивами существует такое понятие как приведение типов, одно из не очень приятных свойств C++, тем не менее приведение типов сохранено и в языке Java. Иногда мы сталкиваемся с такими ситуациями, когда нам нужно совершать взаимодействия с данными разных типов. И очень хорошо, что в некоторых ситуациях это возможно. В случае с ссылочными переменными, там свои особенности, связанные с полиморфизмом и наследованием, но сегодня мы рассматриваем простые типы и соответственно приведение простых типов. Существует преобразование с расширением и преобразование сужающее. Всё на самом деле просто. Если тип данных становится больше (допустим, был int, а стал long), то тип становится шире (из 32 бит становится 64). И в этом случае мы не рискуем потерять данные, т.к. если влезло в int, то в long влезет тем более, поэтому данное приведение мы не замечаем, так как оно осуществляется автоматически. А вот в обратную сторону преобразование требует явного указания от нас, данное приведение типа называется — сужение. Так сказать, чтобы мы сами сказали: «Да, я даю себе отчёт в этом. В случае чего — виноват сам».
public static void main(String []args)
Чтобы потом в таком случае не говорили что «Ваша Джава плохая», когда получат внезапно -128 вместо 128 ) Мы ведь помним, что в байте 127 верхнее значение и всё что находилось выше него соответственно можно потерять. Когда мы явно превратили наш int в байт, то произошло переполнение и значение стало -128.
Область видимости

Это то место в коде, где данная переменная будет выполнять свои функции и хранить в себе какое-то значение. Когда же эта область закончится, переменная перестанет существовать и будет стерта из памяти и. как уже можно догадаться, посмотреть или получить ее значение будет невозможно! Так что же это такое — область видимости? Область определяется «блоком» — вообще всякой областью, замкнутой в фигурные скобки, выход за которые сулит удаление данных объявленных в ней. Или как минимум — сокрытие их от других блоков, открытых вне текущего. В Java область видимости определяется двумя основными способами:
- Классом.
- Методом.
Как я и сказал, переменная не видна коду, если она определена за пределами блока, в котором она была инициализирована. Смотрим пример:
int x; x = 6; if (x >= 4) < int y = 3; >x = y;// переменная y здесь не видна!
И как итог мы получим ошибку:
Error:(10, 21) java: cannot find symbol symbol: variable y location: class com.javaRush.test.type.Main
Области видимости могут быть вложенными (если мы объявили переменную в первом, внешнем блоке, то во внутреннем она будет видна).
Заключение
- Целые числа: byte, short, int, long — представляют собой целые числа со знаком.
- Числа с плавающей точкой — эта группа включает себе float и double — типы, которые хранят числа с точностью до определённого знака после запятой.
- Булевы значения — boolean — хранят значения типа «истина/ложь».
- Символы — в эту группу входит типа char.
7. Java – Примитивные и ссылочные типы данных, литералы
Переменные не что иное как зарезервированные места памяти для хранения значений. Это означает, что при создании переменной Вы резервируете некоторое пространство в памяти.
Основываясь на типе данных, который присвоен переменной, операционная система выделяет память и решает, что может быть сохранено в зарезервированную памяти. Поэтому, назначая различные типы данных для переменных, в Java можно хранить целые числа, десятичные дроби или символов в этих переменных.
Существует два типа данных в Java:
- простые или примитивные типы данных;
- ссылочные типы данных (ссылка/объект).
Примитивные типы данных
Существует 8 типов данных, поддерживаемых Java. Основные типы данных предопределены языком и названы по ключевому слову. Теперь давайте посмотрим в деталях эти восемь базовых типов данных существующих в языке программирования Java.
Тип byte
- Тип данных byte является 8-разрядным знаковым целым числом.
- Минимальная величина равна -128 (-2 7 ).
- Максимальное значение равно 127 (включительно) (2 7 -1).
- По умолчанию – 0.
- byte предназначен для экономии места в больших массивах, главным образом вместо целых чисел, поскольку byte в четыре раза меньше, чем int.
- Пример:
byte a = 100; byte b = -50;
Тип short
- Тип данных short является 16-разрядным знаковым целым числом.
- Минимальное значение равно -32768 (-2 15 ).
- Максимальная величина равна 32 767 (включительно) (2 15 -1).
- Тип short в Java может также использоваться для экономии памяти как byte. Размер short в 2 раза меньше, чем int.
- По умолчанию – 0.
- Пример:
short s = 10000; short r = -20000;
Тип int
- В языке Java тип данных int является 32-разрядным знаковым целым числом.
- Минимальный размер int – 2 147 483 648 (-2 31 ).
- Максимальная величина равна 2,147,483,647 (включительно)(2 31 -1).
- Тип int обычно используется для целых значений. Если нет озабоченности по поводу памяти.
- По умолчанию равно 0.
- Пример:
int a = 100000; int b =-200000;
Тип long
- Тип данных long является 64-разрядным знаковым целым числом.
- Минимальное значение равно – 9,223,372,036,854,775,808 (-2 63 ).
- Максимальная величина – 9,223,372,036,854,775,807 (включительно). (2 63 -1).
- В Java Применяется когда требуется более широкий диапазон, чем int.
- По умолчанию – 0L.
- Пример:
long a = 100000L; long b =-200000L;
Тип float
- Тип данных float является c одинарной точностью 32-битный IEEE 754 с плавающей точкой.
- Тип float используется главным образом для сохранения памяти в больших массивах чисел с плавающей точкой.
- По умолчанию – 0.0f.
- Тип float никогда не должен применяется для точного значения, например, валюты.
- Пример:
float f1 = 234.5f;
Тип double
- Тип данных double является c двойной точностью 64-битный IEEE 754 с плавающей точкой.
- Обычно используется для десятичных значений.
- Тип double никогда не должен применяется для точного значения, например, валюты.
- По умолчанию – 0.0d.
- Пример:
double d1 = 123.4;
Тип boolean
- Тип данных boolean представляет собой один бит информации.
- Существует только два возможных значения: true и false.
- Предназначен для простых признаков, которые позволяют отслеживать условия true или false.
- По умолчанию – false.
- Пример:
boolean one = true;
Тип char
- Тип данных char является одним 16-разрядным символом Юникода.
- Минимальная величина равна «\u0000» (или 0).
- Максимальная величина равна «\uffff» (или 65535 включительно).
- В Java char нужен для хранения любого символа.
- Пример:
char letterA ='A';
Ссылочные типы данных
- Ссылочные переменные создаются с использованием определенных конструкторов классов. Они предназначены для доступа к объектам. Эти переменные объявляются с определенным типом, который не может быть изменен. Например, Employee, Puppy и т.д.
- Объекты класса и различные виды переменных массива подпадают под ссылочный тип данных.
- По умолчанию в Java значение любой переменной ссылки — null.
- Ссылочная переменная может применяться для обозначения любого объекта, объявленного или любого совместимого типа.
- Пример:
Animal animal = new Animal("giraffe");
Литералы в Java
Литерал – представление исходного кода как фиксированное значение. Оно представлено непосредственно в коде без каких-либо вычислений.
Литерал в Java можно назначить любой переменной из основного типа. Например:
byte a = 68; char a = 'A';
Byte, int, long, и short может выражаться десятичной (основание 10), шестнадцатеричной (основание 16) или восьмеричной (основание 8) системой исчисления.
При использовании литералов в Java, префикс 0 применяться для указания восьмеричной системы, а префикс 0x указывает на шестнадцатеричную систему. Например:
int decimal = 100; int octal = 0144; int hexa = 0x64;
Строковые литералы в языке Java задаются как и в большинстве других языков, заключив последовательность символов между парой двойных кавычек. Примеры строковых литералов:
"Hello World" "two\nlines" "\"This is in quotes\""
Типы литералов String и char могут содержать любые символы Юникода. Например:
char a = '\u0001'; String a = "\u0001";
Язык Java поддерживает несколько специальных управляющих последовательностей для литералов String и char:
| Обозначение | Представление |
| \n | Новая строка (0x0a) |
| \r | Возврат каретки (0x0d) |
| \f | Прогон страницы (0x0c) |
| \b | Возврат на шаг (0x08) |
| \s | пробел (0x20) |
| \t | Табуляция |
| \» | Двойная кавычка |
| \’ | Апостроф |
| \\ | Обратная косая черта |
| \ddd | Восьмеричный символ (ddd) |
| \uxxxx | Шестнадцатеричный символ UNICODE (xxxx) |
Более подробно управляющие последовательности с примерами рассмотрим в следующих уроках.
Следующий урок объясняет различные типы переменных и их использование. Это даст Вам хорошее представление о том, как они могут использоваться в java-классах, интерфейсах и т.д.
Оглавление
- 1. Java – Самоучитель для начинающих
- 2. Java – Обзор языка
- 3. Java – Установка и настройка
- 4. Java – Синтаксис
- 5. Java – Классы и объекты
- 6. Java – Конструкторы
- 7. Java – Типы данных и литералы
- 8. Java – Типы переменных
- 9. Java – Модификаторы
- 10. Java – Операторы
- 11. Java – Циклы и операторы цикла
- 11.1. Java – Цикл while
- 11.2. Java – Цикл for
- 11.3. Java – Улучшенный цикл for
- 11.4. Java – Цикл do..while
- 11.5. Java – Оператор break
- 11.6. Java – Оператор continue
- 12. Java – Операторы принятия решений
- 12.1. Java – Оператор if
- 12.2. Java – Оператор if..else
- 12.3. Java – Вложенный оператор if
- 12.4. Java – Оператор switch..case
- 12.5. Java – Условный оператор (? 🙂
- 13. Java – Числа
- 13.1. Java – Методы byteValue(), shortValue(), intValue(), longValue(), floatValue(), doubleValue()
- 13.2. Java – Метод compareTo()
- 13.3. Java – Метод equals()
- 13.4. Java – Метод valueOf()
- 13.5. Java – Метод toString()
- 13.6. Java – Метод parseInt()
- 13.7. Java – Метод Math.abs()
- 13.8. Java – Метод Math.ceil()
- 13.9. Java – Метод Math.floor()
- 13.10. Java – Метод Math.rint()
- 13.11. Java – Метод Math.round()
- 13.12. Java – Метод Math.min()
- 13.13. Java – Метод Math.max()
- 13.14. Java – Метод Math.exp()
- 13.15. Java – Метод Math.log()
- 13.16. Java – Метод Math.pow()
- 13.17. Java – Метод Math.sqrt()
- 13.18. Java – Метод Math.sin()
- 13.19. Java – Метод Math.cos()
- 13.20. Java – Метод Math.tan()
- 13.21. Java – Метод Math.asin()
- 13.22. Java – Метод Math.acos()
- 13.23. Java – Метод Math.atan()
- 13.24. Java – Метод Math.atan2()
- 13.25. Java – Метод Math.toDegrees()
- 13.26. Java – Метод Math.toRadians()
- 13.27. Java – Метод Math.random()
- 14. Java – Символы
- 14.1. Java – Метод Character.isLetter()
- 14.2. Java – Метод Character.isDigit()
- 14.3. Java – Метод Character.isWhitespace()
- 14.4. Java – Метод Character.isUpperCase()
- 14.5. Java – Метод Character.isLowerCase()
- 14.6. Java – Метод Character.toUpperCase()
- 14.7. Java – Метод Character.toLowerCase()
- 14.8. Java – Метод Character.toString()
- 15. Java – Строки
- 15.1. Java – Метод charAt()
- 15.2. Java – Метод compareTo()
- 15.3. Java – Метод compareToIgnoreCase()
- 15.4. Java – Метод concat()
- 15.5. Java – Метод contentEquals()
- 15.6. Java – Метод copyValueOf()
- 15.7. Java – Метод endsWith()
- 15.8. Java – Метод equals()
- 15.9. Java – Метод equalsIgnoreCase()
- 15.10. Java – Метод getBytes()
- 15.11. Java – Метод getChars()
- 15.12. Java – Метод hashCode()
- 15.13. Java – Метод indexOf()
- 15.14. Java – Метод intern()
- 15.15. Java – Метод lastIndexOf()
- 15.16. Java – Метод length()
- 15.17. Java – Метод matches()
- 15.18. Java – Метод regionMatches()
- 15.19. Java – Метод replace()
- 15.20. Java – Метод replaceAll()
- 15.21. Java – Метод replaceFirst()
- 15.22. Java – Метод split()
- 15.23. Java – Метод startsWith()
- 15.24. Java – Метод subSequence()
- 15.25. Java – Метод substring()
- 15.26. Java – Метод toCharArray()
- 15.27. Java – Метод toLowerCase()
- 15.28. Java – Метод toString()
- 15.29. Java – Метод toUpperCase()
- 15.30. Java – Метод trim()
- 15.31. Java – Метод valueOf()
- 15.32. Java – Классы StringBuilder и StringBuffer
- 15.32.1. Java – Метод append()
- 15.32.2. Java – Метод reverse()
- 15.32.3. Java – Метод delete()
- 15.32.4. Java – Метод insert()
- 15.32.5. Java – Метод replace()
- 16. Java – Массивы
- 17. Java – Дата и время
- 18. Java – Регулярные выражения
- 19. Java – Методы
- 20. Java – Потоки ввода/вывода, файлы и каталоги
- 20.1. Java – Класс ByteArrayInputStream
- 20.2. Java – Класс DataInputStream
- 20.3. Java – Класс ByteArrayOutputStream
- 20.4. Java – Класс DataOutputStream
- 20.5. Java – Класс File
- 20.6. Java – Класс FileReader
- 20.7. Java – Класс FileWriter
- 21. Java – Исключения
- 21.1. Java – Встроенные исключения
- 22. Java – Вложенные и внутренние классы
- 23. Java – Наследование
- 24. Java – Переопределение
- 25. Java – Полиморфизм
- 26. Java – Абстракция
- 27. Java – Инкапсуляция
- 28. Java – Интерфейсы
- 29. Java – Пакеты
- 30. Java – Структуры данных
- 30.1. Java – Интерфейс Enumeration
- 30.2. Java – Класс BitSet
- 30.3. Java – Класс Vector
- 30.4. Java – Класс Stack
- 30.5. Java – Класс Dictionary
- 30.6. Java – Класс Hashtable
- 30.7. Java – Класс Properties
- 31. Java – Коллекции
- 31.1. Java – Интерфейс Collection
- 31.2. Java – Интерфейс List
- 31.3. Java – Интерфейс Set
- 31.4. Java – Интерфейс SortedSet
- 31.5. Java – Интерфейс Map
- 31.6. Java – Интерфейс Map.Entry
- 31.7. Java – Интерфейс SortedMap
- 31.8. Java – Класс LinkedList
- 31.9. Java – Класс ArrayList
- 31.10. Java – Класс HashSet
- 31.11. Java – Класс LinkedHashSet
- 31.12. Java – Класс TreeSet
- 31.13. Java – Класс HashMap
- 31.14. Java – Класс TreeMap
- 31.15. Java – Класс WeakHashMap
- 31.16. Java – Класс LinkedHashMap
- 31.17. Java – Класс IdentityHashMap
- 31.18. Java – Алгоритмы Collection
- 31.19. Java – Iterator и ListIterator
- 31.20. Java – Comparator
- 32. Java – Дженерики
- 33. Java – Сериализация
- 34. Java – Сеть
- 34.1. Java – Обработка URL
- 35. Java – Отправка Email
- 36. Java – Многопоточность
- 36.1. Java – Синхронизация потоков
- 36.2. Java – Межпоточная связь
- 36.3. Java – Взаимная блокировка потоков
- 36.4. Java – Управление потоками
- 37. Java – Основы работы с апплетами
- 38. Java – Javadoc
Типы данных в Java: какие бывают, чем различаются и что такое ссылки и примитивы
Рассказываем, как джависту не запутаться во всех этих byte, short, boolean, char и String.



Василий Порядин
Программист, преподаватель Skillbox. Пишет про Java.
Основа любого языка программирования — данные и операции с ними. Java не исключение. Это строго типизированный язык, поэтому типы данных значат в нём очень многое.
Java следит за тем, чтобы все переменные, выражения и значения соответствовали своему типу данных. Поэтому операции, которые допустимы для одного типа, нельзя провести с другим — тип переменной определяет, какие операции с ней можно выполнить.
Когда код на Java компилируется, машина проверяет соответствие типов операндов во всех методах, конструкторах и других операторах. Если в программе есть хотя бы одна недопустимая операция, компилятор не превратит её в байт-код. Поэтому контроль типов данных помогает уменьшить количество ошибок при написании программы.
В этой статье мы рассмотрим:
- какие типы данных есть в Java;
- чем различаются примитивные и ссылочные переменные;
- какие у переменных бывают значения по умолчанию;
- как упаковка и распаковка помогают превратить примитивные переменные в объект.
Какие типы данных есть в Java
В Java типы данных делят на две большие группы: примитивные и ссылочные. В состав примитивных типов (или просто примитивов) входят четыре подвида и восемь типов данных:
2) числа с плавающей точкой (float, double);
3) логический (boolean);
4) символьный (char).
Ссылочные типы данных ещё называют ссылками. К ним относятся все классы, интерфейсы, массивы, а также тип данных String.
Хотя у примитивов и ссылок много общего, между ними есть существенные различия. И главное различие — в том, что именно в них хранится.
Вот пример использования примитивных и ссылочных типов данных:

Как используют целочисленные переменные
Целочисленные типы данных различаются только диапазонами значений. Их основная задача — хранить информацию для вычислений.
Тип 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:
ListString> 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 — как превратить примитив в объект
Иногда с примитивами приходится работать как с объектами — например, передавать им значение по ссылке или создавать список из чисел (а списки работают только с объектами).
Поэтому у каждого примитива есть соответствующий ему ссылочный тип — его называют классом-обёрткой. В таких классах хранятся методы для преобразования типов данных, а также другие константы и методы, которые применяются при работе с примитивами.
| Тип данных | Класс-обёртка |
|---|---|
| byte | Byte |
| short | Short |
| int | Integer |
| long | Long |
| char | Character |
| float | Float |
| double | Double |
| boolean | Boolean |
Ссылочные типы данных (обёртки) пишут с прописной буквы, потому что это полноценные классы. А в 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.
Читайте также:
- Ключевое слово var в Java: что, зачем и почему
- 7 этапов взросления программиста: Михаил Флёнов о пути в профессию
- Ползай, как муравей, летай, как пчела: алгоритмы, которые придумала сама природа
Основные отличия примитивных типов от непримитивных в Java
Возьмусь предположить, что вы спрашиваете разницу между примитивными типами и классами обертками.
Примитивные типы
- Выигрыш в производительности.
Оболочки (обертки) типов
- Являются частью объектной иерархии.
- Необходимы в случаях когда класс может работать только с объектами.
- Удобство использования таких полей как MAX_VALUE и.т.д.
P.S.
Прекрасный ответ о причинах введения автоматической автоупаковки/распаковки.
Отслеживать
ответ дан 3 мар 2017 в 20:09
Sanek Zhitnik Sanek Zhitnik
2,212 2 2 золотых знака 17 17 серебряных знаков 28 28 бронзовых знаков
Непримитивные являются классами, потомками Object. Соответственно, передаются по ссылке. К ним применяются все правила работы с классами и объектами. Это часто бывает нужно, если их нужно передать в качестве аргумента методу, принимающему Object. Или указать в качестве параметра типа для Generic-а. Например, List . Но не может быть List .
В свою очередь, примитивные типы передаются по значению. Занимают меньше памяти и лишены при обработке оверхеда объектов. Во многих случаях при присвоении или передаче значения Java конвертирует примитивные типы в их объектные обёртки и обратно автоматически.
EDIT: Важно не путать, что именно мы передаём по значению, а что — по ссылке. Любая переменная ссылочного типа (reference type) хранит в себе ссылку на объект или null, если она не связана с объектом. Так же, как и в случае с переменной примитивного типа, переменная ссылочного типа передаётся по значению. В этом смысле разницы между ними нет.
Однако большая разница возникает, когда думаем о передаче примитива и объекта (или массива).
Передавая примитивное значение, в вызванном методе мы теряем всякую связь с прежней переменной, содержавшей это значение, и никак на неё повлиять не можем.
Однако, передавая ссылку, мы получаем ключ к объекту или массиву, что в общем случае создаёт проблему shared mutable state и может привести к разного рода «сюрпризам», когда объект-владелец переменной внезапно обнаруживает (или даже и не обнаруживает), что значение его атрибута изменилось.
С помощью специальных методик проектирования классов (или специальных библиотек с такими классами) можно добиться того, чтобы однажны созданные экземпляры ссылочных типов было (почти) невозможно изменить. Ключевые слова: immutable type, persistent type, value object. В языках JVM, ориентированных на функциональное программирование, такие типы входят в базовую библиотеку. В самой Java таким образом реализованы, например, обёртки над примитивными типами и String.
