Почему люди все еще используют примитивные типы в Java?

163

Начиная с Java 5, у нас был бокс / распаковка примитивных типов, чтобы они были intупакованы java.lang.Integer, и так далее, и так далее.

В последнее время я вижу много новых Java-проектов (для которых определенно требуется JRE как минимум версии 5, если не 6), которые используют, intа не java.lang.Integer, хотя гораздо удобнее использовать последний, так как в нем есть несколько вспомогательных методов для преобразования до longзначений и др.

Почему некоторые все еще используют примитивные типы в Java? Есть ли ощутимая выгода?

Нафтули Кей
источник
49
задумывались о потреблении памяти и производительности?
Тедил
76
Я добавил тег autoboxing ... и обнаружил, что три человека действительно следуют за ним. В самом деле? Люди следят за тэгом AUTOBOXING?
CorsiKa
4
@glowcoder Они не настоящие люди, это просто абстрактные концепции, которые принимают человеческую форму, чтобы отвечать на SO. :)
biziclop
9
@TK Kocheran В основном потому, что new IntegeR(5) == new Integer(5)по правилам, оценивать как ложное.
biziclop
10
Смотрите GNU Trove или Mahout Collections или HPPC или ... для решений для коллекций примитивных типов. Те из нас, кто заботится о скорости, проводят время, используя больше примитивных типов, а не меньше.
bmargulies

Ответы:

395

В « Эффективной 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

corsiKa
источник
54
А теперь представьте, если бы также iбыли объявлены Long!
ColinD
14
@TREE - спецификация фактически требует, чтобы виртуальные машины создавали весы в определенном диапазоне. Но, к сожалению, это позволяет им расширить этот диапазон, что означает, что программы могут вести себя по-разному на разных виртуальных машинах. Так много для кроссплатформенности ...
Даниэль Уорвикер
12
Java потерпела неудачу, все больше и больше плохих вариантов дизайна. Автобокс - полный провал, он не является надежным, предсказуемым или переносимым. Мне действительно очень интересно, о чем они думают ... вместо того, чтобы исправить ужасающую дуальность примитивных объектов, им удалось сделать это хуже, чем вначале.
Поп Каталин
34
@ Каталин Я не согласен с вами, что автобокс полный провал. У него есть некоторые недостатки, которые ничем не отличаются от любого другого дизайна, который можно было бы использовать (в том числе и ничего). Они ясно дают понять, чего можно и чего не следует ожидать, и, как и любой другой дизайн, ожидают, что разработчики знают и подчиняются контрактам. из этих конструкций.
CorsiKa
9
@NaftuliTzviKay Это не «провал». Они делают ОЧЕНЬ ЧТО ЯВЛЯЮТСЯ, что ==оператор выполняет сравнения ссылочных идентификаторов в Integerвыражениях и сравнения равенства значений в intвыражениях. Integer.equals()существует по этой самой причине. Вы никогда не должны использовать ==для сравнения значений в любом не примитивном типе. Это Java 101.
NullUserException
86

Autounboxing может привести к трудно обнаружить NPE

Integer in = null;
...
...
int i = in; // NPE at runtime

В большинстве ситуаций нулевое назначение inнамного менее очевидно, чем выше.

Хайко Рупп
источник
43

Типы в штучной упаковке имеют худшую производительность и требуют больше памяти.

Orbit
источник
40

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

int x = 1000;
int y = 1000;

Теперь оцените:

x == y

Это true. Вряд ли удивительно. Теперь попробуйте в штучной упаковке:

Integer x = 1000;
Integer y = 1000;

Теперь оцените:

x == y

Это false. Наверное. Зависит от времени выполнения. Достаточно ли этой причины?

Дэниел Уорвикер
источник
36

Помимо проблем с производительностью и памятью, я хотел бы затронуть еще одну проблему: без 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!
xehpuk
источник
7
Это было бы сломано, да. Однако, удаление (int) является недостатком дизайна IMO. Имена методов никогда не должны быть перегружены, если есть малейшая вероятность перепутать.
MrBackend
4
@MrBackend Достаточно справедливо. Интересно, что Vectorбыло removeElementAt(int)с самого начала. remove(int)был представлен со структурой коллекций в Java 1.2.
xehpuk
6
@MrBackend: когда Listразрабатывался API, не существовало ни Generics, ни Autoboxing, поэтому не было шансов смешаться remove(int)и remove(Object)
Хольгер
@Franklin Yu: конечно, но при разработке нового языка / версии без ограничений совместимости вы не остановитесь на изменении этой неудачной перегрузки. Вы бы просто избавились от различий между примитивами и коробочными значениями, так что вопрос, который нужно использовать, никогда не появится.
Хольгер
27

Можете ли вы представить себе

  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, когда вы просто выполняете арифметические и локальные вычисления.

Том Кварендон
источник
19

Примитивные типы намного быстрее:

int i;
i++;

Целое число (все числа, а также строка) является неизменным типом: после создания он не может быть изменен. Если бы iбыло Integer, то i++было бы создать новый объект Integer - гораздо дороже с точки зрения памяти и процессора.

Питер Кнего
источник
Вы не хотите, чтобы одна переменная изменялась, если вы делаете i++для другой переменной, поэтому Integer вполне должен быть неизменным, чтобы иметь возможность сделать это (или, по крайней мере, это i++должно было бы создать новый объект Integer в любом случае). (И примитивные значения тоже неизменны - вы просто не замечаете этого, поскольку они не являются объектами.)
Paŭlo Ebermann
4
@ Paŭlo: Сказать, что примитивные ценности являются неизменными, бессмысленно. Когда вы переназначаете примитивную переменную на новое значение, вы не создаете ничего нового. Распределение памяти не используется. Точка зрения Питера: i ++ для примитива не выделяет память, но для объекта это обязательно.
Эдди
@Eddie: (Это не обязательно требует выделения памяти, оно также может возвращать кэшированное значение. Я думаю, что для некоторых небольших значений это так.) Моя точка зрения заключалась в том, что неизменность целых чисел здесь не является решающей точкой, вы все равно захотите иметь другой объект, независимо от неизменности.
Paŭlo Ebermann
@ Paŭlo: единственное, что я хотел сказать, это то, что Integer на порядок медленнее, чем примитивы. И это связано с тем, что коробочные типы являются неизменяемыми, и каждый раз, когда вы меняете значение, создается новый объект. Я не утверждал, что с ними что-то не так или тот факт, что они неизменны. Просто они медленнее и кодер должен это знать. Посмотрите, как работает Groovy без примитивных типов jroller.com/rants/entry/why_is_groovy_so_slow
Питер Кнего
1
Неизменность и ++красная сельдь здесь. Представьте себе , Java была расширена для поддержки оператора перегрузки в очень простым способом, таким образом, что если класс (например, Integerесть метод plus, то вы можете написать i + 1вместо i.plus(1). И предположим также , что компилятор достаточно умен , чтобы расширить i++в i = i + 1. Теперь можно сказать , i++и эффективно «приращение переменной я» , не Integerбудучи изменяемым.
Daniel Earwicker
16

Прежде всего, привычка. Если вы программировали на Java в течение восьми лет, вы накапливаете значительное количество инерции. Зачем менять, если нет веских причин для этого? Это не значит, что использование коробочных примитивов дает дополнительные преимущества.

Другая причина состоит в том, чтобы утверждать, что nullэто недопустимый вариант. Было бы бессмысленно и неправильно вводить сумму двух чисел или переменную цикла как Integer.

Это тоже аспект производительности, в то время как во многих случаях разница в производительности не критична (хотя когда это так, это довольно плохо), никто не любит писать код, который можно было бы написать так же легко, быстрее, чем мы уже привык к.

biziclop
источник
15
Я не согласен. Аспект производительности может быть критическим. Очень мало из этого, вероятно, инерция или сила привычки.
Эдди
7
@ Эдди Может быть, но это очень редко. Поверьте мне, для большинства людей аргументы в пользу производительности - это просто оправдание.
biziclop
3
Я тоже хотел бы защитить аргумент производительности. В Android с Dalvik каждый создаваемый вами объект увеличивает «риск» вызова GC, и чем больше у вас будет пауз, тем дольше будет время. Поэтому создание целых чисел вместо int в цикле, вероятно, обойдется вам в несколько пропущенных кадров.
Игорь Чордаш
1
@PSIXO Это справедливо, я написал это исключительно с точки зрения серверной Java. Мобильные устройства - это совершенно другое животное. Но я хотел сказать, что даже разработчики, которые в противном случае пишут ужасный код без какого-либо отношения к производительности, будут ссылаться на это как на причину, для них это звучит очень оправданно.
Бизиклоп
12

Кстати, Smalltalk имеет только объекты (без примитивов), и все же они оптимизировали свои маленькие целые числа (используя не все 32 бита, только 27 или около того), чтобы не выделять пространство кучи, а просто использовать специальный битовый шаблон. Также другие общие объекты (true, false, null) имели здесь специальные битовые комбинации.

Таким образом, по крайней мере на 64-битных JVM (с пространством имен указателей на 64 бита) должна быть возможность вообще не иметь никаких объектов Integer, Character, Byte, Short, Boolean, Float (и small Long) (кроме созданных ими). явным образом new ...()), только специальные битовые комбинации, которыми могут эффективно управлять обычные операторы.

Пауло Эберманн
источник
Я должен был сказать «некоторые реализации», так как это не регулируется спецификациями языка, я думаю. (И, к сожалению, я не могу привести здесь никаких источников, это только из того, что я где-то слышал.)
Paŭlo Ebermann
ŭло, JIT уже сохраняет мету в указателе; В том числе, указатель может хранить информацию GC или Klass (оптимизация класса - намного лучшая идея, чем оптимизация целых чисел, о которых мне наплевать). Изменение указателя потребует сдвига кода / cmp / jnz (или что-то в этом роде) перед каждой загрузкой указателя. Скорее всего, ветвление не очень хорошо предсказывается аппаратными средствами (поскольку это может быть как тип значения, так и обычный объект), и это приведет к снижению производительности.
bestsss
3
Я делал Smalltalk в течение нескольких лет. Оптимизация все еще была довольно дорогой, так как для каждой операции над int им приходилось снимать маску и повторно применять их. В настоящее время java находится на одном уровне с C при манипулировании примитивными числами. С маской + маска это, вероятно, будет> 30% медленнее.
Р.Мёллер
9

Я не могу поверить, что никто не упомянул, что я думаю, что это самая важная причина: «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, и безымянные занятия.

CromTheDestroyer
источник
8

Пара причин, чтобы не избавляться от примитивов:

  • Обратная совместимость.

Если это будет устранено, любые старые программы даже не запустятся.

  • JVM переписать.

Вся JVM должна быть переписана для поддержки этой новой вещи.

  • Большой объем памяти.

Вам нужно будет сохранить значение и ссылку, которая использует больше памяти. Если у вас огромный массив байтов, использование byte's значительно меньше, чем использование Byte'.

  • Проблемы с нулевым указателем.

Объявление, а int iзатем выполнение iчего-либо, не вызовет никаких проблем, но объявление, Integer iа затем выполнение того же действия приведет к NPE.

  • Вопросы равенства.

Рассмотрим этот код:

Integer i1 = 5;
Integer i2 = 5;

i1 == i2; // Currently would be false.

Было бы ложным. Операторы должны быть перегружены, и это приведет к серьезной переписке материала.

  • Медленный

Обертки объектов значительно медленнее, чем их примитивные аналоги.

Анубийский нуб
источник
i1 == i2; будет ложным, только если i1> = 128. Итак, текущий пример неверен
Geniy
7

Объекты гораздо более тяжелые, чем примитивные типы, поэтому примитивные типы гораздо более эффективны, чем экземпляры классов-оболочек.

Примитивные типы очень просты: например, int 32-битный и занимает ровно 32 бита в памяти, и им можно манипулировать напрямую. Целочисленный объект - это законченный объект, который (как и любой объект) должен храниться в куче, и доступ к нему возможен только через ссылку (указатель) на него. Скорее всего, он также занимает более 32 бит (4 байта) памяти.

Тем не менее, тот факт, что в Java есть различие между примитивными и не примитивными типами, также является признаком возраста языка программирования Java. Более новые языки программирования не имеют этого различия; компилятор такого языка достаточно умен, чтобы самому определить, используете ли вы простые значения или более сложные объекты.

Например, в Scala нет примитивных типов; есть класс Int для целых чисел, а Int - это реальный объект (который вы можете использовать для методов и т. д.). Когда компилятор компилирует ваш код, он использует примитивные int за кулисами, поэтому использование Int столь же эффективно, как и использование примитива int в Java.

Умеш К
источник
1
Я бы предположил, что JRE будет достаточно «умным», чтобы делать это и с Java-примитивами. Потерпеть поражение.
Нафтули Кей
7

В дополнение к тому, что сказали другие, примитивные локальные переменные выделяются не из кучи, а вместо этого в стеке. Но объекты выделяются из кучи и, следовательно, должны быть удалены.

Эдди
источник
3
Извините, это неправильно. Умная JVM может выполнять анализ экранирования для любых распределений объектов и, если они не могут экранировать, размещать их в стеке.
Rlibby
2
Да, это начинает быть особенностью современных JVM. Через пять лет то, что вы говорите, будет правдой для большинства используемых тогда JVM. Сегодня это не так. Я почти прокомментировал это, но решил не комментировать. Возможно, я должен был что-то сказать.
Эдди
6

Примитивные типы имеют много преимуществ:

  • Проще написать код
  • Производительность лучше, так как вы не создаете экземпляр объекта для переменной
  • Так как они не представляют ссылку на объект, нет необходимости проверять наличие нулей
  • Используйте примитивные типы, если вам не нужны преимущества бокса.
Adriana
источник
5

Трудно понять, какие оптимизации происходят под прикрытием.

Для локального использования, когда компилятор имеет достаточно информации для оптимизации, исключая возможность нулевого значения, я ожидаю, что производительность будет такой же или похожей .

Тем не менее, массивы примитивов, очевидно, сильно отличаются от коллекций в штучной упаковке примитивов. Это имеет смысл, учитывая, что в глубине коллекции возможно очень мало оптимизаций.

Более того, логические издержкиInteger намного выше по сравнению с : теперь вам нужно беспокоиться о том, генерирует ли исключение или нет .intint a = b + c;

Я бы максимально использовал примитивы и полагался на фабричные методы и автобокс, чтобы дать мне более семантически мощные коробочные типы, когда они необходимы.

Мэтью Уиллис
источник
5
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.

Кит
источник
2
  1. Вам нужны примитивы для выполнения математических операций
  2. Примитивы занимают меньше памяти, как указано выше, и лучше работают

Вы должны спросить, почему требуется класс / тип объекта

Причина наличия Типа объекта состоит в том, чтобы облегчить нашу жизнь, когда мы имеем дело с Коллекциями. Примитивы не могут быть добавлены непосредственно в List / Map, вам нужно написать класс-оболочку. Здесь вам помогут готовые классы целочисленных типов, а также множество полезных методов, таких как Integer.pareseInt (str).

Prabakaran
источник
2

Я согласен с предыдущими ответами, используя примитивы обертки объектов может быть дорого. Но если производительность не критична для вашего приложения, вы избегаете переполнения при использовании объектов. Например:

long bigNumber = Integer.MAX_VALUE + 2;

Значение bigNumber-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

Так что проще решить подобные проблемы, используя объекты-обертки примитивов.

На ваш вопрос уже ответили так, что я отвечаю просто для того, чтобы добавить немного больше информации, не упомянутой ранее.

Фернандо Гарсия
источник
1

Потому что 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 и результат примитивного типа.

user3462649
источник
1

Эти примитивные типы являются гораздо быстрее и требуют гораздо меньше памяти . Поэтому мы можем захотеть использовать их.

С другой стороны, текущая спецификация языка Java не позволяет использовать примитивные типы в параметризованных типах (обобщениях), в коллекциях Java или API Reflection.

Когда нашему приложению нужны коллекции с большим количеством элементов, мы должны рассмотреть возможность использования массивов с как можно более «экономичным» типом.

* Для получения подробной информации см. Источник: https://www.baeldung.com/java-primitives-vs-objects

Арун Джошла
источник
0

Чтобы быть кратким: примитивные типы быстрее и требуют меньше памяти, чем в штучной упаковке

Белая ссылка
источник