Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to cast Wildcard (?) to a Generic type T?

I have a static method that i want to use to load classes and instantiate my objects at runtime, but when i compile, i got this warning:

warning: [unchecked] unchecked cast
            T t = (T) ctor.newInstance();
required: T
found:    CAP#1
where T is a type-variable:
    T extends Object declared in method <T>forName(String,Set<String>)
    where CAP#1 is a fresh type-variable:
    CAP#1 extends Object from capture of ?
1 warning

Here's the code:

public static <T> Set<T> forName(String modulePath, Set<String> classes) throws InvalidModuleException{
    try {
        ClassLoader cl = new URLClassLoader(new URL[]{new URL(modulePath)});

        Set<T> list = new HashSet<>(classes.size());
        for (String className : classes) {
            Class<?> clazz = (Class<?>) Class.forName(className, true, cl);
            Constructor<?> ctor = clazz.getConstructor();
            T t = (T) ctor.newInstance();
            list.add(t);
        }
        return list;    
    } catch (MalformedURLException | ReflectiveOperationException ex) {
        throw new InvalidModuleException(ex.getMessage());
    }
}

Someone can explain me that?

[UPDATE] Here's and example of method call:

HashSet<String> set = new HashSet<>();
h.add("fully_classfied_classname_readed_from_file"); //Class that extends AbstractApplication
Set<AbstractApplication> abs = Apps.forName("plugins/module.jar", set);
like image 781
ehbarbian Avatar asked Sep 25 '22 16:09

ehbarbian


1 Answers

You cannot do that in a safe way using strings to indicate the classes. For one thing, there is no way for the compiler to know that the set of strings actually contains fully-qualified class names; even if they are class names, there is no guarantee that the classes specified by the names are subclasses of T.

Instead of passing in a set of strings, pass in a set of classes, constrained to be subclasses of T:

Set<Class<? extends T>> classes

Then you can just iterate over these classes, and remove the need for any casting:

for (Class<? extends T> clazz : classes) {
  Constructor<? extends T> ctor = clazz.getConstructor();
  T t = ctor.newInstance();
  list.add(t);
}

If the requirement to defer initializations of the classes is absolute, you have little choice but to add @SuppressWarnings appropriately to this method, and hope that the configuration from which these strings are loaded is correct.

like image 150
Andy Turner Avatar answered Sep 28 '22 07:09

Andy Turner