1

I have a database table that contains a column named type. For every row in my database column I have to create an object depending on the type. At the moment I use if else statements for that:

if (type.equals("object1")){
    Object1 object1 = new Object1();
}
else if (type.equals("object2")){
    Object2 object2 = new Object2();
}

Somewhat nicer would be to use an enum, as the number of types is limited but is there a possibility to let the creation of an object depend on the value of the String?

I'm open to suggestions that might solve my problem in another way than I am trying to.

J. Maes
  • 6,862
  • 5
  • 27
  • 33

5 Answers5

3

You could create a map from String to Class and use newInstance. That however relies on the existence of no-arg constructors.

import java.util.*;

class Test {
    public static void main(String[] args) throws Exception {
        Map<String, Class<?>> classes = new HashMap<String, Class<?>>();
        classes.put("object1", Object1.class);
        classes.put("object2", Object2.class);

        String type = "object2";

        Object obj = classes.get(type).newInstance();

        //...
    }
}

class Object1 { ... }

class Object2 { ... }
aioobe
  • 413,195
  • 112
  • 811
  • 826
3

You can use

Object o = Class.forName(type).newInstance();

If you have an int and two Strings as arguments you need.

Object o = Class.forName(type)
                .getConstructor(int.class, String.class, String.class)
                .newInstance(intValue, string1, string2);

Another possibility is to use factory methods

Object o = getClass().getMethod("create_" + type).invoke(null);

static Object1 create_object1() {
     return new Object1(/* with args */);
}

static Object2 create_object2() {
     return new Object2(/* with other args */);
}

but the most flexible approach may be to use a switch

Object o;
switch(type) { // in Java 7
    case "object1": o = new Object1(); break;
    case "object2": o = new Object2(); break;

What would be more elegant is using closures in Java 8.

http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html

Holger
  • 285,553
  • 42
  • 434
  • 765
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • 1
    I'm developing for Android so the switch statement will not work – J. Maes Feb 07 '12 at 10:22
  • Good point, same goes for closures. The first example will work. You may need to add a package at the start of the name and change the case of the first letter. ;) – Peter Lawrey Feb 07 '12 at 10:27
  • I'm trying the Object o = Class.forName(type).newInstance(); I think it would suit me the best. I'm stuck however at passing the constructor variables. I need to pas an int and two Strings and this question addresses this but I can't seem to figure it out (or just understand what happens) http://stackoverflow.com/questions/234600/can-i-use-class-newinstance-with-constructor-arguments – J. Maes Feb 07 '12 at 10:48
  • @J.Maes as you can read in the post you cite, you can use `Constructor.newInstance()` instead of `Class.newInstance();`. I've edited my answer, take a look. – davioooh Feb 07 '12 at 12:06
  • @J.Maes Added an example for this case. – Peter Lawrey Feb 07 '12 at 12:35
  • With both methods (use Constructor object or getConstructor, I receive a ClassNotFoundException caused by NoClassDefFoundError. The type matches the name of the class which is in the same package. Could it also be that this method is much slower than the regular switch/if,else I used earlier? – J. Maes Feb 07 '12 at 12:51
  • You have to add the package (as I have stated already) Using reflection is relatively slower, but takes less than a micro-second. – Peter Lawrey Feb 07 '12 at 12:53
0
package main;

import java.util.ArrayList;
import java.util.List;

public class TempClass {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        List<Class> classes = new ArrayList<Class>() {{
            add(Class.forName("main.Object1"));
            add(Class.forName("main.Object2"));
        }};
        for (Class aClass : classes) {
            Object o = aClass.newInstance();
        }
    }
}


package main;

public class Object1 {
    public Object1() {
        System.out.println("Object1");
    }
}


package main;

public class Object2 {
    public Object2() {
        System.out.println("Object2");
    }
}
kornero
  • 1,109
  • 8
  • 11
0

Using switch or if statements forces you to hard-code the various options, so you will obtain a not so flexible solution, in my opinion.

I always prefer to write code that is extensible for future software developments.

So I suggest you to use Object o = Class.forName(type).newInstance(); putting in your database the name of the class you want to instantiate and, if you need it, a custom label.

EDIT

If you have to pass some parameter to the constructors of the classes you have to instantiate, is convenient to use Constructor.newInstance() instead of Class.newInstance(); and use the Constructor object to instantiate the class, for example:

Class myClass = Class.forName(type);

// get the constructor you need specifying parameters type 
Constructor constructor = myClass.getConstructor(String.class);

Object myObject = constructor.newInstance("constructor-arg");
davioooh
  • 23,742
  • 39
  • 159
  • 250
0

If the classes in question are part of an inheritance hierarchy and you happen to use some kind of mapping tool/framework like Hibernate or iBATIS/MyBatis, it might include a way to configure so called discriminators.

You could then configure your "type" column as the descriminator column and map the supported values to your classes. The framework will then instantiate the appropriate classes for you when you retrieve the objects from the database.

Martin Klinke
  • 7,294
  • 5
  • 42
  • 64