As you can see, Java Enums are powerful and flexible. There are so many use cases, and they extend into a lot of different domains. They have abilities that are unique (not just within Java, but amongst all non-JVM languages).
That said, everything that is good about enums comes because we make sacrifices along the way. We dont just get this stuff for free – there is a cost. There always is a cost.
Enums, a powerful tool in the Java developer’s arsenal, provide a structured and efficient way to represent a fixed set of constants. This guide delves into the intricacies of enums, equipping you with the knowledge to ace your next Java enum interview
What are Enums?
Enums, introduced in Java 1.5, are special types that encapsulate a collection of named constants. These constants are implicitly static and final, ensuring their immutability. Enums offer a plethora of benefits, including:
- Type Safety: Enums eliminate the risk of assigning invalid values to variables, promoting code reliability.
- Readability: Enums enhance code clarity by replacing cryptic integer constants with meaningful names.
- Switch Case Compatibility: Enums seamlessly integrate with switch statements, simplifying conditional logic.
- Serializable and Comparable: Enums inherently implement the Serializable and Comparable interfaces, streamlining data serialization and comparison tasks.
Demystifying Enum Interview Questions
This section tackles common enum interview questions, providing insightful explanations and practical examples.
1. Why Use Enums?
Enums excel in scenarios where a predefined set of constants is required. They offer type safety readability, and integration with switch statements, making them ideal for representing concepts like days of the week, colors or currency denominations.
2. Can Enum Constants Be Static and Final?
Absolutely! Enum constants are implicitly static and final, guaranteeing their immutability. This means you cannot modify their values once declared.
3. Invoking Enum Constructors
Yes you can invoke an enum constructor using the enum name. For instance, consider the following enum
enum Color { RED, GREEN}Color() { System.out.println("This is the constructor");}
Invoking Color.RED
will trigger the constructor, printing “This is the constructor” to the console.
4. Extending Enums
Enums cannot be extended. They are defined using the enum
keyword and populated with elements, but these elements cannot be further augmented in the code.
5. Creating Enum Objects
Creating objects of enums is not possible. Enums are designed to represent a fixed set of constants, and creating instances would contradict this principle.
6. Advantages of Enums
Enums offer a multitude of advantages:
- Type Safety: Enums prevent assigning invalid values to variables, enhancing code reliability.
- Readability: Enums improve code clarity by replacing cryptic integer constants with meaningful names.
- Switch Case Compatibility: Enums integrate seamlessly with switch statements, simplifying conditional logic.
- No Class Extension: Enums cannot extend other classes, ensuring their focused purpose.
- No Object Creation: Enums cannot be instantiated, maintaining their role as constant collections.
- Variable, Constructor, and Method Support: Enums can incorporate variables, constructors, and methods, extending their functionality.
7. Using Enums with Switch Case
Enums can be passed as arguments in switch statements, as demonstrated in the following example:
enum Color { RED, GREEN, BLUE}public class SwitchEnum { public static void main(String[] args) { Color k = Color.GREEN; switch (k) { case RED: System.out.println("I am " + k); break; case GREEN: System.out.println("I am " + k); break; case BLUE: System.out.println("I am " + k); break; } }}
8. The ordinal()
Method
The ordinal()
method returns the order in which enum instances are declared within the enum. For example, in the DayOfWeek
enum:
public enum DayOfWeek { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY}
The ordinal()
method assigns numbers to each element starting from 0. MONDAY
will be 0, TUESDAY
will be 1, and so on. Calling DayOfWeek.Thursday.ordinal()
will return 3.
9. Implementing Interfaces with Enums
Enums can implement interfaces in Java, providing flexibility and specialized implementations. This allows enums to be used as specific implementations in certain cases.
10. Exploring Additional Enum Features
Enums offer a wealth of additional features, including:
- Overriding Methods: Enums can override methods, such as
toString()
, to provide customized string representations. values()
Method: Thevalues()
method returns an array of enum constants in the order they are declared.valueOf()
Method: ThevalueOf()
method converts a String to an enum constant.compareTo()
Method: ThecompareTo()
method compares two enum constants, returning 0 if they are equal, a negative integer if the invoking object is less than the compared object, or a positive integer if the invoking object is greater than the compared object.
11. Frequently Asked Questions (FAQs)
- Can enums extend classes? No, enums cannot extend classes.
- Can enums have abstract methods? Yes, enums can have abstract methods.
- Can enums be subclassed? No, enums cannot be subclassed.
- Can enums have a main method? Yes, enums can have a main method.
12. Additional Resources
By mastering these concepts and practicing with examples, you’ll be well-equipped to tackle enum-related questions in your next Java interview. Remember, enums are a powerful tool that can enhance your code’s readability, type safety, and efficiency. So, embrace enums and unlock their full potential in your Java projects!
What else can Java Enums do?
Plus a lot more! In the section on Bounded and Indexed Domain, I talked about how Enums let you tell the compiler EXACTLY how many instances of that type will be present at runtime. I also said that knowing the number of instances at compile-time gives you some POWERFUL guarantees. Here are some of those benefits now.
- Singletons (and Multi-tons): Enums are not only the recommended way to make singletons in Java, they are also the best way to do it (so far). You shouldn’t make your own singleton if you only want one instance of an object. Instead, you should use an enum.
- Helper Methods Enums come pre-baked with some helpful methods. For example, consider the enum java. time. It shows the days of the week, like Monday, Tuesday, etc., with DayOfWeek. The fact that it is an enum means that these helpful methods are already included. These methods will be built in to every other enum you make by default. valueOf(String name)—If someone sends you a String and you want to turn it into your Enum, all you have to do is call the static method valueOf that comes with your Enum. The String must exactly match your enum values name. But if it does, then its simple, easy deserialization. name()—Also, this method will take the enum value (like Monday, Tuesday, etc.) and turn it into a String that is an exact match for the name enum value. The previous method demonstrated simple, easy deserialization. This method is for simple, easy serialization. values() — You can call this method to get a list of all the values in the right order. In this case, it will give you a list of all the days of the week, starting with MONDAY. This works well for looping or getting an enum value by index. compareTo(E other) — All enums are Comparable by default. One enum value comes before or after another, so it’s easy to tell which one it is. ordinal() — You can call this method to get the positional index of the enum value if you only want it (for example, if you want to pick one at random from the enum values). This might not seem very useful, but it can be used in shockingly powerful ways.
- Enum Collections: Enums have versions of Java that are EXTREMELY FAST and better at using memory. util. Set and java. util. Map — respectfully called java. util. EnumSet and java. util. EnumMap. To show how fast is really fast, I did some very simple and unofficial performance tests and found that EnumMap was over three times faster than Java. util. HashMap. And roughly 15% faster than java. util. IdentityHashMap, which is the non-enum equivalent of EnumMap. You should not use the enum variants if you have an enum and need a set or a map whose keys are the enum. When working with enums, you should always use them. Its a free performance boost. Not only that, but both of these enum variants are ORDERED, which means that comparing EnumMap to HashMap and IdentityHashMap is unfair (for EnumMap!) because EnumMap has a lot more features and functions than the hash variants. A “fair” comparison would be comparing EnumMap to java. util. TreeMap, which is even SLOWER than HashMap and IdentityHashMap. I hope you can see how much more powerful these enum variants are than the rest of the Collection library. And dont forget, I said they are also memory-optimized too. Not only are you getting the best performance the Collection library has to offer, but you are also getting one of the lowest memory costs in the library. Here’s why: at compile time, we know EXACTLY HOW MANY INSTANCES THERE ARE. This lets us work quickly and with little memory. We can use optimization strategies that aren’t physically possible for any of the other collections now that we know this. These enum-variants can use a byte[] (or a bit vector, which is very similar) to show inclusion. For example, the enums ordinal() method is used to represent indexes in an array in EnumSet. It then just puts a 0 or a 1 to show whether the matching enum value is in the set (I told you the ordinal() method could be used for shockingly powerful things). They are so unbelievably fast and light because of this byte. A byte is unbelievably fast and light.
How well do Java Enums get along with other Java features?
Extremely well! Java as a language prioritizes cohesiveness. That means that a feature that is added to the language should play nicely with all other features. Enums are no exception to this. Because of this, you can use an Enum anywhere you can use a Final Class!
However, there are some special interactions between Enums and other features of Java. Those interactions are what I will highlight in this section.
I should also mention, this is the part of the post that is more 2023 focused. As more features have been added, Java Enums have had more chances to interact with them in unique ways.
Just one last thing: I want to stress again what I already said: Java Enums don’t really work well with generics, and even then, it’s only class-level generics. Anything not class-level works beautifully.
- Checking for Single-Type Exhaustiveness with Enums: At compile time, we know the exact number of values that an enum data type can have. This gives the compiler a special ability when using a switch expression or statement. If the value we are switching over is an enum, then a switch expression or statement will let you turn on Exhaustiveness Checking (Ctrl F “Exhaustiveness of switch Expressions”). Exhaustiveness checking is a special feature that says our switch is exhaustive if all of its case labels cover all possible values for the value we are switching over (except for null). This is EXTREMELY POWERFUL. Lets say that I use my Direction enum again. But let’s say I add the diagonal values, like NORTHEAST, SOUTHEAST, etc. If I have a switch expression that depends on an enum value, then I need to cover every possible case for that code to work. When my Direction enum only had 4 values, I only needed 4 case labels. My enum works as long as I have those 4 case labels. The switch expression will not work when I add the four diagonals, making the total eight. This is because it is no longer exhaustive. Yes, this is HUGE because it gets rid of all those annoying bugs where you change something here but forget to change it there. There is, of course, an easy way to avoid Exhaustiveness Checking if you don’t want it: just use a default clause. The only thing you need to do is say what should happen by default if you don’t match any of the other case labels. That said, it is strongly encouraged to NOT opt out. Adding “do nothing” (or “do the same thing”) case labels to the enum will tell it what to do in those situations. Of course, if that doesn’t work for you or is too much trouble (for example, library staff), then that’s what the default clause is for.
- There are times when we want to split an enum into several parts but still want to do Exhaustiveness Checking. This is when Sealed Types and Switch Patterns come in handy. Adding Sealed Types and Switch Patterns to the mix is the answer in those kinds of situations. With a sealed type, only a certain set of types can be added to it. These types are known at the time of compilation. When the case labels don’t hold constants, they hold type patterns. This is called a switch pattern. In a type pattern, we check a value to see if it can be assigned to a type. In that case, we make a new focused variable and give it the value in question. Read more in the link above for Switch Patterns. To show all three of these at once, let’s say I have an enum Alphabet. It’s worth 26 things, which are the 26 letters of the English language. But what if I want to tell the difference between vowels and consonants? All I have to do is make my Alphabet enum a Sealed Interface that only lets Consonant and Vowel enums in. After adding the five vowels, I put the rest of the Alphabet values into the Consonant group. After I get that, I can switch to a different instance of Alphabet and still get exhaustiveness checking. I will also be able to “chunk” my exhaustiveness checks by type. I can handle all instances of Consonant in the same way while handling each instance of Vowel separately. To do this, I just need to include the type Consonant as one of my type patterns for a single case label. The other case labels will be for each value of Vowel. For now, this will still give me Exhaustiveness Checking, but it will only look at the important parts.
- More may or may not be coming in the future! It is very likely that we will get Constant Patterns in the future. That means we can use our enum value as a Constant Pattern anywhere we do pattern matching. When that happens, enums will become much more useful because they will give us yet another way to check for exhaustion that we can’t do now.
As you can see, Java Enums are powerful and flexible. There are so many use cases, and they extend into a lot of different domains. They have abilities that are unique (not just within Java, but amongst all non-JVM languages).
That said, everything that is good about enums comes because we make sacrifices along the way. We dont just get this stuff for free – there is a cost. There always is a cost.