Preface
If you attended Google IO this year then you might remember a talk by Colt McAnlis about memory performance. Colt went over several common pitfalls, and eventually touched on enums, adamantly saying we shouldn't use them.
Enums have been a hot subject in the past for Android. You can find plenty of questions about it on StackOverflow, and Google actually recommends to "strictly avoid using enums on Android".
As a Java developer who uses lots of enums, this didn't set well with me and seemed a bit heavy-handed. This post is a shallow dive into how enums work in Java and Android. By the end, I'll let you come to your own verdict.
I highly recommend watching Chet Haase and Romain Guy talk about Android performance.
Update: This was a lot of fun to write, and seemed to generate good discussion. Colt McAnlis came out with an Android Performance video on the matter. I've updated this post with some new information in response.
What is an Enum?
If you're reading this then you've probably used an enum or two before, and are familiar with its syntax:
public enum Operators {
Add, Subtract, Multiply, Divide; // Horray for Pascal case!
}
We now have a simple Operator
enum. So what exactly does the compiler convert this to? You may remember hearing that an enum is actually a regular old Java class, and that's correct.
Using a tool called javap
we can see exactly what it turns into:
public final class Operator extends java.lang.Enum<Operator> {
public static final Operator Add;
public static final Operator Subtract;
public static final Operator Multiply;
public static final Operator Divide;
public static Operator[] values();
public static Operator valueOf(java.lang.String);
static {};
}
Interestingly our fields are just our enum type, meaning you could chain the values if you wanted to be evil:
public void pureEvil() {
Operator operator = Operator.Add.Subtract; // Sets to Subtract
}
Though your IDE should warn you about referencing a static field from an instance variable.
You can also see a static initializer block at the end of the class, which presumably goes through and sets all the static fields to their values.
The Dirty Details
There are two important ways to evaluate the performance of this enum, the memory footprint and amount of bytecode generated. The memory footprint can be calculated like so:
- 4 bytes per object reference
- A minimum of 16 bytes per object allocation (due to alignment)
In our simple Operators enum, you can see we have:
- 4 objects allocated (plus a reference) to the enum values themselves
- An Operators[] object
- A String
name
object per Operator - An int
ordinal
object per Operator - The enclosing Operator class
Using Android Studio's new heap dump analyzer, we can take a look at a live example.
Since everything is static the memory is consumed during class-loading.
Integer Constants
Say we compared this to using static final ints:
public class Operator {
public static final int ADD = 1;
public static final int SUBTRACT = 2;
public static final int MULTIPLY = 3;
public static final int DIVIDE = 4;
}
This implementation will be much more efficient because each integer takes only 4 bytes of memory to create, totaling 16 bytes.
Dex Bytecode
All code in your app is converted to Dex bytecode which is then executed by Dalvik (or ART).
Not surprisingly, both methods generate different amount of bytecode. Roughly speaking, more bytecode takes longer to run (in most cases). Creating my simple enum took 1222 bytes of bytecode, while the integer constants only took 126 bytes, a 10x difference in size.
Knowing that, if you need code to be very performant integer constants may be the way to go.
To measure the bytecode, I simply looked at the .class files generated.
Why Use Enums Then?
The performance analysis clearly seems to be in favor of integer constants, so why bother using enums? Well, to name a few reasons they:
- Are type-safe, so you can only use valid enum types
- Provide better code readability
- Can implement interfaces
- Support polymorphic behavior
The true power of enums comes when you take advantage of their polymorphic behavior. Take a look at the following code:
public double applyOperator(Operator operator, double first, double second) {
switch (operator) {
case Add:
return first + second;
case Subtract:
return first - second;
case Multiply:
return first * second;
case Divide:
return first / second;
default:
throw new IllegalStateException("Unknown operator: " + operator.name());
}
}
This is a basic function that performs an operation on two numbers. This switch statement isn't doing much, and could be directly replaced with integer constants. That's not how you should use enums, look at this equivalent:
public enum Operator {
Add {
@Override
public double applyOperator(double first, double second) {
return first + second;
}
},
Subtract {
@Override
public double applyOperator(double first, double second) {
return first - second;
}
},
Multiply {
@Override
public double applyOperator(double first, double second) {
return first * second;
}
},
Divide {
@Override
public double applyOperator(double first, double second) {
return first / second;
}
};
public abstract double applyOperator(double first, double second);
}
Now the applyOperator
function is an abstract method of our enum. The key benefit of this method is that it's more maintainable. If you want to add another operator, you'll be forced to implement all abstract methods. Alternatively, you'd have to hunt down all switch cases using your enum and update those, which is more prone to human error.
As a rule of thumb, enum related code should be encapsulated inside the enum if possible.
Proguard
In some cases, Proguard can optimize Enums into integer constants for you; a free win! You get the best of both worlds. The Proguard docs are very vague, so it might not work for non-trivial enums.
AOSP
With these benchmarks in mind, it definitely makes sense that the Android framework avoids enums. The framework wants to be as lean as possible, however, framework design doesn't translate to app design; your app is not a massive framework.
How Do I Choose?
I've given all the info you need to make a decision, but the decision is still yours to make.
Don't prematurely optimize your code. Test and measure — on the extreme ends of devices you support — if performance becomes a problem. To be fair, this is exactly the advice that Romain and Chet gave during their talk. Only the performance guide makes it seem like such a clear-cut case.
Now I'd like to interject my own opinion on the matter. The vast majority of apps should be using enums, ignore what the Android docs say. Unless you're working at Facebook's scale, the performance difference doesn't really matter. Instead, spend time making your code more readable and maintainable.
It's really not hard to refactor enums to integer constants if you've somehow determined they are affecting your app performance. I'm not sure why there's scaremongering around this (like the Gremlin example in the video). This would be something you could do in a day.
As a side note, you should also look into the Support Annotations library which adds some compile-time checks to integer constants.
Conclusion
This became a lot more wordy than I originally intended, but hopefully you enjoyed the read. I had a great time writing this, and would love to hear others' opinions. Also, please correct me if I was wrong on something!
tl;dr: integer constants are faster, enums are more readable/maintainable, performance difference rarely makes a difference.