Long, Integer, Double, Float, Short, Boolean, Character, Byte - это версии-оболочки примитивов - long, int, double, float, short, boolean, char, byte. Оболочки позволяют хранить значения в коллекциях, таких как списки, наборы, карты. Дополнительным преимуществом классов-оболочек перед примитивными является возможность выполнения метода над этим объектом.
Например, мы можем:
counter.toString();
or
counterB.compareTo(counterA);
например, на Длинный, целочисленный, двойной или плавающий объект.
Тип оболочки хранится в памяти в куче, а стек содержит ссылку на объект в куче. Примитивные значения располагаются только в стеке.
Арифметические операции над оберткой и примитивом
Давайте проанализируем это
public static void main(String[] args) { int repeats = 40000000; long time; time = System.currentTimeMillis(); long counterA = 0L; for (int i = 0; i < repeats; i++) { counterA = counterA + 4L; } System.out.println(counterA + " A: " + (System.currentTimeMillis() - time) + " ms"); time = System.currentTimeMillis(); Long counterB = 0L; for (int i = 0; i < repeats; i++) { counterB = counterB + 4L; } System.out.println(counterB + " B: " + (System.currentTimeMillis() - time) + " ms"); }
То же самое можно сказать и о примитивных длинных файлах и обертках типа Long.
Та же задача, но разница во времени обработки огромна. Лонги суммируются гораздо дольше:
Причина тому - ненужное размещение объектов. Каждое добавление создает новый объект Long. Есть много объектов, которые позже нужно будет убрать сборщиком мусора.
Типы оболочки для расчета не нужны.
Вам нужна оболочка или примитив - используйте правильный метод преобразования parseType / valueOf
Существует значительная разница между методами, которые анализируют строку до примитивного числа, например:
- Long.parseLong
- Integer.parseInt
- Double.parseDouble
- Float.parseFloat
и методы, возвращающие тип оболочки для этого числа:
- Long.valueOf
- Integer.valueOf
- Double.valueOf
- Float.valueOf
Очень часто valueOf используется для получения неправильного примитива.
Вы можете делать это даже неосознанно, что может быть дорогостоящим при использовании в часто выполняемом коде.
Я провел тест, который сравнивает время обработки Long.parseLong и Long.valueOf для разных чисел много раз - 20000 в тесте.
Это код:
public static void main(String[] args) { int repeats = Integer.parseInt(args[0]); List<String> numbers = new ArrayList<>(); for (int i = 0; i < 1000; i++) { numbers.add(String.valueOf(i)); } long time; time = System.currentTimeMillis(); long sumOfLongsA = 0L; for (int i = 0; i < repeats; i++) { for (String number : numbers) { sumOfLongsA += Long.parseLong(number); } } System.out.println(sumOfLongsA + " Parse: " + (System.currentTimeMillis() - time) + " ms"); time = System.currentTimeMillis(); long sumOfLongsB = 0L; for (int i = 0; i < repeats; i++) { for (String number : numbers) { sumOfLongsB += Long.valueOf(number); } } System.out.println(sumOfLongsB + " value: " + (System.currentTimeMillis() - time) + " ms"); }
Результат выглядит следующим образом:
Как вы видите, использование обертки типа long делает всю обработку на 80% медленнее.
Тот же тест был повторен для целых чисел, чисел с двойной точностью и чисел с плавающей запятой.
Как видите, когда дело доходит до десятичных чисел, разница между суммированием типа оболочки и примитивных чисел меньше, но все же типы оболочки все равно на 30% медленнее.
Распаковка и автобокс
Распаковка и автоматическая упаковка делают описанную выше проблему еще более скрытой.
public static void main(String[] args) { int repeats = Integer.parseInt(args[0]); List<String> numbers = new ArrayList<>(); for (int i = 0; i < 1000; i++) { numbers.add(String.valueOf(i)); } long time; time = System.currentTimeMillis(); float sumOfLongsB = 0; for (int i = 0; i < repeats; i++) { for (String number : numbers) { sumOfLongsB += calculateValueObject(number); } } System.out.println(sumOfLongsB + " value: " + (System.currentTimeMillis() - time) + " ms"); time = System.currentTimeMillis(); double sumOfLongsA = 0; for (int i = 0; i < repeats; i++) { for (String number : numbers) { sumOfLongsA += calculateValuePrimitive(number); } } System.out.println(sumOfLongsA + " Parse: " + (System.currentTimeMillis() - time) + " ms"); } private static float calculateValuePrimitive(String number) { final int baseCalculation = Integer.parseInt(number); return baseCalculation * 1.23f; } private static float calculateValueObject(String number) { final int baseCalculation = Integer.valueOf(number); return baseCalculation * 1.23f; }
Разберем тот же случай, но с расчетной методикой. Каждый метод расчета делает то же самое. Разница в том, что в calculateValueObject Integer. valueOf использовался вместо Integer. parseInt.
Как вы видите, дальнейшие вычисления выполняются с примитивом int, поэтому целое число-оболочка доступно только на мгновение в:
final int baseCalculation = Integer.valueOf(number);
где он распакован до примитивного типа int.
Эта ошибка может дорого обойтись:
Та же проблема может возникнуть, когда вы передаете примитив методу, которому требуется тип оболочки. Например.:
private static float calculateValuePrimitive(String number) { final int baseCalculation = Integer.parseInt(number); return calculateWithVatTax(baseCalculation); } private static float calculateWithVatTax(Integer baseCalculation) { return baseCalculation * 1.23f; } private static float calculateValueObject(String number) { final int baseCalculation = Integer.valueOf(number); return baseCalculation * 1.23f; }
Автобокс может существенно повлиять на производительность:
В этом случае автобокс стал на 35% медленнее.
Резюме
Не используйте типы обертки, если в этом нет необходимости! Типы оболочки имеют решающее значение, когда дело доходит до хранения данных. Только для вычислительных примитивов!