A common discussion in the Android community is Enums and whether to use them. As Colt McAnlis rightly points out on a video from his series Android Performance Patterns, Enums can become a problem when misused. It’s also mentioned by Chet Haase on Developing for Android: The Rules.
I want to say, first of all, that Enums are amazing. One of the great features introduced in Java 1.5 back in 2004. Some people doesn’t know that Enums are more than just an enumeration of values. They are special static classes with constant instances. With methods, variables and all the things that we’ve got to love from OOP (we’ll ignore class inheritance in this case). I’ll go into more details about this later on.
However, because of Enums are objects they can also be expensive in terms of memory and speed if compared with other alternatives like constant ints. That said, using an int or a string to represent an entity within a set of finite possibilities can be a bit of an anti-pattern in OOP and generally a tad strange. This is one of the reasons why Enums are so powerful, they let you establish a type that has a limited number of possible values unlike using a natural number or a string.
The Android team knows how useful strong typing can be and they added a feature on the build pipeline which allows the compiler to obtain type safety on plain constants. This is done using the support annotations library, which can be added as a Gradle dependency:
Among other useful utilities, this library provides us with the annotations
@StringDef. These provide the means to define a set of constants so that Lint can detect if we are using the wrong entry. If we use the wrong values when calling a method the compiler will fail. This, however, doesn’t provide runtime safety. However, we are interested in the usage in code where the mock typing remains handy.
When declaring Enums we type something similar to this:
Referring to them, as the type of a parameter in a method or constructor, is done in a safe and strongly typed manner:
Using support annotations, the declaration is similar with a few extra boilerplate:
Instead of using the original Enum we can mark the given parameter with the annotation we created to limit the values to be provided:
It does require a bit of extra code and there is a possibility of forgetting to update the
@IntDef configuration on the marker annotation. However, it makes it more memory and parcelable friendly. That said, as I’ve mentioned before on StackOverflow, Enums are already parcelable friendly-ish since they are Serialisable.
But Enums are more than a plain list of values
Enums can be used to provide a static map of values or to create thread safe singleton or “multiton” instances.
Using Enums with a single entry to create a singleton is probably one of the use cases that has no better alternatives (Other than doing the thread safety yourself).
Static Animal noises
To make a static map using Enums we can do something like this:
Doing this allows us to have a statically instantiated map of values that looks a lot nicer than creating a
Map manually, it’s thread-safe and type bound. A more practical example are URI paths. Not only you could access the URI for a given endpoint, but can potentially inject the domain you want to use.
Animal noises If we want to use annotations we have to ways to get a similar result. The easier one that applies to the Animal class is to use @StringDef instead of @IntDef. However, such example is too simple. If we have something more complex this will not work.
If we are going for the annotation approach we can use a SparseArray to provide the same data as with the enum:
This is a simple map, but sometimes we want something more complex. For instance, in Effective Java, Joshua Bloch has a chapter on this: Item 30: Use Enums instead of int constants. The main reason, as I understand it, is to avoid associating data and functionality related to each of the entries to get too far away from them.
Noisy Animal Multiton
By keeping the related data and functionality close to the Enum entries we ensure that any new entries provide with the same behaviour as the other. Continuing with the animal sounds example, we can provide a method for the Enum entry to do the work of making the sound in something we’ll call a Speaker:
At first, you may think, “Whoa! hold on, that’s horrible!”. It may look strange but once you get used to it, you can see how it makes sense. Each type has an implementation, in the same way, that you would do with any creational pattern, but static. This enforces anyone extending the options to also provide the logic needed to make the animal’s sound.
Like not everything is a nail when you have a hammer, this is not a solution for every problem. This applies to things that rarely change, like the endpoints of your networking layer or the number of tabs in a view. Is important that when using this pattern no dangerous references (e.g. fragments) are kept.
More Sparse Animals
Now, to provide the same behaviour in a light-weight way we could do something like this:
One of the problems with using a SparseArray to provide implementation mapping (or any other map for that matter) is that it provides with the option to forget to update new entries in the generation of the mapping.
All that I’ve talked about can easily and rightfully considered early optimisation since there are situations where not only Enums are ok but they can actually be safer. This is just another tool available. There is no silver bullet.
One of the instances where I would strongly recommend to use Support Annotations instead of Enums is as part of model entities. Especially if they are parcelled as Serialisable on rotation or as part of an Intent. If you have a few dozen of objects (or more, but hope not) this could slow down the parcelling process. That said there are other ways to serialise an Enum as I mention on SO.
In the use case mentioned about URLs and endpoints, enums can be more useful. They provide a safe and concise way to define a set of data that will not change at runtime for the most part.