Когда использовать примитив против класса в Java?

54

Я вижу, что Java имеет логическое (класс) против логического (примитив). Аналогично, есть Integer (класс) против int (примитив). Как лучше всего использовать примитивную версию против класса? Должен ли я в основном всегда использовать версию класса, если у меня нет конкретной причины (производительности?), Чтобы не делать этого? Какой самый распространенный и приемлемый способ использования каждого?

Кейси Паттон
источник
На мой взгляд, это не классы, а коробки. Они есть только там, поэтому вы можете использовать примитивы там, где требуются объекты, то есть в коллекциях. Вы не можете добавить два целых числа (вы можете притворяться, но на самом деле java - это автоматический бокс | распаковка значений для вас).
каменная металлургия

Ответы:

47

В пункте 5 «Эффективной Явы» Джошуа Блох говорит

Урок ясен: предпочитайте примитивы коробочным примитивам и следите за непреднамеренным автобоксом .

Одно хорошее применение для классов - это когда они используются как универсальные типы (включая классы Collection, такие как списки и карты) или когда вы хотите преобразовать их в другой тип без неявного приведения (например, 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объявляется как a Longвместо a long, что означает, что программа создает около 2 ^ 31 ненужных Longэкземпляров (примерно по одному на каждый раз, когда объект long iдобавляется в Long sum). Изменение объявления суммы с Longна longсокращает время выполнения с 43 секунд до 6,8 секунд на моей машине.

m3th0dman
источник
2
Было бы полезно, если бы вы перечислили причины Блоха, а не просто цитировали его заключение!
vaughandroid
@Baqueta Я редактировал пост. Причина в производительности.
m3th0dman
Это немного понятнее, спасибо. Я чувствую себя оправданным, отправляя свой собственный ответ прямо сейчас. :)
vaughandroid
Вам может показаться интересной архитектура lmax: «Чтобы подняться на другую ступеньку, понадобилось немного больше ума. Есть несколько вещей, которые команда LMAX сочла полезными для этого. Одна из них заключалась в написании пользовательских реализаций java-коллекций, которые были разработаны быть дружественным к кешу и быть осторожным с мусором. Примером этого является использование примитивных java long в качестве ключей hashmap со специально написанной реализацией 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% примеров / учебников / и т. Д. Там будут использовать примитивы. Всякий раз, когда вы читаете один, у вас будут дополнительные когнитивные мысли о том, как это будет выглядеть в том стиле, к которому вы привыкли.
  • Другим людям будет сложнее читать ваш код. Всякий раз, когда вы задаете вопросы о переполнении стека, вам придется просеивать ответы / комментарии, спрашивая: «Почему вы не используете примитивы?». Если вы мне не верите, просто посмотрите на битвы, которые люди ведут за такие вещи, как размещение скобок, что даже не влияет на сгенерированный код!

Обычно я бы перечислил некоторые контрапункты, но я, честно говоря, не могу придумать каких-либо веских причин не соглашаться здесь!

vaughandroid
источник
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те переменные в шаблоне, которые могут иметь любое значение в объекте.

tobias_k
источник
2
(Не было моего отрицательного голоса, но ...) Java уже обнаруживает неинициализированные переменные и не позволит вам читать переменную, пока каждый путь кода, ведущий к ее использованию, окончательно присвоил значение. Таким образом, вы не получаете много от возможности назначить nullпо умолчанию. Напротив: вам лучше не «инициализировать» переменную вообще. Установка любого значения по умолчанию даже nullзакрывает компилятор ... но также не позволяет обнаружить отсутствие полезного назначения по всем путям кода. Таким образом, ошибка, которую мог поймать компилятор, ускользает во время выполнения.
cHao
@cHao Но что, если нет разумного значения по умолчанию для инициализации переменной? Вы можете установить его на 0.0, или -1, или Integer.MAX_VALUE, или False, но, в конце концов, вы не знаете, является ли это значением по умолчанию или фактическим значением, которое было присвоено этой переменной. В тех случаях, когда это важно, nullзначение может быть более понятным.
tobias_k
Это не ясно, просто проще сказать компилятору не предупреждать вас о неясной логике, пока ошибка не будет распространена. : P В тех случаях, когда нет разумного значения по умолчанию, не инициализируйте переменную. Установите его только тогда, когда у вас есть разумное значение для этого. Это позволяет Java останавливать вас во время компиляции, если ваше значение может быть установлено неправильно.
Чао
@cHao Я имел в виду, что могут быть случаи, когда вы не можете инициализировать переменную и вам приходится иметь дело с ней во время выполнения. В таких случаях «по умолчанию», например, «ноль», который может быть четко определен как «по умолчанию» или «неинициализирован», может быть лучше, чем любая инициализация во время компиляции, которая также может быть допустимым значением.
tobias_k
Вы имеете в виду такой случай? Случаи, о которых я могу думать, находятся на границах интерфейса (например, в качестве параметров или возвращаемых типов) ... но даже там, они были бы намного лучше, если бы были обернуты. (Обнаженные nullприходят с целым рядом проблем, включая нулевую паранойю.) Внутри функции переменная, которая может быть фактически неинициализирована в момент использования, обычно указывает на раскрытые случаи. (Определенный анализ назначений является упрощенным, поэтому возможны ложные срабатывания. Но вы часто можете их разрешить, упрощая логику, поэтому.)
cHao
2

По словам непрофессионала:

Вы используете обертки, когда вам нужно добавить вещи в коллекции.

Коллекции не могут содержать примитивы.

Тулаинс Кордова
источник
0

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

Как правило, вы должны стараться использовать собственные типы данных, когда это возможно.

Silvarion
источник