СоХабр закрыт.

С 13.05.2019 изменения постов больше не отслеживаются, и новые посты не сохраняются.

| сохранено

H Примитивные типы в Java в черновиках Tutorial

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



Примитивные типы


Примитивные типы немного нарушают объектную ориентированность языка Java, так так представляют одиночные (простые) значения. Эта особенность объясняется желанием обеспечить максимальную эффективность. Создавать объект простой переменной с помощью new недостаточно эффективно, так как new перемещает объект в кучу. Вместо этого создается «автоматическая» переменная, которая не является ссылкой на объект. Переменная хранит единственное значение и располагается в стеке. Стек — это область хранения данных, расположена в RAM. Процессор имеет прямой доступ до этой области через указатель на стек, поэтому стек — очень быстрый и эффективный способ хранения данных. По скорости стек уступает только регистрам (логично, так как регистры расположены внутри процессора).
Все размеры примитивных типов строго фиксированы и не зависят от машинной архитектуры. Это одна с причин улучшенной переносимости Java-программ.
В Java определено восемь примитивных типов, которые можно разбить на четыре группы:
Целые числа Числа с плавающей точкой Символы Логичные значения
byte, short, int, long float, double char boolean

Целые числа


Для целых чисел определены четыре примитивных типа: byte, short, int, long. Все эти типы представляют целочисленные значения со знаком: положительные или отрицательные. В Java нет положительных целочисленных значений без знака (unsigned). Как было сказано раньше, все размеры примитивных типов фиксированы:
Тип Длина в байтах Длина в битах Диапазон
byte 1 8 [-128, 127] или [-27, 27-1]
short 2 16 [-32768, 32767] или [-215, 215-1]
int 4 32 [-2147483648, 2147483647] или [-231, 231-1]
long 8 64 [-9223372036854775808, 9223372036854775807] или [-263, 263-1]

Наименьшим целочисленным типом является byte. Переменные этого типа очень удобны для работы с потоками ввода-вывода и при манипулировании двоичными данными. Далее идет тип short, который применяется реже всех остальных типов. Наиболее часто употребляемым типом является int. Его постоянно используют в циклах, для индексации массивов. Может показаться, что использование типов byte и short в местах, где не требуется широкий диапазон значений, будет более эффективным чем использование int. Но это не так, потому что при вычислении выражений значения типа byte или short будут преобразованы в int (мы еще вернемся к этому вопросу). Когда длины типа int недостаточно для хранения значения, нужно использовать long. Его диапазон значений достаточно велик, что делает long удобным при работе с большими целыми числами.

Числа с плавающей точкой


Числа с плавающей точкой (или действительные числа) представлены типами float и double. Используются для хранения значений с точностью до определенного знака после десятичной точки.
Тип Длина в байтах Длина в битах Диапазон
float 4 32 [1.4e-45, 3.4028235e38]
double 8 64 [4.9e-324, 1.7976931348623157308]
Тип float определяет числовое значение с плавающей точкой одинарной точности. Этот тип используется, когда нужно числовое значение с дробной частью, но без особой точности. Тип double используется для хранений значений с плавающей точкой двойной точности. Обработка значений двойной точности выполняется быстрее, чем обработка значений одинарной точности. Поэтому большинство математических функций класса java.lang.Math возвращают значения типа double. Эффективнее всего использовать double, когда требуется сохранить точность многократно повторяющихся вычислений или манипулировать большими числами.

Символы


В спецификации примитивный тип char принадлежит к целочисленным типам (или integral types), но поскольку он играет немного другую роль, можно выделить для него собственную категорию. Его роль — представлять символы Unicode. Для хранения символов требуется 16 бит. Странно, ведь для представления символов основных языков (например, английского, французского, испанского) достаточно 8 бит. Но такая цена интернационализации. Unicode использует полный набор международных символов на всех известных языках мира.
Тип Длина в байтах Длина в битах Диапазон
char 2 16 ['\u0000', '\uffff'] или [0, 65535]

Логичные значения


Примитивный тип boolean предназначен для хранения логических значений. Данный тип может принимать одно из двух возможных значений: true (истина) или false (ложь). Значения boolean возвращаются со всех логичных операций (например, операции сравнения). Является обязательным при построении циклов, операторов (например, for, if).

Литералы


Значения примитивных типов данных в большинстве случаев инициализируются с помощью литералов. Рассмотрим их.

Целочисленные литералы


Наиболее часто используемые литералы. Любое целочисленное значение является числовым литералом (например, -10, 10 — десятичные значения). Можно использовать восьмеричные, шестнадцатеричные и двоичные литералы:

// десятичный литерал, числа [0, 9], не начинается с 0
int decimal = 10; // 10
// восьмеричный литерал начинается с 0, далее числа [0, 7]
int octal = 010; // 8
// шестнадцатеричный литерал начинается с 0x или 0Х, далее числа [0, 9] и символы [a-f]
int hexadecimal = 0x10; // 16
// двоичный литерал начинается с Оb или 0B, далее числа [0, 1]
int binary = 0b10; // 2

Все целочисленные литералы представляют значения int. Если значение литерала лежит в диапазоне byte, short или char, то его можно присвоить переменной этого типа без приведения типов. Для создания литерала типа long, необходимо явно указать компилятору, дополнив литерал буквой 'l' или 'L':

byte b1 = 127;
byte b2 = 128; // ошибка

short s1 = -32768;
short s2 = -32769; // ошибка

char c1 = 0;
char c2 = -1; // ошибка

long l1 = 10l;
long l2 = 0x7fffffffffffffffL; // максимальное значение типа long

Литералы с плавающей точкой


Существует две формы записи литеров с плавающей точкой: стандартная и экспоненциальная:

// стандартная форма
double d1 = 0.; // эквивалентно .0 или 0.0;
double d2 = 0.125;
// экспоненциальная форма - используется символ 'e' или 'E'
// после него степень числа 10, на которую следует умножить данное число
double d3 = 125E+10; // если степень положительная, '+' можно упустить
double d4 = 1.25e-10;

Всех литералам с плавающей точкой по-умолчанию присваивается тип double. Поэтому чтобы создать литерал типа float, нужно после литерала указать букву 'f' или 'F'. К литералам также можно добавлять букву 'd' или 'D', сообщая, что это литерал типа double, но зачем?

double d1 = 0.125;
float f2 = 0.125f;

Можно использовать шестнадцатеричные литералы с плавающей точкой, например:

// P - двоичный порядок, что обозначает степень числа 2, на которое следует умножить данное число
double d = 0x10.P10d; // конечно, можно и без 'd' 
float f = 0x20.P10f;

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

// можно делать любые комбинации с использованием любого количества символов '_'
int phone = 111__111__111;
int bin = 0b1000_1000_1000;
double dollars = 23_000.450__500;
// не допускается использовать символ '_' в конце или начале литерала, также не можно разрывать '0x' и '0b'

Символьные литералы


Символьные литералы заключаются в одинарные кавычки. Все отображаемые символы можно задавать таким способом. Если символ нельзя ввести непосредственно, используют управляющее последовательности начинающиеся с символа '\'. Хотя все эти последовательности можно заменить соответствующим Unicode кодом. Также символьный литерал можно создать используя восьмеричную ('\xxx') и шестнадцатеричную форму ('\uxxxx').

char h = 'a'; // стандартная  форма
char a = '\001'; // восьмеричная форма
char c = '\u0001'; // шестнадцатеричная форма 

Существуют также строковые литералы. Информацию о них можно получить тут.

Логические литералы


С логическими операторами все просто. Существует только два логических литерала:

boolean yes = true; // истина
boolean no = false; // ложь

Логические литералы можно присваивать только переменным типа boolean. Также важно понимать, что false не равен 0, а true не равен 1. Преобразовать переменную типа boolean в другие примитивные типы не выйдет.

Операции


Целочисленные операции



Операции над числами с плавающей точкой


Логичные операции


Приведение и преобразование


Классы

–2
1590

комментарии (4)

+5
lany ,   * (был изменён)
По ходу чтения:
Вместо этого создается «автоматическая» переменная, которая не является ссылкой на объект. Переменная хранит единственное значение и располагается в стеке.

Спецификация JVM ничего не говорит о том, где размещается примитивная переменная. Чаще всего для JIT-компилированного кода она размещается именно в регистре. В стек её складывают, если регистров не хватает. Также есть вероятность, что переменная не существует вовсе, потому что оптимизатор так решил. Например:

double a = computeSomething();
int b = (int)a;
double c = b/2.0;
// дальше работаем с "b" и "c", а с "a" не работаем

Вполне вероятно, что computeSomething() вернёт результат в FP-регистр, оттуда его преобразуют в целое, переместят в целочисленный регистр, и регистр, где хранилось a, будет переиспользован для переменной c (или ещё для чего-нибудь), потому что компилятор видит, что a больше не нужна.

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

double lameDivide(double x, double y) {
    Double result = x * y;
    return result;
}

Тут как бы автобоксинг, объект создаётся, всё такое. По факту JIT-компилятор способен заметить, что объект на самом деле не нужен и не создавать его вообще. Это тривиальный пример, на самом деле и более сложные объекты раскладываются на поля, которые раскидываются в регистры вместо того, чтобы писать в кучу.
+2
lany ,  
Обработка значений двойной точности выполняется быстрее, чем обработка значений одинарной точности

Мне самому сейчас некогда профилировать, но могу сослаться на недавний ответ Андрея Паньгина, где он показал, что квадратный корень для float считается быстрее, чем для double.
+2
lany ,  
Все они унаследованы от абстрактного класса Number

И прямо под этим нарисована картинка, где видно, что Boolean и Character не унаследованы.

возможность использования объектов классов-оберток в качестве параметров к методам

Э? А типа примитивы нельзя использовать в качестве параметров к методам?

Ещё операции сравнения и иже с ними — это «логические операции», а не «логичные» :-)
–1
tobilko ,  
спасибо, исправим

примитивы передаются в параметры по значению, объекты позволят передавать по ссылке