Почему Java API использует int
, когда short
или даже byte
будет достаточно?
Пример: DAY_OF_WEEK
поле в классе Calendar
использует int
.
Если разница слишком минимальна, то почему эти типы данных (short
, int
) существуют вообще?
Почему Java API использует int
, когда short
или даже byte
будет достаточно?
Пример: DAY_OF_WEEK
поле в классе Calendar
использует int
.
Если разница слишком минимальна, то почему эти типы данных (short
, int
) существуют вообще?
Некоторые из причин уже указаны. Например, тот факт, что ... (Почти) Все операции над байтом, короткие будут продвигать эти примитивы до int. Тем не менее, следующий следующий вопрос следующий: ПОЧЕМУ эти типы продвигаются до int
?
Итак, чтобы перейти на один уровень глубже: ответ может быть просто связан с набором инструкций виртуальной машины Java. Как показано в таблице в спецификации виртуальной машины Java, интегральные арифметические операции все, такие как добавление, разделение и другие, доступны только для типа int
и типа long
, а не для меньших типов.
(В стороне: меньшие типы (byte
и short
) в основном предназначены только для массивов. Массив, такой как new byte[1000]
, займет 1000 байт, а массив типа new int[1000]
займет 4000 байты)
Теперь, конечно, можно сказать, что "... очевидным следующий вопрос будет: WHY - эти инструкции предлагаются только для int
(и long
)?".
Одна из причин упоминается в вышеупомянутой спецификации JVM:
Если каждая типизированная команда поддерживала все типы данных во время выполнения Java Virtual Machine, было бы больше инструкций, чем было бы представлено в байте
Кроме того, виртуальная машина Java может рассматриваться как абстракция реального процессора. И введение специального Arithmetic Logic Unit для меньших типов не будет стоить усилий: ему понадобятся дополнительные транзисторы, но он все равно может выполнить только одно дополнение за один такт. Доминирующая архитектура, когда JVM была спроектирована, была 32 бита, как раз для 32-битного int
. (Операции с 64-битным значением long
реализованы как особый случай).
(Примечание: последний абзац немного упрощен, учитывая возможную векторность и т.д., но должен дать основную идею без глубокого погружения в темы дизайна процессора)
EDIT: короткое добавление, сосредоточенное на примере из вопроса, но в более общем смысле: можно также спросить, не выгодно ли хранить поля с использованием меньших типов. Например, можно подумать, что память может быть сохранена путем сохранения Calendar.DAY_OF_WEEK
как byte
. Но здесь вступает в действие формат файла Java Class: все Поля в файле класса занимают хотя бы один "слот", который имеет размер одного int
(32 бит). ( "Широкие" поля, double
и long
, занимают два слота). Таким образом, явное объявление поля как short
или byte
не сохранит никакой памяти.
(Почти) Все операции с byte
, short
будут продвигать их до int
, например, вы не можете писать:
short x = 1;
short y = 2;
short z = x + y; //error
Арифметика проще и проще при использовании int
, нет необходимости бросать.
В терминах пространства это делает очень мало различий. byte
и short
будут усложнять ситуацию, я не думаю, что эта микро оптимизация стоит того, поскольку мы говорим о фиксированном количестве переменных.
byte
является актуальным и полезным, когда вы программируете встроенные устройства или работаете с файлами/сетями. Также эти примитивы ограничены, что делать, если расчеты могут превышать их пределы в будущем? Попытайтесь подумать о расширении для класса Calendar
, который может привести к увеличению числа.
Также обратите внимание, что в 64-битных процессорах локали будут сохраняться в реестрах и не будут использовать какие-либо ресурсы, поэтому использование int
, short
и других примитивов не будет иметь никакого значения. Более того, многие реализации Java выстраивают переменные * (и объекты).
*byte
и short
занимают то же пространство, что и int
, если это локальные переменные, переменные класса или даже переменные экземпляра. Зачем? Поскольку в (большинстве) компьютерных системах адреса переменных выравниваются, так, например, если вы используете один байт, вы фактически получите два байта - один для самой переменной и другой для заполнения.
С другой стороны, в массивах byte
принимают 1 байт, short
берут 2 байта и int
берут четыре байта, потому что в массивах только начало и, возможно, конец его должны быть выровнены. Это будет иметь значение, если вы хотите использовать, например, System.arraycopy()
, тогда вы действительно заметите разницу в производительности.
Поскольку арифметические операции проще при использовании целых чисел по сравнению с шортами. Предположим, что константы действительно были смоделированы значениями short
. Тогда вам придется использовать API таким образом:
short month = Calendar.JUNE;
month = month + (short) 1; // is july
Обратите внимание на явное литье. Короткие значения неявно повышаются до значений int
, когда они используются в арифметических операциях. (В стеке операндов шорты даже выражаются как ints.) Это было бы довольно громоздким для использования, поэтому значения int
часто предпочтительны для констант.
По сравнению с этим коэффициент эффективности хранения минимален, поскольку существует фиксированное число таких констант. Мы говорим о 40 константах. Изменение их хранения с int
до short
будет безопасным для вас 40 * 16 bit = 80 byte
. Подробнее см. этот ответ.
Если вы использовали философию, в которой интегральные константы хранятся в наименьшем типе, в котором они вписываются, тогда у Java будет серьезная проблема: всякий раз, когда программисты пишут код с использованием интегральных констант, им приходится уделять пристальное внимание их коду, чтобы проверить, тип констант имеет значение, и если необходимо посмотреть тип документации и/или делать любые преобразования типов.
Итак, теперь, когда мы наметили серьезную проблему, какие выгоды вы могли бы надеяться достичь с этой философией? Я был бы не удивлен, если бы единственный эффект, наблюдаемый во время этого изменения, был бы тем, что вы получите, когда будете смотреть на константу через отражение. (и, конечно, любые ошибки вводятся ленивыми/невольными программистами, не правильно учитывающими типы констант)
Взвешивание плюсов и минусов очень просто: это плохая философия.
Сложность проектирования виртуальной машины - это функция количества операций, которые она может выполнять. Легче иметь четыре реализации инструкции типа "multiply" - по одному для 32-битного целого, 64-разрядного целого, 32-битного с плавающей запятой и 64-битной с плавающей запятой, - чем иметь, кроме того, к вышеизложенным, версии для меньших числовых типов. Более интересным вопросом дизайна является то, что должно быть четыре типа, а не меньше (выполнение всех целочисленных вычислений с 64-битными целыми числами и/или выполнение всех вычислений с плавающей запятой с 64-битными значениями с плавающей запятой). Причина использования 32-разрядных целых чисел заключается в том, что Java, как ожидается, будет работать на многих платформах, где 32-разрядные типы могут работать так же быстро, как 16-битные или 8-битные типы, но операции с 64-битными типами будут заметно помедленнее. Даже на платформах, где с 16-разрядными типами будет работать быстрее, дополнительные затраты на работу с 32-битными величинами будут компенсированы простотой, предоставляемой только 32-разрядными типами.
Что касается выполнения вычислений с плавающей запятой по 32-битным значениям, преимущества немного менее ясны. Существуют некоторые платформы, где вычисления, такие как float a=b+c+d;
, могут выполняться наиболее быстро, преобразовывая все операнды в тип более высокой точности, добавляя их, а затем преобразовывая результат обратно в 32-разрядный номер с плавающей запятой для хранения. Существуют и другие платформы, где было бы более эффективно выполнять все вычисления с использованием 32-битовых значений с плавающей запятой. Создатели Java решили, что все платформы должны делать то же самое, и что они должны одобрять аппаратные платформы, для которых 32-разрядные вычисления с плавающей запятой быстрее, чем более длинные, хотя этот сильно ухудшенный ПК и скорость и точность математики с плавающей запятой на типичном ПК, а также на многих машинах без блоков с плавающей точкой. Обратите внимание, что, в зависимости от значений b, c и d, использование промежуточных вычислений с высокой точностью при вычислении выражений, подобных вышеупомянутому float a=b+c+d;
, иногда приводит к результатам, которые значительно более точны, чем было бы достигнуто для всех промежуточных операндов, вычисленный с точностью float
, но иногда дает значение, которое меньше, чем меньше. В любом случае, Sun решила, что все должно быть сделано одинаково, и они решили использовать значения минимальной точности float
.
Обратите внимание, что основные преимущества меньших типов данных становятся очевидными, когда большое количество из них хранится вместе в массиве; даже если не было преимуществ для того, чтобы иметь отдельные переменные типов меньших, чем 64-битные, целесообразно иметь массивы, которые могут хранить меньшие значения более компактно; с локальной переменной be a byte
, а не long
сохраняет семь байтов; имеющие массив из 1 000 000 номеров, удерживают каждое число как byte
, а не long
волны 7 000 000 байт. Поскольку каждому типу массива требуется только несколько операций (в первую очередь, считывать один элемент, хранить один элемент, копировать диапазон элементов в массиве или копировать ряд элементов из одного массива в другой), добавленная сложность иметь больше типы массивов не так строги, как сложность наличия большего числа типов дискретных числовых значений, которые можно использовать непосредственно.
Собственно, было бы небольшое преимущество. Если у вас есть
class MyTimeAndDayOfWeek {
byte dayOfWeek;
byte hour;
byte minute;
byte second;
}
то на типичной JVM ему нужно столько места, сколько класс, содержащий один int
. Потребление памяти округляется до следующего кратного 8 или 16 байтов (IIRC, который настраивается), поэтому случаи, когда есть реальная экономия, довольно редки.
Этот класс будет несколько проще использовать, если соответствующие методы Calendar
вернули byte
. Но таких методов Calendar
нет, только get(int)
, которые должны возвращать int
из-за других полей. Каждая операция на меньших типах продвигается до int
, поэтому вам нужно много кастинга.
Скорее всего, вы либо откажетесь, либо перейдете к int
или сеттерам записи, например
void setDayOfWeek(int dayOfWeek) {
this.dayOfWeek = checkedCastToByte(dayOfWeek);
}
Тогда тип DAY_OF_WEEK
не имеет значения.