Коли користуватися примітивом vs класом на Java?


54

Я бачу, що у Java є булева (клас) проти булева (примітивна). Так само є Integer (class) vs int (примітивний). Яка найкраща практика щодо використання примітивної версії проти класу? Чи повинен я в основному завжди використовувати версію класу, якщо у мене немає конкретної причини (продуктивності?), Щоб не робити? Який найпоширеніший, прийнятий спосіб використання кожного?



На мій погляд, це не заняття, це бокси. Вони є лише там, щоб ви могли використовувати примітиви там, де потрібні предмети, тобто в колекціях. Ви не можете додати два цілих числа (ви можете зробити вигляд, але справді java автоматично боксує | розпаковує значення для вас).
каменеметал

Відповіді:


47

У пункті 5 "Ефективна Java" Джошуа Блох говорить

Урок зрозумілий: віддавайте перевагу примітивам боксуючим примітивам і стежте за ненавмисним автобоксингом .

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

Редагувати: Причина Джошуа Блоха:

// Hideously slow program! Can you spot the object creation?
public static void main(String[] args) {
    Long sum = 0L;
    for (long i = 0; i < Integer.MAX_VALUE; i++) {
         sum += i;
    }
    System.out.println(sum);
}

Ця програма отримує правильну відповідь, але вона набагато повільніше, ніж повинна бути, через типографічну помилку з одним символом. Змінна sumоголошується Longзамість a long, що означає, що програма створює близько 2 ^ 31 непотрібних Longекземплярів (приблизно один для кожного разу, коли long iдодається до Long sum). Зміна декларації суми з Longна longскорочує час виконання з 43 секунд до 6,8 секунди на моїй машині.


2
Це допоможе, якщо ви перерахуєте причини Блоха, а не просто цитуєте його висновок!
vaughandroid

@Baqueta Я редагував публікацію. Причина - продуктивність.
m3th0dman

Це трохи зрозуміліше, дякую. Я вважаю виправданим розміщувати власну відповідь зараз. :)
vaughandroid

Можливо, вам здасться цікавою архітектура lmax - "Щоб піднятися на інший порядок, було потрібно трохи більше кмітливості. Є кілька речей, які команда LMAX виявила корисними, щоб потрапити туди. Одне було написати спеціальні реалізації колекцій java, які були розроблені бути зручним у кеш-пам'яті та обережним зі сміттям. Прикладом цього є використання примітивних java longs як клавіш хешмапу із спеціально написаним масивом, виконаним із реалізацією Map ".

Це схоже на те, що JIT повинен впоратися
deFreitas

28

Стандартна практика полягає в тому, щоб спілкуватися з примітивами, якщо ви не маєте справу з дженериками (переконайтеся, що ви знаєте про автобоксинг та розпакування !).

Є низка вагомих причин для дотримання конвенції:

1. Ви уникаєте простих помилок:

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

Найпоширенішою помилкою є використання a == bзамість a.equals(b). Люди звикли робити a == bз примітивами, тому це легко зробити, коли ви використовуєте обгортки об'єктів.

Integer a = new Integer(2);
Integer b = new Integer(2);
if (a == b) { // Should be a.equals(b)
    // This never gets executed.
}
Integer c = Integer.valueOf(2);
Integer d = Integer.valueOf(2);
if (c == d) { // Should be a.equals(b), but happens to work with these particular values!
    // This will get executed
}
Integer e = 1000;
Integer f = 1000;
if (e == f) { // Should be a.equals(b)
    // Whether this gets executed depends on which compiler you use!
}

2. Читання:

Розглянемо наступні два приклади. Більшість людей сказали б, що друге легше читати.

Integer a = 2;
Integer b = 2;
if (!a.equals(b)) {
    // ...
}
int c = 2;
int d = 2;
if (c != d) {
    // ...
}

3. Продуктивність:

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

Цитата "... говорять про 97% часу: передчасна оптимізація - корінь усього зла", цитата тут насправді не стосується. Він говорив про оптимізації, які ускладнюють код (або систему) - якщо ви згодні з пунктом №2, це оптимізація, яка робить код менш складним!

4. Це конвенція:

Якщо ви робите різні стилістичні рішення для 99% інших програмістів Java, є два мінуси:

  • Ви знайдете код інших людей важче для читання. 99% прикладів / навчальних посібників / тощо там будуть використовувати примітиви. Кожного разу, коли ви читаєте його, у вас з'являться додаткові пізнавальні витрати, як думати про те, як це виглядатиме у стилі, до якого ви звикли.
  • Інші люди будуть важче читати ваш код. Щоразу, коли ви задаєте питання щодо переповнення стека, вам доведеться просіювати відповіді / коментарі, запитуючи "чому ви не використовуєте примітиви?". Якщо ви не вірите мені, просто подивіться на бійки людей за такі речі, як розміщення дужок, що навіть не впливає на створений код!

Зазвичай я перелічу деякі зустрічні моменти, але я, чесно кажучи, не можу придумати жодних вагомих причин, щоб не піти з конвенцією тут!


2
Не пропонуйте людям порівнювати предмети ==. Об'єкти слід порівнювати з equals().
Тулен Кордова

2
@ user61852 Я не пропонував це як щось робити, а як звичайну помилку, яку роблять! Чи варто зробити це зрозумілішим?
vaughandroid

Так, ви не згадуєте, що об'єкти слід порівнювати з equals()... Ви даєте їм вирішення, щоб порівняння об'єктів з ==результатами очікуваних результатів.
Тулен Кордова

Влучне зауваження. Змінив це.
vaughandroid

Я додав equals()до другого фрагмента коду і змінив свій голос.
Тулен Кордова

12

Зазвичай я йду з примітивами. Однак однією особливістю використання класів як Integerі Booleanє можливість присвоєння nullцих змінних. Звичайно, це означає, що вам потрібно постійно nullперевіряти, але все ж краще отримати NullPointerException, ніж мати логічні помилки через використання деяких intабо booleanзмінних, які не були ініціалізовані правильно.

Звичайно, оскільки з Java 8 ви можете (і, мабуть, повинні) піти на крок далі, а замість, наприклад, Integerви можете використовувати Optional<Integer>для змінних, які можуть мати або не мати значення.

Крім того, він запроваджує можливість використовувати nullдля присвоєння цим змінним значення " невідоме " або " підстановочне ". Це може бути корисно в деяких ситуаціях, наприклад, у Ternary Logic . Або ви можете перевірити, чи відповідає певний об'єкт певному шаблону; у цьому випадку ви можете використовувати nullдля тих змінних у шаблоні, які можуть мати будь-яке значення в об'єкті.


2
(Не було моїм зворотним моментом, але ...) Java вже виявляє неініціалізовані змінні, і не дасть вам прочитати змінну до кожного кодового шляху, що веде до її використання, остаточно призначив значення. Таким чином, ви не отримуєте багато від можливості призначити nullза замовчуванням. Навпаки: вам краще взагалі не "ініціалізувати" змінну. Встановлення будь-якого значення за замовчуванням, навіть null, вимикає компілятор ..., але також запобігає виявленню відсутності корисного призначення по всіх кодових шляхах. Таким чином, помилка, яку компілятор міг виявити, прослизає до виконання.
cHao

@cHao Але що робити , якщо немає розумного значення по замовчуванням для ініціалізації змінної з? Ви можете встановити це 0.0чи -1, або Integer.MAX_VALUE, або False, але, врешті-решт, ви не знаєте, чи це значення за замовчуванням, чи фактичне значення, яке було призначено цій змінній. У випадках, коли це важливо, мати nullзначення може бути зрозумілішим.
tobias_k

Це не зрозуміліше, просто простіше сказати компілятору не попереджати про незрозумілу логіку, поки помилка вже не поширилася. : P У випадках, коли немає розумного за замовчуванням, не ініціалізуйте змінну. Встановіть його лише після того, як у вас з’явиться розумне значення для розміщення туди. Це дозволяє Java зупинити вас під час компіляції, якщо ваше значення може бути встановлено неправильно.
cHao

@cHao Я мав на увазі, можуть бути випадки, коли ви не можете ініціалізувати змінну, і вам доведеться мати справу з нею під час виконання. У таких випадках "типовий" на зразок "null", який чітко можна визначити як за замовчуванням, або "неініціалізований", може бути кращим за будь-яку ініціалізацію часу компіляції, яка також може бути дійсним значенням.
tobias_k

Чи є у вас такий випадок на увазі? Випадки, про які я можу подумати, знаходяться на межі інтерфейсу (наприклад: як параметри або типи повернення) ... але навіть там вони будуть набагато кращими, якщо їх завернути. (Голі nullстикаються з цілою низкою проблем, включаючи нульову параноїю.) У межах функції змінна, яка фактично може бути неініціалізована в точці використання, як правило, вказує на непокриті випадки. (Аналіз визначених завдань спрощений, тому можливі помилкові позитиви. Але часто можна вирішити їх, спростивши логіку, так.)
cHao

2

Словами мирянина:

Ви використовуєте обгортки, коли потрібно додавати речі до колекцій.

Колекції не можуть містити примітивів.


0

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

Як правило, ви повинні намагатися використовувати нативні типи даних, коли це можливо.

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