Почему люди все еще используют примитивные типы в Java?
Начиная с Java 5, у нас был бокс / распаковка примитивных типов, так что int
завернут, чтобы быть java.lang.Integer
и так и так и так далее.
В последнее время я вижу много новых Java-проектов (которые определенно требуют JRE по крайней мере версии 5, если не 6), которые используют int
скорее, чем java.lang.Integer
хотя использовать его гораздо удобнее, так как в нем есть несколько вспомогательных методов для преобразования в long
значения и др.
Почему некоторые все еще используют примитивные типы в Java? Есть ли ощутимая выгода?
19 ответов
В " Эффективной Java" Джошуа Блоха, пункт 5: "Избегайте создания ненужных объектов", он публикует следующий пример кода:
public static void main(String[] args) {
Long sum = 0L; // uses Long, not long
for (long i = 0; i <= Integer.MAX_VALUE; i++) {
sum += i;
}
System.out.println(sum);
}
и это займет 43 секунды, чтобы бежать. Если взять Long в примитиве, то это снизится до 6,8 секунд... Если есть какие-либо признаки, почему мы используем примитивы.
Отсутствие равноправия местных ценностей также является проблемой (.equals()
довольно многословен по сравнению с ==
)
для бизиклопа:
class Biziclop {
public static void main(String[] args) {
System.out.println(new Integer(5) == new Integer(5));
System.out.println(new Integer(500) == new Integer(500));
System.out.println(Integer.valueOf(5) == Integer.valueOf(5));
System.out.println(Integer.valueOf(500) == Integer.valueOf(500));
}
}
Результаты в:
false
false
true
false
РЕДАКТИРОВАТЬ Почему (3) возвращается true
и (4) возврат false
?
Потому что это два разных объекта. 256 целых чисел, ближайших к нулю [-128; 127] кэшируются JVM, поэтому они возвращают один и тот же объект для них. Однако за пределами этого диапазона они не кэшируются, поэтому создается новый объект. Чтобы усложнить задачу, JLS требует, чтобы было кэшировано не менее 256 маховиков. Реализаторы JVM могут добавить больше, если они захотят, это означает, что это может выполняться в системе, где кэшируются ближайшие 1024, и все они возвращают true... #awkward
Autounboxing может привести к трудно обнаружить NPE
Integer in = null;
...
...
int i = in; // NPE at runtime
В большинстве ситуаций нулевое назначение in
гораздо менее очевидно, чем выше.
Типы в штучной упаковке имеют худшую производительность и требуют больше памяти.
Примитивные типы:
int x = 1000;
int y = 1000;
Теперь оцените:
x == y
Это true
, Вряд ли удивительно. Теперь попробуйте в штучной упаковке:
Integer x = 1000;
Integer y = 1000;
Теперь оцените:
x == y
Это false
, Наверное. Зависит от времени выполнения. Достаточно ли этой причины?
Помимо проблем с производительностью и памятью, я хотел бы затронуть еще одну проблему: List
Интерфейс будет сломан без int
,
Проблема перегружена remove()
метод (remove(int)
против remove(Object)
). remove(Integer)
всегда будет разрешать вызов последнего, поэтому вы не можете удалить элемент по индексу.
С другой стороны, при добавлении и удалении int
:
final int i = 42;
final List<Integer> list = new ArrayList<Integer>();
list.add(i); // add(Object)
list.remove(i); // remove(int) - Ouch!
Можете ли вы представить себе
for (int i=0; i<10000; i++) {
do something
}
цикл с java.lang.Integer вместо этого? Java.lang.Integer является неизменным, поэтому каждое приращение в цикле будет создавать новый объект Java в куче, а не просто увеличивать int в стеке с помощью одной инструкции JVM. Представление будет дьявольским.
Я бы действительно не согласился с тем, что гораздо удобнее использовать java.lang.Integer, чем int. Напротив. Автобокс означает, что вы можете использовать int там, где в противном случае вы были бы вынуждены использовать Integer, а java-компилятор позаботится о вставке кода для создания нового объекта Integer для вас. Автобокс - это все, что позволяет вам использовать int там, где ожидается Integer, с компилятором, вставляющим соответствующую конструкцию объекта. Это никоим образом не устраняет и не уменьшает потребность в int в первую очередь. С автобоксом вы получаете лучшее из обоих миров. Вы получаете Integer, созданный для вас автоматически, когда вам нужен Java-объект на основе кучи, и вы получаете скорость и эффективность int, когда вы просто выполняете арифметические и локальные вычисления.
Примитивные типы намного быстрее:
int i;
i++;
Целое число (все числа, а также строка) является неизменным типом: после создания он не может быть изменен. Если i
было целым, чем i++
создаст новый объект Integer - гораздо более дорогой с точки зрения памяти и процессора.
В первую очередь, привычка. Если вы программировали на Java в течение восьми лет, вы накапливаете значительное количество инерции. Зачем менять, если нет веских причин для этого? Это не значит, что использование коробочных примитивов дает дополнительные преимущества.
Другая причина состоит в том, чтобы утверждать, что null
не является допустимым вариантом. Было бы бессмысленным и вводящим в заблуждение объявлять сумму двух чисел или переменную цикла как Integer
,
Это тоже аспект производительности, в то время как во многих случаях разница в производительности не критична (хотя когда это так, это довольно плохо), никто не любит писать код, который можно было бы написать так же легко, быстрее, чем мы уже использовал к.
Кстати, у Smalltalk есть только объекты (без примитивов), и все же они оптимизировали свои маленькие целые числа (используя не все 32 бита, только 27 или около того), чтобы не выделять пространство кучи, а просто использовать специальный битовый шаблон. Также другие общие объекты (true, false, null) имели здесь специальные битовые комбинации.
Таким образом, по крайней мере на 64-битных JVM (с пространством имен указателей на 64 бита) должна быть возможность вообще не иметь никаких объектов Integer, Character, Byte, Short, Boolean, Float (и small Long) (кроме созданных ими). явным new ...()
), только специальные битовые комбинации, которыми обычные операторы могли бы манипулировать достаточно эффективно.
Я не могу поверить, что никто не упомянул о том, что, по моему мнению, является наиболее важной причиной: "int" так намного проще печатать, чем "Integer". Я думаю, что люди недооценивают важность краткого синтаксиса. Производительность на самом деле не является причиной для того, чтобы их избегать, потому что большую часть времени, когда кто-либо использует числа, находится в индексах цикла, а увеличение и сравнение этих значений ничего не стоит в любом нетривиальном цикле (независимо от того, используете ли вы int или Integer).
Другая причина заключалась в том, что вы можете получить NPE, но этого очень легко избежать с помощью коробочных типов (и этого гарантированно избежать, если вы всегда инициализируете их ненулевыми значениями).
Другая причина заключалась в том, что (new Long(1000))==(new Long(1000)) имеет значение false, но это просто еще один способ сказать, что ".equals" не имеет синтаксической поддержки коробочных типов (в отличие от операторов <,>, = и т. д.), поэтому мы возвращаемся к причине "более простого синтаксиса".
Я думаю, что пример непримитивного цикла Стива Йегге очень хорошо иллюстрирует мою точку зрения: http://sites.google.com/site/steveyegge2/language-trickery-and-ejb
Подумайте об этом: как часто вы используете типы функций в языках, которые имеют хороший синтаксис для них (например, любой функциональный язык, Python, Ruby и даже C), по сравнению с Java, где вы должны имитировать их с помощью интерфейсов, таких как Runnable и Callable, и безымянные уроки.
Пара причин, чтобы не избавляться от примитивов:
- Обратная совместимость.
Если это будет устранено, любые старые программы даже не запустятся.
- JVM переписать.
Вся JVM должна быть переписана для поддержки этой новой вещи.
- Большой объем памяти.
Вам нужно будет сохранить значение и ссылку, которая использует больше памяти. Если у вас есть огромный массив байтов, используя byte
значительно меньше, чем при использовании Byte
"S.
- Проблемы с нулевым указателем.
декларирование int i
затем делать вещи с i
не приведет ни к каким проблемам, но объявляя Integer i
и затем, сделав то же самое, вы получите NPE.
- Вопросы равенства.
Рассмотрим этот код:
Integer i1 = 5;
Integer i2 = 5;
i1 == i2; // Currently would be false.
Было бы ложным. Операторы должны быть перегружены, и это приведет к серьезной переписке материала.
- Медленный
Обертки объектов значительно медленнее, чем их примитивные аналоги.
В дополнение к тому, что сказали другие, примитивные локальные переменные выделяются не из кучи, а вместо этого в стеке. Но объекты выделяются из кучи и, следовательно, должны быть сборщиком мусора.
Объекты гораздо более тяжелые, чем примитивные типы, поэтому примитивные типы гораздо более эффективны, чем экземпляры классов-оболочек.
Примитивные типы очень просты: например, int 32-битный и занимает ровно 32 бита в памяти, и им можно манипулировать напрямую. Целочисленный объект - это законченный объект, который (как и любой объект) должен храниться в куче, и доступ к нему возможен только через ссылку (указатель) на него. Скорее всего, он также занимает более 32 бит (4 байта) памяти.
Тем не менее, тот факт, что в Java есть различие между примитивными и не примитивными типами, также является признаком возраста языка программирования Java. Более новые языки программирования не имеют этого различия; компилятор такого языка достаточно умен, чтобы самому определить, используете ли вы простые значения или более сложные объекты.
Например, в Scala нет примитивных типов; есть класс Int для целых чисел, а Int - это реальный объект (который вы можете использовать для методов и т. д.). Когда компилятор компилирует ваш код, он использует примитивные int за кулисами, поэтому использование Int столь же эффективно, как и использование примитива int в Java.
Примитивные типы имеют много преимуществ:
- Проще написать код
- Производительность лучше, так как вы не создаете экземпляр объекта для переменной
- Поскольку они не представляют ссылку на объект, нет необходимости проверять наличие нулей
- Используйте примитивные типы, если вам не нужно использовать преимущества бокса.
int loops = 100000000;
long start = System.currentTimeMillis();
for (Long l = new Long(0); l<loops;l++) {
//System.out.println("Long: "+l);
}
System.out.println("Milliseconds taken to loop '"+loops+"' times around Long: "+ (System.currentTimeMillis()- start));
start = System.currentTimeMillis();
for (long l = 0; l<loops;l++) {
//System.out.println("long: "+l);
}
System.out.println("Milliseconds taken to loop '"+loops+"' times around long: "+ (System.currentTimeMillis()- start));
Миллисекунды зациклили "100000000" раз вокруг Long: 468
Миллисекунды зацикливаются на 100000000 раз вокруг: 31
С другой стороны, я бы не отказался увидеть, как что-то подобное находит путь в Java.
Integer loop1 = new Integer(0);
for (loop1.lessThan(1000)) {
...
}
Где цикл for автоматически увеличивает loop1 от 0 до 1000 или
Integer loop1 = new Integer(1000);
for (loop1.greaterThan(0)) {
...
}
Где цикл for автоматически уменьшает loop1 1000 до 0.
Трудно понять, какие оптимизации происходят под прикрытием.
Для локального использования, когда компилятор имеет достаточно информации для оптимизации, исключая возможность нулевого значения, я ожидаю, что производительность будет такой же или похожей.
Тем не менее, массивы примитивов, очевидно, очень отличаются от коллекций в штучной упаковке примитивов. Это имеет смысл, учитывая, что в глубине коллекции возможно очень мало оптимизаций.
Более того, Integer
имеет гораздо более высокие логические издержки по сравнению с int
: теперь вам нужно беспокоиться о том, int a = b + c;
бросает исключение.
Я бы максимально использовал примитивы и полагался на фабричные методы и автобокс, чтобы дать мне более семантически мощные коробочные типы, когда они необходимы.
- Вам нужны примитивы для выполнения математических операций
- Примитивы занимают меньше памяти, как указано выше, и лучше работают
Вы должны спросить, почему требуется класс / тип объекта
Причина наличия Типа объекта состоит в том, чтобы облегчить нашу жизнь, когда мы имеем дело с Коллекциями. Примитивы не могут быть добавлены непосредственно в List/Map, вам нужно написать класс-оболочку. Здесь вам помогут классы Readymade Integer, а также множество полезных методов, таких как Integer.pareseInt(str).
Я согласен с предыдущими ответами, использование примитивов обертки объектов может быть дорогим Но, если производительность не критична для вашего приложения, вы избегаете переполнения при использовании объектов. Например:
long bigNumber = Integer.MAX_VALUE + 2;
Значение bigNumber
is -2147483647, и вы ожидаете, что это будет 2147483649. Это ошибка в коде, которая будет исправлена с помощью:
long bigNumber = Integer.MAX_VALUE + 2l; // note that '2' is a long now (it is '2L').
А также bigNumber
было бы 2147483649. Такие ошибки иногда легко пропустить и могут привести к неизвестному поведению или уязвимостям (см. CWE-190).
Если вы используете объекты-оболочки, эквивалентный код не будет компилироваться.
Long bigNumber = Integer.MAX_VALUE + 2; // Not compiling
Так что проще решить подобные проблемы, используя объекты-обертки примитивов.
На ваш вопрос уже ответили так, что я отвечаю просто для того, чтобы добавить немного больше информации, не упомянутой ранее.
Эти примитивные типы являются гораздо быстрее и требуют гораздо меньше памяти. Поэтому мы могли бы предпочесть их использовать.
С другой стороны, текущая спецификация языка Java не позволяет использовать примитивные типы в параметризованных типах (обобщенных), в коллекциях Java или API Reflection.
Когда нашему приложению нужны коллекции с большим количеством элементов, мы должны рассмотреть возможность использования массивов с как можно более "экономичным" типом.
* Подробную информацию см. В источнике: https://www.baeldung.com/java-primitives-vs-objects
Потому что JAVA выполняет все математические операции в примитивных типах. Рассмотрим этот пример:
public static int sumEven(List<Integer> li) {
int sum = 0;
for (Integer i: li)
if (i % 2 == 0)
sum += i;
return sum;
}
Здесь операции напоминания и унарного плюса не могут применяться к типу Integer(Reference), компилятор выполняет распаковку и выполняет операции.
Итак, убедитесь, сколько операций автобоксирования и распаковки происходит в Java-программе. Поскольку для выполнения этой операции требуется время.
Как правило, лучше сохранять аргументы типа Reference и результат примитивного типа.
Вкратце: примитивные типы быстрее и требуют меньше памяти, чем коробочные.