Store classes in a Map so that they can be instantiated

1.8k Views Asked by At

I want to be able to create instances of classes, based on HashMap entries.

E.g. this is what I'd try writing off the top of my head:

public class One implements Interface {
  public void sayName() {
    System.out.println("One");
  }
}

public class Two implements Interface {
  public void sayName() {
    System.out.println("Two");
  }
}

Map<String, Interface> associations = new HashMap<String, Interface>();

associations.put("first", One);
associations.put("second", Two);

Interface instance = new associations.get("first")();

instance.sayName(); // outputs "One"

But I strongly suspect this will not work in Java.


My situation: I want to create a way to associate String names with classes.

A user can create instances of a class by using its "name".

I feel like trying: making a map of names to classes (I don't know how to store classes in a map), and getting the item from the map that matches "name" and then instantiating it.

That won't work.


How can I associate classes with String names and instantiate those classes using the 'name' I've given it?

3

There are 3 best solutions below

4
Michael On BEST ANSWER

You can use the Supplier functional interface and a method reference to the default constructor:

Map<String, Supplier<Interface>> associations = new HashMap<>();

associations.put("first", One::new);
associations.put("second", Two::new);

To instantiate a new object, call Supplier.get:

Interface foo = associations.get("first").get();

If your constructors require arguments, you'll need to use another functional interface. For one- and two-argument constructors, you can use Function and BiFunction respectively. Any more and you'll need to define your own functional interface. Supposing the constructors both take a string, you could do this:

class One implements Interface
{
    One(String foo){ }

    public void sayName() {
        System.out.println("One");
    }
}

Map<String, Function<String, Interface>> associations = new HashMap<>();
associations.put("first", One::new);

and then use Function.apply to get the instance:

Interface a = associations.get("first").apply("some string");

If your constructors take different number of arguments then you're out of luck.

4
Jacob G. On

I'd highly recommend the use of Supplier in Michael's answer. Alternatively, you should be able to use the following:

var associations = Map.of("first", One.class, "second", Two.class);

var foo = associations.get("first").getConstructor().newInstance();

If your constructors require arguments, just pass the classes to getConstructor and the values to newInstance. For example, if Two takes an int in its constructor:

var foo = associations.get("two").getConstructor(int.class).newInstance(5);

Note: This uses Java 10.

0
pamcevoy On

Did you want to store classes and create new instances on demand, or store instances and just use them?

public class ClassInHashMap {
    public static void main(String[] args) {
    Map<String,Class<? extends SomeInterface>> associations = new HashMap<String,Class<? extends SomeInterface>>();
        associations.put("first", One.class);
        associations.put("second", Two.class);

        try {
            Class<? extends SomeInterface> someCls = associations.get("first");
            SomeInterface instance = someCls.getDeclaredConstructor().newInstance();
            instance.sayName(); // outputs "One"
        }
        catch (IllegalArgumentException e) {
            e.printStackTrace();
        }
        catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        catch (SecurityException e) {
            e.printStackTrace();
        }
        catch (InstantiationException e) {
            e.printStackTrace();
        }
        catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

interface SomeInterface {
    public void sayName();
}

class One implements SomeInterface {
    public void sayName() {
        System.out.println("One");
    }
}

class Two implements SomeInterface {
    public void sayName() {
        System.out.println("Two");
    }
}