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 objects. With methods, variables and all the things that we’ve got to love from OOP (we’ll ignore class inheritance on 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 a bit strange in general. This is one of the reasons why Enums are so powerful, they let you stablish a type that has a limit 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 pack 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 on usage in code where the mock typing remains handy.
Animal enum When declaring Enums we type something similar to this extend:
Referring to them, as the type of a parameter in a method or constructor, is done in a safe and strong type way:
Animal annotation Using support annotations, the declaration is similar with a few extra declarations:
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). So we’ll ignore that case here.
Static Animal noises To make a static map using Enums we can do something like this:
Doing this allows us to have a statically initiated map of values that looks a lot nicer than creating a
Map manually, it is 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 associate 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 in 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!”. I looks strange at once 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 with 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.
Final thoughts 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 more safe. 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. Specially 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 parceling 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 run time for the most part.