Чому люди все ще використовують примітивні типи на Java?


163

Починаючи з Java 5, у нас був бокс / розпакування примітивних типів, таким чином, intщоб бути завершеним java.lang.Integer, і так, і так далі.

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

Чому деякі все ще використовують примітивні типи на Java? Чи є якась відчутна користь?


49
коли-небудь думали про споживання пам’яті та продуктивність?
Теділ

76
Я додав тег автобоксингу ... і з’ясував, що насправді його слідують три людини. Дійсно? Люди дотримуються тегу AUTOBOXING?
corsiKa

4
@glowcoder Вони не справжні люди, вони просто абстрактні поняття, які передбачають людську форму для відповіді на ТАК. :)
biziclop

9
@TK Kocheran Переважно тому, що new IntegeR(5) == new Integer(5)слід, за правилами, оцінювати як хибне.
biziclop

10
Див. Колекції GNU Trove або Mahout або HPPC або ... для вирішення колекцій примітивних типів. Ті з нас, хто дбає про швидкість, проводять наш час, використовуючи більш примітивні типи, не менше.
бармагулії

Відповіді:


395

В пункті 5 ефективного Java Джошуа Блоха : "Уникайте створення зайвих об'єктів", він розміщує такий приклад коду:

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 секунди. Якщо взяти Довгий у примітив, він зводить його до 6,8 секунди ... Якщо це є ознакою, чому ми використовуємо примітиви.

Відсутність рідного значення рівності також викликає заклопотаність ( .equals()досить багатослівний по порівнянні з ==)

для biziclop:

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

EDIT Чому (3) повертається trueі (4) повертається false?

Тому що це два різні об’єкти. 256 цілих чисел, найближчих до нуля [-128; 127] кешовані JVM, тому вони повертають той самий об’єкт для них. Поза межами цього діапазону вони не кешовані, тому створюється новий об'єкт. Щоб ускладнити справи, JLS вимагає кешувати щонайменше 256 маховиків. Реалізатори JVM можуть додати більше, якщо вони захочуть, тобто це може працювати в системі, де найближчі 1024 кешуються, і всі вони повертають справжнє ... #awkward


54
А тепер уявіть, як би iїх також оголосили Long!
ColinD

14
@TREE - специфіка фактично вимагає від машин візуальних машин для створення махових ваг у певному діапазоні. Але, на жаль, це дозволяє їм розширити цей діапазон, тобто програми можуть поводитися по-різному на різних віртуальних машинах. Стільки для крос-платформи ...
Даніель Ервікер

12
Java знизилася, все більше і більше поганих варіантів дизайну. Автобоксинг - це повний збій, він не є надійним, передбачуваним або портативним. Мені справді цікаво, що вони думали ... замість того, щоб виправити жахливу первісність-об'єктну подвійність, їм вдалося зробити це гірше, ніж в першу чергу.
Поп Каталін

34
@Catalin Я не згоден з вами, що автобоксинг - це повна невдача. Він має деякі недоліки, які нічим не відрізняються від будь-якого іншого дизайну, який міг би бути використаний (включаючи нічого). Вони чітко дають зрозуміти, чого можна, а чого не можна чекати, і, як і будь-який інший дизайн, вони очікують, що розробники будуть знати і виконувати договори. цих конструкцій.
corsiKa

9
@NaftuliTzviKay Це не "провал". Вони роблять ДУЖЕ ЧИСТИМИ, що ==оператор виконує порівняння ідентичності посилань на Integerвирази та порівняння рівності значень на intвиразах. Integer.equals()існує саме з цієї причини. Ніколи не слід використовувати ==для порівняння значень будь-якого непомітного типу. Це Java 101.
NullUserException

86

Автобокс може спричинити за собою важко помітити NPE

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

У більшості ситуацій нульове призначення inнабагато менш очевидно, ніж вище.


43

Типи в ящику мають нижчу продуктивність і потребують більше пам’яті.


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!

7
Це було б порушено, так. Однак видалити (int) є вадою дизайну IMO. Імена методів ніколи не слід перевантажувати, якщо є найменші шанси на змішання.
MrBackend

4
@MrBackend Ярмарок досить. Цікаво, що Vectorмали removeElementAt(int)з самого початку. remove(int)був представлений разом із рамками колекцій на Java 1.2.
xehpuk

6
@MrBackend: коли Listбув розроблений API, не існувало ні дженериків, ні автобоксингу, тому шансів на змішання не було remove(int)і remove(Object)
Holger

@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: (Не обов'язково потрібно розподіл пам'яті; він також може повернути кешоване значення. Я думаю, що це стосується деяких невеликих значень.) Моя думка полягала в тому, що незмінність Integers тут не є вирішальним моментом, ви все одно хочете мати інший об’єкт, незалежно від незмінності.
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++і ефективно "збільшувати змінну i", не Integerзмінюючи її.
Даніель Ервікер

16

Перш за все, звичка. Якщо ви кодували в Java протягом восьми років, ви накопичуєте значну кількість інерційності. Навіщо змінюватися, якщо для цього немає вагомих причин? Це не так, якби використання приміщень з коробкою приносить додаткові переваги.

Інша причина - стверджувати, що nullце неправдивий варіант. Було б безглуздим і оманливим оголошувати суму двох чисел або змінну циклу як Integer.

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


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

7
@Eddie Це може бути, але це дуже рідко. Повірте, для більшості людей аргументи ефективності - лише привід.
biziclop

3
Я теж хотів би захистити аргумент ефективності. На Android з Dalvik кожен створений вами об’єкт збільшує "ризик" виклику GC, і чим більше об'єктів у вас паузи будуть довші. Тож створення Integers замість int у циклі, ймовірно, коштуватиме вам декількох скинутих кадрів.
Ігор Чордаш

1
@PSIXO Справедливий момент, я писав це з чисто серверною Java на увазі. Мобільні пристрої - це зовсім інша тварина. Але моя думка полягала в тому, що навіть розробники, які в іншому випадку пишуть жахливий код, не враховуючи продуктивність, посилаються на це як на причину, від них це дуже сильно звучить як привід.
бізиклоп

12

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

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


Я мав би сказати "деякі реалізації", оскільки це не регулюється мовними специфікаціями. (І, на жаль, я не можу навести тут жодних джерел, це лише з того, що я десь почув.)
Paŭlo Ebermann

Так, JIT вже зберігає мета вказівника; в т. ч. вказівник може зберігати інформацію про GC або Klass (оптимізація класу набагато краща ідея, ніж оптимізація Integers, про яку я можу менше піклуватися). Зміна вказівника потребує зміщення коду shift / cmp / jnz (або щось подібне) перед кожним завантаженням вказівника. Мабуть, гілка не буде дуже добре передбачити апаратне забезпечення (оскільки це може бути як Тип Значення, так і звичайний Об'єкт), і це призведе до досягнення продуктивності.
bestsss

3
Я робив Smalltalk кілька років. Оптимізація все-таки була досить дорогою, оскільки для кожної операції на int їх потрібно було розкрити і повторно застосувати. В даний час java знаходиться нарівні з C при маніпулюванні примітивними числами. Якщо маскувати + маску, швидше за все, це буде> на 30% повільніше.
Р.Моллер

9

Я не можу повірити, що ніхто не згадав, що, на мою думку, є найважливішою причиною: "int" - це так, що це набагато простіше, ніж "Integer". Я думаю, що люди недооцінюють важливість стислого синтаксису. Продуктивність насправді не є причиною їх уникнути, оскільки більшість часу, коли використовується цифра, знаходиться в індексах циклу, а збільшення та порівняння цих витрат нічого не коштує в будь-якому нетривіальному циклі (будь-то ви використовуєте int чи Integer).

Іншою причиною було те, що ви можете отримати NPE, але це надзвичайно просто уникнути за допомогою типів в коробці (і це гарантується уникнути, якщо ви завжди ініціалізуєте їх до ненульових значень).

Інша причина полягала в тому, що (new Long (1000)) == (new Long (1000)) є помилковим, але це лише інший спосіб сказати, що ".equals" не має синтаксичної підтримки для типів у вікні (на відміну від операторів <,> , = і т. д.), тому ми повертаємося до причини "простішого синтаксису".

Я думаю, що примітивний цикл Стіва Йегге дуже добре ілюструє мою думку: http://sites.google.com/site/steveyegge2/language-trickery-and-ejb

Подумайте над цим: як часто ви використовуєте типи функцій на мовах, які мають гарний синтаксис для них (як будь-яка функціональна мова, python, ruby ​​і навіть C) порівняно з java, де вам доведеться імітувати їх за допомогою інтерфейсів, таких як Runnable та Callable та безіменні заняття.


8

Пара причин не позбутися примітивів:

  • Зворотна сумісність

Якщо його усунути, будь-які старі програми навіть не запускаються.

  • Перепишіть JVM.

Весь JVM повинен бути переписаний для підтримки цієї нової речі.

  • Більший слід пам’яті.

Вам потрібно буде зберегти значення та посилання, що використовує більше пам’яті. Якщо у вас є величезний масив байтів, використання byte's значно менше, ніж використання Byte' s.

  • Проблеми з нульовим покажчиком.

Декларація int iтого, що робити щось із цим, iне призведе до жодних проблем, але декларація Integer iі те, що робити те саме, призведе до NPE.

  • Питання рівності.

Розглянемо цей код:

Integer i1 = 5;
Integer i2 = 5;

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

Було б помилковим. Операторам довелося б перевантажуватись, і це призведе до значного переписування матеріалів.

  • Повільно

Обгортки об'єктів значно повільніше, ніж їх примітивні аналоги.


i1 == i2; було б помилковим, лише якщо i1> = 128. Отже, поточний приклад невірний
Геній

7

Об'єкти набагато важкіші, ніж примітивні типи, тому примітивні типи значно ефективніші, ніж екземпляри класів із обгортки.

Примітивні типи дуже прості: наприклад, int становить 32 біти і займає в пам'яті рівно 32 біти, і ними можна керувати безпосередньо. Об'єкт Integer - це повний об'єкт, який (як і будь-який об'єкт) повинен зберігатися в купі, і отримати доступ до нього можна лише за допомогою посилання (вказівника) на нього. Він, швидше за все, також займає більше 32 біт (4 байти) пам'яті.

Це говорить про те, що Java відрізняє примітивні та непримітивні типи - це також ознака віку мови програмування Java. Нові мови програмування не мають цього розрізнення; компілятор такої мови досить розумний, щоб зрозуміти сам, чи використовуєте ви прості значення або більш складні об'єкти.

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


1
Я міг би припустити, що JRE буде досить "розумним", щоб це зробити і з Java-загорнутими примітивами. Збій.
Naftuli Kay

7

Окрім того, що сказали інші, примітивні локальні змінні не виділяються з купи, а замість цього у стеці. Але об’єкти виділяються з купи і таким чином треба збирати сміття.


3
Вибачте, це неправильно. Розумний JVM може зробити аналіз втечі на будь-яких виділеннях об'єктів і, якщо вони не можуть вийти, виділити їх у стеку.
rlibby

2
Так, це починає бути особливістю сучасних СП. Через п'ять років те, що ви говорите, буде справедливим для більшості JVM, які тоді використовуються. Сьогодні це не так. Я майже прокоментував це, але вирішив не коментувати це. Можливо, я повинен був щось сказати.
Едді

6

Первісні типи мають багато переваг:

  • Простіший код для запису
  • Продуктивність краща, оскільки ви не інстанціюєте об'єкт для змінної
  • Оскільки вони не являють собою посилання на об'єкт, немає необхідності перевіряти нулі
  • Використовуйте примітивні типи, якщо вам не потрібно використовувати переваги боксу.

5

Важко дізнатися, які оптимізації відбуваються під обкладинками.

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

Однак масиви примітивів, мабуть, сильно відрізняються від колекцій приміщень, що містять коробки. Це має сенс, враховуючи, що дуже мало оптимізацій можливо в межах колекції.

Крім того, Integerмає набагато вищі логічні витрати в порівнянні з int: тепер ви повинні турбуватися про те, int 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" разів довгий: 468

Мільсекунд, витрачений на цикл "100000000" разів довгий час: 31

Зі сторони, я не заперечував би, щоб щось подібне знайшов це шлях до Java.

Integer loop1 = new Integer(0);
for (loop1.lessThan(1000)) {
   ...
}

Якщо цикл for цикл автоматично збільшує цикл1 від 0 до 1000 або

Integer loop1 = new Integer(1000);
for (loop1.greaterThan(0)) {
   ...
}

Де цикл for автоматично автоматично зменшує цикл1 1000 до 0.


2
  1. Вам потрібні примітиви для виконання математичних операцій
  2. Примітиви займають менше пам’яті, як відповіли вище, і ефективніше

Вам слід запитати, чому потрібен тип класу / об’єкта

Причина виникнення типу об’єкта - це полегшити наше життя, коли ми маємо справу з колекціями. Примітиви не можна додавати безпосередньо до списку / карти, а вам потрібно написати клас обгортки. Готовий тип класів Readymade Integer допомагає вам тут, плюс він має багато корисних методів, таких як Integer.pareseInt (str)


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


1

Ці примітивні типи є набагато швидше і вимагають набагато менше пам'яті . Тому ми можемо бажати використовувати їх.

З іншого боку, поточна специфікація мови Java не дозволяє використовувати примітивні типи в параметризованих типах (generics), у колекціях Java або API Reflection.

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

* Детальну інформацію див. У джерелі: https://www.baeldung.com/java-primitive-vs-objects


0

Якщо коротко: примітивні типи швидші і вимагають менше пам’яті, ніж коробки

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.