Declaring constants this way is easier because it can be added to existing class and it will occupy less space compared to Enum, but if you declare your constants this way you won’t get any type safety, for example :

You can see that we can pass any int into setComplaintReason(int) and the compiler won’t give us any warning because it’s correct for now, but it can also lead us to unexpected behaviour when the passed value is incorrect like this :

The code above will compile, but there will be a potential bug because we are not expecting that value to be passed into our function (need to check bad value) and also there are nothing preventing a collision of constants, we can made a mistake and set two constants with same value.

Enum

Declaring constants using Enum provide us type-safety and a lot of flexibility.
Let’s address the type-safety problem we have above, with enum our code will become like this :

So with changing our plain int to ComplaintReason, now compiler can know if we pass wrong value to setComplaintReason(ComplaintReason) which will be better because the error will occurs at compile time rather than runtime. With enum our problem with collision of constants also won’t occur because Enum values are unique.

We also get a lot of flexibilty with enum because we can define our own function inside enum and we can do method overloading too.

As always, with every advantages there will always disadvantages, enums often require more memory compared to simple final static variable constants, but it doesn’t mean we shouldn’t use enum at all.

@IntDef and @StringDef

Google introduced @IntDef and @StringDef into their Android Support Annotation, because they want provide another way to define constants that use small memory while maintaining type-safety too.
Let’s take a look at our code with @IntDef :

You can see that the code is pretty similar with the first example with final static variable constants, but now we have @ComplaintReason in front of our variable and setter’s parameter. With this we can get type-safety that we don’t have before, we can’t pass incorrect value to setComplaintReason(@ComplaintReason int complaintReason) anymore because the compiler will check if is the value passed is the value included inside @IntDef or not.
So now this code won’t compile :

public func foo() {
ComplaintReport complaintReport = new ComplaintReport();
complaintReport.setComplaintReason(3); // Will give you an error that told you to use one of the value inside @IntDef
}

Using @IntDef will also have same memory size as final static variable constants because everything is done in compile time, as you can see we define it with @Retention(RetentionPolicy.SOURCE) which means that the annotated type will be discarded by the compiler, you can take a look at other RetentionPolicy if you are interested in it.

Ending Words

Like I said before every choices have their own advantages and disadvantages, usually I choose between Enum or @IntDef/@StringDef based on the use-case.
For example if I have just want my constants to be simple values, I will use @IntDef/@StringDef. If I want it to be more complex, I’ll use Enums even it consumes more memory it provide flexibility like I explained above.

Thanks for reading and if you have any question you can write comment below or reach me via twitter @niko_yuwono.