Effective Kotlin: Item 6 — Avoid creating unnecessary objects
Android’s performance tips briefly mention Avoid Creating Unnecessary Objects. However, item 6 of Joshua Bloch’s recently updated book, Effective Java, has far more practical examples I recommend you check out. The hardest to spot of these being autoboxing. How does it apply to Kotlin where the compiler decides for you whether, for example, an Int
turns into a primitive type, int
, or a reference type, Integer
.
Typically the compiler sticks to the advice of Effective Java preferring primitives to reference types. Lukas Lechner points out three exceptions:
Nullable types
If you use Nullable types such as Int?
, then this will always compile as Integer
as there’s no way for a primitive reference to hold null
. When developing with Kotlin ask yourself, “does this need to be null?”
Objects in collections
Collections typically allow nulls and use generics storing reference types only. To avoid auto-boxing on Android, you can use Sparse arrays such as SparseIntArray
which compared to HashMap<Integer, Intger>
uses significantly less memory too, 8072 bytes vs 64136 bytes for 1000 elements according to Romain Guy. However, although more memory efficient they are not as fast as HashMap
especially with a lot of elements.
Type arguments in generic classes
The issue with generics is the type argument must be a reference type. Usually, the use of generics will be apparent, but one hidden case is in the use of lambda functions. Consider the following code:
fun awesomeAlgorithm(factor: Int): (Int, Int) -> Int {
return { a, b -> a + b * factor }
}
This generates byte-code where the lambda’s input and output will be boxed just to be unboxed again, not what you would call efficient. Function2
is the interface used for functions with two input parameters, Kotlin defines these interfaces from 0 to 22.
@NotNull
public static final Function2 awesomeAlgorithm(final int factor) {
return new Function2() {
public Object invoke(Object var1, Object var2) {
return Integer.valueOf(this.invoke(
((Number) var1).intValue(),
((Number) var2).intValue()
));
}
public final int invoke(int a, int b) {
return a + b * factor;
}
};
}
Depending on how you are using lambdas the solution is relatively straightforward, define functions with lambda parameters as inline. Exploring Kotlin’s hidden costs — Part 1 demonstrates this in more detail.
However, you cannot merely inline the example given above. An alternative, albeit slightly more work, is to follow the guidance of Kotlin λ and auto-boxing of parameters and result and, for example, create your own explicit IntBinaryOperator.
With Kotlin don’t just be careful when explicitly creating objects but pay particular care when you are using lambdas as these are more likely to hide the already well-hidden auto-boxed types.
Each week I am looking at “items” from Joshua Bloch’s well-respected book, Effective Java to see how it applies to Kotlin. You can find the rest of the items I’ve covered at Effective Kotlin. Please let me know your thoughts.