Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Suppressing Warnings when using a dynamic class reference

Background

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.

Problem

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() );
  }
}

Question

Is there a way to avoid the @SupressWarnings annotation when compiling with -Xlint and still avoid the warnings?

like image 977
Dave Jarvis Avatar asked Nov 12 '22 09:11

Dave Jarvis


1 Answers

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
}
like image 134
Paul Bellora Avatar answered Nov 15 '22 07:11

Paul Bellora