Java wrapper types— tips to avoid performance pitfalls
Long, Integer, Double, Float, Short, Boolean, Character, Byte are wrapper versions of primitive ones — long, int, double, float, short, boolean, char, byte. Wrappers allow to keep values in the collections like lists, sets, maps. Additional advantage of wrapper classes over primitive ones is possibility of method execution on those object.
For example, we can do:
counter.toString();
or
counterB.compareTo(counterA);
on e.g. Long, Integer, Double or Float object.
Wrapper type is kept in memory on the heap and stack holds the reference to the object on the heap. Primitive values are located only on the stack.
Arithmetical operations on wrapper vs primitive
Let’s analyze this
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");
}
The same summing up is done on primitive longs and wrapper Longs.
Same task but the difference in processing time is huge. Longs are summed up much longer:
The reason for that is unnecessary allocation of objects. Each addition produces new Long object. There are lots of objects that has to be garbage collected later on.
Wrapper types are not needed for calculation.
Do you need wrapper or primitve — use proper conversion method parseType/valueOf
There is a significant difference between methods that parse string to the primitive number like:
- Long.parseLong
- Integer.parseInt
- Double.parseDouble
- Float.parseFloat
and methods that return wrapper type of that number:
- Long.valueOf
- Integer.valueOf
- Double.valueOf
- Float.valueOf
It is a very often practise to use valueOf for getting a primitive which is wrong.
You may even do this unconsciously which can be costly when used in code that is often run.
I have done a test that compares time of processing Long.parseLong and Long.valueOf for different numbers many times — 20000 in the test.
This is a code:
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");
}
Result looks as follows:
As you see getting wrapper type of long makes the whole processing 80% slower.
The same test was repeated for integers, doubles and floats.
As you see when it comes to decimal numbers there is smaller difference between summing up wrapper type and primitive numbers but still wrapper types are 30% slower.
Unboxing and autoboxing
Unboxing and autoboxing make the problem described above even more hidden.
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;
}
Let’s analyze the same case but with calculation methods. Each calculation method does the same. The difference is that in calculateValueObject Integer.valueOf was used instead of Integer.parseInt.
As you see further calculations are done on the primitive int so wrapper integer is only available for a moment in:
final int baseCalculation = Integer.valueOf(number);
where it is unboxed to primitive int.
That error can be costly:
The same issue can take place when you pass primive to the method that requires wrapper type. E.g.:
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;
}
The autoboxing can make a significant impact on the performance:
In this case autoboxing made it 35% slower.
Summary
Do not use wrapper types when you do not need to! Wrapper types are crucial when it comes to keeping data. For computational use primitives only!