Почему API Java используется int
, когда short
или даже byte
было бы достаточно?
Пример: DAY_OF_WEEK
поле в классе Calendar
использует int
.
Если разница слишком минимальна, тогда почему эти типы данных ( short
, int
) вообще существуют?
Почему API Java используется int
, когда short
или даже byte
было бы достаточно?
Пример: DAY_OF_WEEK
поле в классе Calendar
использует int
.
Если разница слишком минимальна, тогда почему эти типы данных ( short
, int
) вообще существуют?
Ответы:
Некоторые из причин уже были указаны. Например, тот факт, что «... (почти) все операции с байтами, краткими будут переводить эти примитивы в int» . Однако следующий очевидный вопрос будет следующим: ПОЧЕМУ продвигаются эти типы int
?
Итак, чтобы пойти на один уровень глубже: ответ может быть просто связан с набором инструкций виртуальной машины Java. Как показано в Таблице в Спецификации виртуальной машины Java , все интегральные арифметические операции, такие как сложение, деление и другие, доступны только для типа int
и типа long
, но не для более мелких типов.
(За исключением: меньшие типы ( byte
и short
) в основном предназначены только для массивов . Массив like new byte[1000]
займет 1000 байтов, а массив like new int[1000]
займет 4000 байтов)
Теперь, конечно, можно сказать, что «... следующий очевидный вопрос будет следующим: ПОЧЕМУ эти инструкции предлагаются только для int
(и long
)?» ,
Одна из причин упоминается в спецификации JVM, упомянутой выше:
Если бы каждая типизированная инструкция поддерживала все типы данных времени выполнения виртуальной машины Java, было бы больше инструкций, чем можно было бы представить в байте.
Кроме того, виртуальная машина Java может рассматриваться как абстракция реального процессора. А внедрение специализированного арифметического логического модуля для небольших типов не стоило бы усилий: для этого потребовались бы дополнительные транзисторы, но при этом он мог выполнять только одно сложение за один такт. Доминирующая архитектура при разработке JVM была 32-битной, как раз для 32-битной int
. (Операции, включающие 64-битное long
значение, реализуются как особый случай).
(Примечание: последний абзац немного упрощен, учитывая возможную векторизацию и т. Д., Но должен дать основную идею, не вдаваясь слишком глубоко в темы проектирования процессора)
РЕДАКТИРОВАТЬ: краткое дополнение, сосредоточив внимание на примере из вопроса, но в более общем смысле: можно также спросить, не будет ли полезно хранить поля с использованием меньших типов. Например, можно подумать, что память может быть сохранена путем сохранения Calendar.DAY_OF_WEEK
в виде byte
. Но здесь вступает в игру формат файла классов Java: все поля в файле классов занимают как минимум один «слот», который имеет размер один int
(32 бита). («Широкие» поля double
и long
занимают два слота). Так что явное объявление поля как short
или byte
не сохранит никакой памяти либо.
int
. Если у вас есть ссылка на другую реализацию, я бы обновил ответ и соответственно вставил ссылку.
(Почти) Все операции 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
значений, когда они используются в арифметических операциях. (В стеке операндов шорты даже выражаются в виде целых чисел.) Это было бы весьма неудобно для использования, поэтому int
значения часто предпочтительнее для констант.
По сравнению с этим, выигрыш в эффективности хранения минимален, потому что существует только фиксированное количество таких констант. Мы говорим о 40 константах. Изменение их хранилища из int
в short
безопасное место 40 * 16 bit = 80 byte
. Смотрите этот ответ для дальнейшего использования.
Если бы вы использовали философию, в которой интегральные константы хранятся в наименьшем типе, в который они вписываются, то у Java возникла бы серьезная проблема: всякий раз, когда программисты пишут код с использованием интегральных констант, они должны обращать особое внимание на свой код, чтобы проверить, является ли тип константы имеют значение, и если это так, ищите тип в документации и / или делайте все необходимые преобразования типов.
Итак, теперь, когда мы наметили серьезную проблему, каких преимуществ вы могли бы надеяться получить с этой философией? Я был бы не удивлен, если бы единственным наблюдаемым во время выполнения эффектом этого изменения был бы тип, который вы получаете, когда вы смотрите на константу с помощью отражения. (и, конечно, любые ошибки, которые вводят ленивые / невольные программисты, неправильно учитывающие типы констант)
Очень просто взвесить все за и против: это плохая философия.
Сложность конструкции виртуальной машины зависит от того, сколько видов операций она может выполнить. Кроме того, проще иметь четыре реализации команды типа «умножение» - по одной для 32-разрядного целого, 64-разрядного целого, 32-разрядного с плавающей точкой и 64-разрядного с плавающей запятой - чем иметь к вышеупомянутому, версии для меньших числовых типов также. Более интересным вопросом проектирования является то, почему должно быть четыре типа, а не меньше (выполнение всех целочисленных вычислений с 64-разрядными целыми числами и / или выполнение всех вычислений с плавающей запятой с 64-разрядными значениями с плавающей запятой). Причина использования 32-разрядных целых чисел заключается в том, что Java должна была работать на многих платформах, где 32-разрядные типы могут обрабатываться так же быстро, как 16-разрядные или 8-разрядные типы, но операции с 64-разрядными типами будут заметно помедленнее.только 32-битные типы
Что касается выполнения вычислений с плавающей точкой на 32-битных значениях, преимущества немного менее очевидны. Есть несколько платформ, где такие вычисленияfloat a=b+c+d;
можно было бы выполнить наиболее быстро, преобразовав все операнды в тип с более высокой точностью, добавив их, а затем преобразовав результат обратно в 32-разрядное число с плавающей запятой для хранения. Существуют другие платформы, где было бы более эффективно выполнять все вычисления с использованием 32-битных значений с плавающей точкой. Создатели Java решили, что все платформы должны действовать одинаково, и что им следует отдавать предпочтение аппаратным платформам, для которых 32-разрядные вычисления с плавающей запятой выполняются быстрее, чем более длинные, даже несмотря на то, что этот ПК сильно снижает производительность и точность математики с плавающей запятой на типичном ПК, а также на многих машинах без блоков с плавающей запятой. Обратите внимание, что, в зависимости от значений b, c и d, используются промежуточные вычисления с более высокой точностью при вычислении выражений, подобных вышеупомянутымfloat a=b+c+d;
иногда будет давать результаты, которые являются значительно более точными, чем было бы достигнуто для всех промежуточных операндов, вычисленных с float
точностью, но иногда будет давать значение, которое является чуть менее точным. В любом случае, Sun решила, что все должно быть сделано так же, и они выбрали минимальные float
значения точности .
Обратите внимание, что основные преимущества меньших типов данных становятся очевидными, когда большое их количество хранится вместе в массиве; даже если бы не было преимуществ иметь отдельные переменные типов меньше 64-битных, стоит иметь массивы, которые могут более компактно хранить меньшие значения; иметь локальную переменную, byte
а не long
сохранять семь байтов; имея массив 1000000 чисел держать каждое число в виде , byte
а неlong
волны 7 000 000 байт. Поскольку каждый тип массива должен поддерживать только несколько операций (в частности, чтение одного элемента, сохранение одного элемента, копирование диапазона элементов в массиве или копирование диапазона элементов из одного массива в другой), дополнительная сложность состоит в том, что Типы массивов не так серьезны, как сложность наличия большего количества типов непосредственно используемых дискретных числовых значений.
На самом деле, было бы небольшое преимущество. Если у тебя есть
class MyTimeAndDayOfWeek {
byte dayOfWeek;
byte hour;
byte minute;
byte second;
}
тогда на типичной JVM ему нужно столько же места, сколько классу, содержащему один int
. Потребление памяти округляется до следующего кратного 8 или 16 байт (IIRC, это настраивается), поэтому случаи, когда есть реальная экономия, довольно редки.
Этот класс будет немного проще в использовании, если соответствующие Calendar
методы вернут a byte
. Но таких Calendar
методов нет, только get(int)
которые должны возвращать int
из-за других полей. Каждая операция на меньших типах способствует int
, поэтому вам нужно много кастинга.
Скорее всего, вы либо сдадите и переключитесь на int
или напишите сеттеры, как
void setDayOfWeek(int dayOfWeek) {
this.dayOfWeek = checkedCastToByte(dayOfWeek);
}
Тогда тип DAY_OF_WEEK
не имеет значения, в любом случае.