An existing system creates a plethora of HashMap
instances via its Generics
class:
import java.util.Map;
import java.util.HashMap;
public class Generics {
public static <K,V> Map<K, V> newMap() {
return new HashMap<K,V>();
}
public static void main( String args[] ) {
Map<String, String> map = newMap();
}
}
This is the single point of creation for all instances of classes that implement the Map
interface. We would like the ability to change the map implementation without recompiling the application. This would allow us to use Trove's THashMap
, for example, to optimize the application.
The software cannot be bundled with Trove's THashMap
due to licensing conditions. As such, it would be great if there was a way to specify the name of the map to instantiate at runtime (for those people who have no such licensing restrictions). For example:
import java.util.Map;
import java.util.HashMap;
import gnu.trove.map.hash.THashMap;
public class Generics {
private String mapClassName = "java.util.HashMap";
@SuppressWarnings("unchecked")
public <K,V> Map<K,V> newMap() {
Map<K,V> map;
try {
Class<? extends Map<K,V>> c = (Class<Map<K,V>>)Class.forName(
getMapClassName() ).asSubclass( Map.class );
map = c.newInstance();
}
catch( Exception e ) {
map = new HashMap<K,V>();
}
return map;
}
protected String getMapClassName() {
return this.mapClassName;
}
protected void setMapClassName( String s ) {
this.mapClassName = s;
}
public static void main( String args[] ) {
Generics g = new Generics();
Map<String, String> map = g.newMap();
System.out.printf( "Class = %s\n", map.getClass().toString() );
g.setMapClassName( "gnu.trove.map.hash.THashMap" );
map = g.newMap();
System.out.printf( "Class = %s\n", map.getClass().toString() );
}
}
Is there a way to avoid the @SupressWarnings
annotation when compiling with -Xlint
and still avoid the warnings?
Is there a way to avoid the
@SuppressWarnings
annotation when compiling with-Xlint
and still avoid the warnings?
No. Class.forName
returns a Class<?>
. The only way to assign it to Class<? extends Map<K, V>>
is to do an unchecked cast. Sometimes unchecked casts are necessary, and so using @SuppressWarnings("unchecked")
is acceptable here (provided you document the reason well).
IMHO it would be more correct to keep a reference to Class<? extends Map<?, ?>>
and then do an unchecked cast of the result of newInstance
to Map<K,V>
. I only say this because a Class
object is a canonical representation of a raw type, so a type like Class<? extends Map<K, V>>
is slightly misleading.
This is outside the scope of the question, but here's a suggested alternative for your solution:
public interface MapFactory {
<K, V> Map<K, V> newMap() throws Exception;
}
public enum HashMapFactory implements MapFactory {
INSTANCE;
@Override
public <K, V> Map<K, V> newMap() {
return new HashMap<K, V>();
}
}
public static final class DynamicMapFactory implements MapFactory {
private final Constructor<? extends Map<?, ?>> constructor;
private DynamicMapFactory(Constructor<? extends Map<?, ?>> constructor) {
this.constructor = constructor;
}
@Override
//Impl note: these checked exceptions could also be wrapped in RuntimeException
public <K, V> Map<K, V> newMap() throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
@SuppressWarnings("unchecked") //this is okay because the default ctor will return an empty map
final Map<K, V> withNarrowedTypes = (Map<K, V>)constructor.newInstance();
return withNarrowedTypes;
}
public static DynamicMapFactory make(String className) throws ClassNotFoundException, NoSuchMethodException, SecurityException {
@SuppressWarnings("unchecked") //Class<? extends Map> can safely be cast to Class<? extends Map<?, ?>>
final Class<? extends Map<?, ?>> type =
(Class<? extends Map<?, ?>>)Class.forName(className).asSubclass(Map.class);
final Constructor<? extends Map<?, ?>> constructor = type.getDeclaredConstructor();
return new DynamicMapFactory(constructor);
}
}
public static void main(String[] args) throws Exception {
Map<String, Integer> map1 = HashMapFactory.INSTANCE.newMap();
Map<String, Integer> map2 = DynamicMapFactory.make("java.util.TreeMap").newMap();
System.out.println(map1.getClass()); //class java.util.HashMap
System.out.println(map2.getClass()); //class java.util.TreeMap
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With