Restrict a generic Class parameter to classes that

2020-03-17 09:30发布

问题:

I'm trying to write a Map builder. One of the constructors will allow the client to specify the type of Map they wish to build

public class MapBuilder<K, V> {

    private Map<K, V> map;

    /**
     * Create a Map builder
     * @param mapType the type of Map to build. This type must support a default constructor
     * @throws Exception
     */
    public MapBuilder(Class<? extends Map<K, V>> mapType) throws Exception {
        map = mapType.newInstance();
    }

    // remaining implementation omitted
}

The intent is that it should be possible to construct instances of the builder with:

MapBuilder<Integer, String> builder = new MapBuilder<Integer, String>(LinkedHashMap.class);

or

MapBuilder<Integer, String> builder = new MapBuilder<Integer, String>(HashMap.class);

It seems that the type signature of the constructor argument doesn't currently support this, because the line above causes a "Cannot resolve constructor" compilation error.

How can I change my constructor so that it accepts classes that implement Map only?

回答1:

Use a Supplier instead of a Class:

public MapBuilder(Supplier<? extends Map<K, V>> supplier) {
    map = supplier.get();
}

Which then can be called like this:

MapBuilder<Integer, Integer> builder = new MapBuilder<>(LinkedHashMap::new);

This is also safer, because a Class<Map> could have no default constructor, which would throw an error (which is not very responsive code)



回答2:

The following will work:

public MapBuilder(Class<? extends Map> mapType) throws Exception {
    map = mapType.newInstance();
}


回答3:

The problem is that LinkedHashMap.class is

Class<LinkedHashMap>

and not something like

Class<LinkedHashMap<Integer, String>>

These are also inconvertible types (so you can't cast it) and there's no way to get an instance of the latter.

What you can do is change the constructor to

public MapBuilder(Class<? extends Map> mapType) throws Exception

Generics are erased at run-time so at run-time all Maps will behave just like Map<Object, Object> anyway. Therefore it doesn't matter that the class you're constructing from is using the raw type.


By the way, Class::newInstance is deprecated. Use

mapType.getConstructor().newInstance()


标签: java generics