Java way to create an object based on enum type

5.8k views Asked by At

My class is like this:

class X {}
class Y extends X {};
class Z extends X {};

I have an enum for each subclass (id + class):

enum Type {
   Y_TYPE(1, Y.class), Z_TYPE(2, Z.class);
   int id;
   Class c;
   public Type(int id, Class c) { this.id = id; this.c = c; }
   public static X createInstance() throws Exception {
      return c.newInstance();
   }
}

Then I used them as follows:

X parseFromID(int id) {
   for (Type v : Type.values()) {
     if (v.id == id) {
        return v.createInstance();
     }
   }
}

It works fine but I'm wondering if this a Java-ist way to create data based on integer id ? Is there any bad thing that should look for ?

Is there a way to enforce the class that is passed into are of X type without lengthy if-else condition? Think when I have a large number of subclasses.


Why do you want to work on integer ids?

I'm writing some sort of parser, so I need to convert integer id that I've taken from somewhere to the appropriate object.

3

There are 3 answers

5
JB Nizet On

There is really no reason to use reflection here. Throwing Exception is also a bad practice, and if you didn't use reflection, you wouldn't have to deal with reflection exceptions. You could simply do

enum Type {
    Y_TYPE(1) {
        @Override
        public X createInstance() {
            return new Y();
        }
    }, Z_TYPE(2) {
        @Override
        public X createInstance() {
            return new Z();
        }
    };

    private int id;

    private Type(int id) { 
        this.id = id; 
    }

    public abstract X createInstance();
}

This is also helpful because it doesn't force every subclass to have a public no-arg constructor, and also allows returning the same instance of X or Y, if possible.

If you're concerned about the verbosity of the anonymous class definitions, you could replace them with lambdas, if you're using Java 8:

import java.util.function.Supplier;

enum Type {
    Y_TYPE(1, X::new), Z_TYPE(2, Y::new);

    private int id;
    private Supplier<X> supplier;

    private Type(int id, Supplier<X> supplier) {
        this.id = id;
        this.supplier = supplier;
    }

    public X createInstance() {
        return supplier.get();
    }
}
0
nrainer On

Is there a way to enforce the class that is passed into are of X type without lengthy if-else condition?

Yes, you can use generics to restrict the class. Change the constructor to:

public Type(int id, Class<? extends X> c) { this.id = id; this.c = c; }

Why do you want to work on integer ids? You can use either the enum values directly or - if you need to transfer or store them - their string representation and parse the String if needed using the enum's valueOf method.

0
Aubin On

Using a factory and a map is more academic:

import java.util.HashMap;
import java.util.Map;

interface Factory<T> {

    T createInstance();
}

class X {/**/}
class Y extends X {/**/}
class Z extends X {/**/}

class Factories {

   static Map<Integer, Factory<?>> factories = new HashMap<>();
   static {
      factories.put( 1, X::new );
      factories.put( 2, Y::new );
      factories.put( 3, Z::new );
   }

   @SuppressWarnings("unchecked")
   static <T> Factory<T> get( int id ) {
      return (Factory<T>)factories.get( id );
   }
}

public class Main {

   static void main( String[] args ) {
      final Factory<X> fx = Factories.get( 1 );
      final X x = fx.createInstance();
      final Factory<Y> fy = Factories.get( 2 );
      final Y y = fy.createInstance();
      final Factory<Z> fz = Factories.get( 3 );
      final Z z = fz.createInstance();
   }
}