How to Extend Enums in Java
This tutorial demonstrates how to extend the enum
functionality in Java.
Extend enum
in Java
We can consider enum
as a kind of compiler magic because, in the byte
code, the enum
is represented as a class with several static
members and is inherited from abstract java.lang.Enum
.
It is the reason the enum
cannot extend any other class or enum
. As we cannot extend enum
in Java, it is also impossible for any class to extend an enum
. Let’s learn by using the following example code and see what happens.
Example Code:
package delftstack;
enum Cars { Audi, BMW, Marcedes }
public class Example extends Cars {
public static void main(String... args) {}
}
The code above has an enum
named Cars
, and class Example
is trying to extend it. See output:
/Example.java:5: error: cannot inherit from final Cars
public class Example extends Cars {
^
/Example.java:5: error: enum types are not extensible
public class Example extends Cars {
^
2 errors
As we can see, the class cannot extend the enum
. So if it is impossible to extend the enum
, can we still extend its functionality?
The functionality can be defined as the implemented methods in the enum
. For example, the enum
Cars
from the above code can declare abstract methods for each member; see the example:
enum Cars {
Audi {
@Override
public void drive() {}
},
BMW {
@Override
public void drive() {}
},
Mercedes {
@Override
public void drive() {}
},
;
public abstract void drive();
}
As we can see, each member can override the drive()
method. But unfortunately, it is not always possible to create methods in enum
because:
- If the
enum
belongs to a third-party library or another team, it will not allow to implementation of abstract methods. - If it belongs to the module which doesn’t have the dependency required for the
drive()
method. - If the
enum
is overloaded with other functions and data, it will be unreadable.
There are some solutions provided that can solve these problems and extend the functionality of enum
in Java.
Solution 1: Mirror enum
in Java
As the name suggests, we need to create another enum
with the same data. That new enum
will also implement the drive()
method, So we have two enums now:
Example Code for enum Cars
:
enum Cars {
Audi {
@Override
public void drive() {}
},
BMW {
@Override
public void drive() {}
},
Mercedes {
@Override
public void drive() {}
},
;
public abstract void drive();
}
Example Code for enum DriveCars
:
enum DriveCars {
Audi {
@Override
public void drive() {}
},
BMW {
@Override
public void drive() {}
},
Mercedes {
@Override
public void drive() {}
},
;
public abstract void drive();
}
The second enum
is the mirror of the first one. Now we can use both of these enums by extending the functionality; we need to use built-in enum
methods that are name()
and valueof()
.
See the following example of how to use it:
Cars cars = ... DriveCars.valueOf(cars.name()).drive();
The above code shows how enum Cars
functionality is used in the enum DriveCars
. Which means the functionality is extended.
The name()
method in the above code is final
, which cannot be overridden, and the valueOf
method will be generated by the compiler. Both of these methods are a good fit for each other is there is no functional error in the extended operation.
There is one issue with the above code if the enum Cars
is changed, the enum DriveCars
will not have any idea, and it will cause the failure of the name
and valueof
trick. To solve this issue, we must let the DriveCars
know that its parent mirror is Cars
.
For that, we can use a static
initializer to compare the DriveCars
and Cars
, which will throw the exception if both the enums do not match. Here is an example of that from the enumus library:
enum DriveCars {
....static { Mirror.of(Cars.class); }
}
The utility class will check if both enums match or not. This method will validate the name()
and valueOf()
trick.
Solution 2: Map enum
in Java
If you don’t want to create another enum
that holds only one method. In this case, we can use interface
instead of the enum
; see the example below:
public interface Driver {
void drive();
}
Now to use this interface Drive
with the enum Cars
, we can create a mapping between them. Here is the example for the map:
Map<Cars, Driver> drivers = new EnumMap<>(Cars.class) {
{
put(Audi, new Driver() {
@Override
public void driver() {}
}) put(BMW, new Driver() {
@Override
public void driver() {}
}) put(Mercedes, new Driver() {
@Override
public void driver() {}
})
}
}
Now to use them, use this simple piece of code:
drivers.get(Cars).drive();
The EnumMap
used in the code above will guarantee that each enum
member will appear only once, but it does not guarantee an entry for each member.
We can check the size of the map is equal to the number of members of enums:
drivers.size() == Cars.values().length
The enumus library also provides a utility for this case: if the map does not fit the Cars
, it will throw the IllegalStateException
. Here is the utility:
EnumMapValidator.validateValues(Cars.class, map, "Cars map");
Both methods above show how to make enums powerful by extending their functionality. Though it is impossible to directly extend an enum
, we can use these tricks to extend their functionalities.
Sheeraz is a Doctorate fellow in Computer Science at Northwestern Polytechnical University, Xian, China. He has 7 years of Software Development experience in AI, Web, Database, and Desktop technologies. He writes tutorials in Java, PHP, Python, GoLang, R, etc., to help beginners learn the field of Computer Science.
LinkedIn Facebook