I'm trying to write some Java code for a set of enum classes.
Each of the enums encapsulate some conceptually distinct data, so it doesn't make sense to combine them. The enums also map to values in a database, and so also share some common operations, both instance and static operations, related to loading data from the database.
I need to generalise the set of enum classes I have, such that I can pass any one of these enums into a different class which performs and caches of database lookups relating to each of the different enums.
Since the cache/lookup class will also depend on the public and static methods defined in each enum, how can I code my solution so that I can guarantee that any enum that can be passed into the class will have the required methods?
The normal approach would be to define an interface, but interfaces don't allow static methods.
Alternatively, you might use an abstract class to define the interface and some of the common implementation, but I don't believe that is possible with enums (I understand that enums must extend the Enum class and cannot be extended).
What are my options do I have that enable me to ensure all of my enums implement the methods I need?
Example enum:
public enum MyEnum{
VALUE_ONE("my data");
VALUE_TWO("some other data");
/**
* Used when mapping enums to database values - if that sounds odd,
* it is: it's legacy stuff
*
* set via private constructor
*/
private String myValue;
//private constructor not shown
public static MyEnum lookupEnumByString(String enumValue){
//find the enum that corresponds to the supplied string
}
public String getValue(){
return myValue;
}
}
The enum class body can include methods and other fields. The compiler automatically adds some special methods when it creates an enum. For example, they have a static values method that returns an array containing all of the values of the enum in the order they are declared.
An enum can, just like a class , have attributes and methods. The only difference is that enum constants are public , static and final (unchangeable - cannot be overridden).
1. Enum is a public and static class. Because we call enums inside the main method. And only static and public variables are can call inside the main method.
All Enums are implicitly static, its just you don't need to write the static keyword.
It's all quite complicated and there may be errors, but I hope you get the idea.
// I'm not sure about the right type arguments here
public interface MyEnumInterface<E extends MyEnumInterface & Enum<E>> {
public static boolean aUsefulNonStaticMethod();
String getValue();
MyEnumInfo<E> enumInfo();
}
/** contains some helper methods */
public class MyEnumInfo<E extends MyEnumInterface<E>> {
private static <E extends MyEnumInterface<E>> MyEnumInfo(Class<E> enumClass) {...}
// static factory method
public static <E extends MyEnumInterface<E>> MyEnumInfo<E> infoForClass(Class<E> enumClass) {
... return a cached value
}
public static <E extends MyEnumInterface<E>> MyEnumInfo(E e) {
return infoForClass(e.getClass());
}
// some helper methods replacing static methods of the enum class
E enumForValue(String value) {....}
}
public enum MyEnum implements MyEnumInterface<MyEnum> {
VALUE_ONE("my data");
VALUE_TWO("some other data");
private String myValue; //set via private constructor
//private constructor not shown
public boolean aUsefulNonStaticMethod(){
//do something useful
}
public String getValue(){
return myValue;
}
// the ONLY static method in each class
public static MyEnumInfo<E> staticEnumInfo() {
return MyEnumInfo.infoForClass(MyEnumClass.class);
}
// the non-static version of the above (may be useful or not)
public MyEnumInfo<E> enumInfo() {
return MyEnumInfo.infoForClass(getClass());
}
}
It's a bit strange, that you're using another String in addition to Enum.name(), do you need it?
Because of all enums extending Enum, you can't let them share any code. The best you can do is delegating it all to a helper static method in a utility class.
There's no way to force classes to implement a static method, which is understandable, since there's no way (except for reflection) to call them.
This is the closest I can think of.
You have a class containing your common functionality:
class Util{
//common functionality
public void doSomething(){
}
}
Each Enum has has an instance of this class and can override its methods if necessary:
enum Enum1{
FOO,
BAR;
private Util util = new Util();
public Util getUtil() {
return util;
}
}
enum Enum2{
ALICE,
BOB;
private Util util = new Util(){
@Override
public void doSomething() {
//this one has overridden it
};
};
public Util getUtil() {
return util;
}
}
Example Usage:
Enum2.ALICE.getUtil().doSomething();
** WARNING** the following is Java-pseudocode, and as such, will not compile.
So you want to attach logic to individual enums. This could possibly require some enums to share the same logic while having other enums with their own, specific logic. Also, you want to associate String keys that might not be the same as the Enum's name (what's typically returned by Enum.name()
.
The following java-based pseudocode shows one way (one of the many) in which you could do that. It is not the only one, and I do not claim that it is the best. However, this would be the approach that I would use in such a case.
That is, I would go with object composition via interfaces (a bit sort of Strategy + Template patterns.)
// at package-level visibility
interface EnumHandler
{
SomeRetVal doSomething(DBEnum dbEnum);
}
final class DefaultHandler implements EnumHandler
{
static DefaultHandler _handler = new DefaultHandler();
SomeRetVal doSomething(DBEnum dbEnum)
{
return ping;
}
}
// at public visibility
public interface Actionable
{
// meh, you might need such an interface, or maybe not, just added here
// for illustration purposes. you'll have to make a decision if you need it
// or not.
SomeRetVal doSomething();
}
// have the enum implement the interface if you determine you need
// such an interface
public Enum DBEnum implements Actionable
{
// ONE and THREE share the same logic. TWO has its own.
ONE("db-key-one" ),
TWO("db-key-two, new EnumHandler(){
SomeRetVal doSomething(DBEnum dbEnum){ return pong; } } ),
THREE("db-key-three");
// this guy keeps track of enums by key
static private java.util.Map<String,DBEnum> _MAP =
java.util.Collections.unmodifiableMap(
new java.util.HashMap<String,DBEnum>() );
final private String _key;
final private EnumHandler _handler;
// allows construction of customized handler
DBEnum(final String key, final EnumHandler handler)
{
this._key = key;
this._handler = handler;
this._MAP.put(key, this)
}
// construct using default handler
DBEnum(final String key)
{
this(key, DefaultHandler._handler);
}
// have toString() return the key instead of this.name()
public String toString()
{
return this._key;
}
// implementing Actionable interface (if you choose to use such an interface)
public SomeRetVal doSomething()
{
return this._handler.doSomething(this);
}
// get enum by key
public static DBEnum getByDbKey(final String key)
{
DBEnum retVal = this._MAP.get(key);
if( retVal == null ){ throw new IllegalArgumentException("thingie not found");
return retVal;
}
public static Iterator<String> dbKeys()
{
return _map.keySet().iterator();
}
}
// somewhere else
public static void main(String[] args)
{
DBEnum.ONE.doSomething();
DBEnum.geByDBKey( DBEnum.TWO.toString() ).doSomething();
for( String dbKey : DBEnum.dbKeys() )
{
DBEnum.getByDbKey( dbKey ).doSomething();
}
// the following will kaput with an IllegalArgumentException
DBEnum.getDbByKey( "key-that-is-not-there" ).doSomething();
}
One could, in theory, as far as pulling the actual db keys from a resource file when the Enum is loaded by the class loader. The contents (and changes to the content) of the resource file could be a deployment item. There can be significant advantages to this - a change to the db key would not require a recompilation. BUT such an approach will make things a bit more complex. It would be something I would implement after everything else is done right.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With