jackalope
jackalope

Reputation: 1564

How to instance generic parameterized class

I just implement a MapBuilder to build map easy, But when i try to get an instance of HashMap.class,I suddenly found that I can't use HashMap.class to get such an instance.

It's illegal!

So can anybody tell me why and how to solve this problem?

The MapBuilder is follow:

import java.util.Map;

public abstract class MapBuilder {

    public static <K, V, T extends Map<K, V>> InnerMapBuilder<T, K, V> start(
            Class<T> clazz) {
        return new InnerMapBuilder<>(clazz);
    }

    public static class InnerMapBuilder<T extends Map<K, V>, K, V> {

        private T target;

        public InnerMapBuilder(Class<T> clazz) {
            try {
                target = clazz.newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }

        public InnerMapBuilder<T, K, V> put(K key, V val) {
            target.put(key, val);
            return this;
        }

        public T get() {
            return target;
        }
    }
}

And the test code is below:

public static void main(String[] args) {
    HashMap<String, String> v = start(HashMap<String,String>.class).put("a", "b").get();
    System.out.println(v);
}

Upvotes: 3

Views: 444

Answers (2)

Mark Peters
Mark Peters

Reputation: 81074

It's impossible to obtain a parameterized class type variable for a generic type, as Reimeus has said. So you have three choices.

First, you can live with the unchecked cast:

Class<? extends Map<String, Integer>> clazz = 
    (Class<? extends Map<String, Integer>>) HashMap.class;

Second, you can reify the parameters for a class by extending it (in this example, using an anonymous inner class):

Class<? extends Map<String, Integer>> clazz =
     new HashMap<String, Integer>() {}.getClass();

Or third, and best, just take the Map instance instead of a class in start(). You're not saving the user any work by taking the Class rather than an instance of Map, and the first thing you do is create an instance of it.

By passing it in, the user can even tweak the settings of the map (e.g. for a HashMap, set the load factor, for TreeMap, specify the Comparator) so it's a better alternative anyway. If you need to, you can assert that it's empty when it's passed in.

If for some reason you really need a factory, don't use Class: it doesn't work well as a factory, because the only way you can customize the instance that Class creates is by subclassing the class and providing a new no-arg constructor. Just create an interface Factory<T> that has a method T create() and then accept a Factory<? extends Map<K, V>.

Upvotes: 1

Reimeus
Reimeus

Reputation: 159754

First since start takes a class you would have to pass it an unparameterized class such as HashMap.class. Second, as you're returning a generic type Map, you would have to make your local type match also, so to use:

Map<String, String> v = start(HashMap.class).put("a", "b").get();

Upvotes: 0

Related Questions