2

Hope you can help me with this: I have ...

  • a string list of class names called classNameList
  • a generic class Geography<T>
  • a static generic method <T> void read(Class<T> cl, Geography<T> geo)

I want to loop through the string class name list and call the generic method for each of these classes.

What I tried but obviously did not work:

for (int i = 0; i < classNameList.length; i++) {
   Class<?> myClass = Class.forName(classNameList[i].getName());
   Geography<myClass.newInstance()> geo;
   read(myClass, geo);
}

Error: myClass.newInstance cannot be resolved to a type

My code runs perfectly for a single call of the generic function:

Geography<ExampleClass> ExampleGeo;
read(ExampleClass.class, ExampleGeo);

Any ideas how I could do this?

UPDATE:

Thanks for the helpful input, still it's hard for me to adopt it to my real code. So this is the non simplyfied problem:

I do ready in shapefile-Data with a shapefileLoader, for each feature of the Shapefile a class (GuadAgent) is initialized with a predifined class (PlantWind). I have shapefiles in my input-directory with the names of the Classes their features do represent. I want Java to read in the shapefiles and create the respective agent class. (the agents are also placed in a context and a geography..) Used classes are: ShapefileLoader, Geography, the other classes can be find at the same website

This part is in the main-method:

Geography<GuadAgent> guadGeography = GeographyFactoryFinder.createGeographyFactory(null).createGeography("guadGeography", context, new GeographyParameters<GuadAgent>());
Context<GuadAgent> context = new DefaultContext<GuadAgent>();

FileFilter filter = new FileFilter() {
    @Override
    public boolean accept(File file) {
        return file.getName().endsWith(".shp"); // return .shp files
    }
};
String shapefileDir = System.getProperty("user.dir")+"\\input\\shp\\";
File folder = new File(shapefileDir);
File[] listOfFiles = folder.listFiles(filter);

for (File classFile : listOfFiles) {
        try {
            readForName(classFile,context,guadGeography);
        } catch (ClassNotFoundException | MalformedURLException
                | FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
}

The static Method that reads in the names:

static <T> void readForName(File classFile, Context<GuadAgent> context,Geography<GuadAgent> guadGeography) throws ClassNotFoundException, MalformedURLException, FileNotFoundException {

        String shapefileDir = System.getProperty("user.dir")+"\\input\\shp\\";
        String className = classFile.getName().split("\\.(?=[^\\.]+$)")[0];
        File shapefile = null;
        shapefile = new File(shapefileDir+classFile.getName());

        if (!shapefile.exists()) {
            throw new FileNotFoundException("Could not find the given shapefile: " + shapefile.getAbsolutePath());
        }

        switch (className) {
        case "PlantWind":           
            ShapefileLoader<PlantWind> PlantWindLoader = new ShapefileLoader<PlantWind>(PlantWind.class,shapefile.toURI().toURL() , guadGeography, context);
            PlantWindLoader.load();
            PlantWindLoader.close();
            System.out.println(context.getObjects(PlantWind.class).size());                     
            break;
    // Todo Add other Agent types
    default:
        break;
    }

How can I get rid of the switch? Although their number is finit, there are very many different agents...

Johannes
  • 23
  • 4
  • This might be impossible due to type-erasure. At runtime, there exists only one type `Geography`, with all occurrences of `T` replaced by its upper bound, in your case `Object`. – Kulu Limpa Oct 09 '14 at 22:48

3 Answers3

1

Unfortunately, there's no syntax close to your intention (nice idea though).

The basic problem is that Class.forName() returns an unknown Class<?>, so you need a cast somewhere. It's just a mater of where you put it.

I suggest this approach (which compiles) that bundles up doing a read() based on a class name:

static <T> void readForName(String className) throws ClassNotFoundException {
    Class<T> myClass = (Class<T>) Class.forName(className);
    Geography<T> geo = new Geography<T>();  // No code shown. Adjust as required
    read(myClass, geo);
}

May I also suggest using the foreach loop syntax, for tidier code:

for (String className : classNameList) {
    readForName(className.getName());
}
Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • Can do with `Geography geo = new Geography<>();` as well. I also think the foreach loop in Java is called an enhanced for loop. – Unihedron Oct 10 '14 at 02:53
  • It's called a "for-each loop" in the [official documentation](http://docs.oracle.com/javase/1.5.0/docs/guide/language/foreach.html) and colloquially – Bohemian Oct 10 '14 at 03:16
1

Creating instances from Generic Types at Runtime

I am not entirely clear on what you are trying to accomplish, but at first look it looks like the simplest solution is the best solution.

It could be solved with using a scripting environment ( Groovy, JavaScript, JRuby, Jython ) that could dynamically evaluate and execute arbitrary code to create the objects, but that got extremely convoluted and overly complex, just to create an object.

But unfortunately I think it has a very pedestrian solution.

As long as there is a predefined set of supported types, you can use a Factory pattern. Here I just leverage the Provider<>T interface from the javax.inject/com.google.inject package.

Q26289147_ProviderPattern.java

public class Q26289147_ProviderPattern
{
    private static final List<String> CLASS_NAMES = ImmutableList.of("String", "Integer", "Boolean");
    private static final Map<String, Provider<StrawManParameterizedClass>> PROVIDERS;

    static
    {
        final ImmutableMap.Builder<String, Provider<StrawManParameterizedClass>> imb = ImmutableMap.builder();
        for (final String cn : CLASS_NAMES)
        {
            switch (cn)
            {
                case "String":
                    imb.put(cn, new Provider<StrawManParameterizedClass>()
                    {
                        @Override
                        public StrawManParameterizedClass<String> get() { return new StrawManParameterizedClass<String>() {}; }
                    });
                    break;
                case "Integer":
                    imb.put(cn, new Provider<StrawManParameterizedClass>()
                    {
                        @Override
                        public StrawManParameterizedClass<Integer> get() { return new StrawManParameterizedClass<Integer>() {}; }
                    });
                    break;
                case "Boolean":
                    imb.put(cn, new Provider<StrawManParameterizedClass>()
                    {
                        @Override
                        public StrawManParameterizedClass<Integer> get() { return new StrawManParameterizedClass<Integer>() {}; }
                    });
                    break;
                default:
                    throw new IllegalArgumentException(String.format("%s is not a supported type %s", cn, Joiner.on(",").join(CLASS_NAMES)));
            }
        }
        PROVIDERS = imb.build();
    }

    static <T> void read(@Nonnull final StrawManParameterizedClass<T> smpc) { System.out.println(smpc.type.toString()); }

    static abstract class StrawManParameterizedClass<T>
    {
        final TypeToken<T> type = new TypeToken<T>(getClass()) {};

        @Override
        public String toString() { return type.getRawType().getCanonicalName(); }
    }

    public static void main(final String[] args)
    {
        for (final String cn : CLASS_NAMES)
        {
            read(PROVIDERS.get(cn).get());
        }
    }
}

Disclaimer:

This is just a proof of concept example, I would never use a switch statement like that in production code I would use a Strategy Pattern or Chain of Responsibility Pattern to encapsulate the logic of what type to create based on the ClassName key.

This initially looked like a generics problem, it isn't, it is a creation problem.

That said, you don't need to pass around instances of Class<?> you can get Generic Type information off of Parameterized classes at runtime with TypeToken from Guava.

You can even create instances of any generic type at runtime with TypeToken from the Guava library.

The main problem is this syntax isn't supported: Geography<myClass.newInstance()> geo; and I can't think of anyway to fake it other than the Provider implementation above.

Here is a straw man example of how to use TypeToken so that your parameterized classes will always know their types!

Q26289147.java

import com.google.common.reflect.TypeToken;

public class Q26289147
{
    public static void main(final String[] args) throws IllegalAccessException, InstantiationException
    {
        final StrawManParameterizedClass<String> smpc = new StrawManParameterizedClass<String>() {};
        final String string = (String) smpc.type.getRawType().newInstance();
        System.out.format("string = \"%s\"",string);
    }

    static abstract class StrawManParameterizedClass<T>
    {
        final TypeToken<T> type = new TypeToken<T>(getClass()) {};
    }
}

Notes:

  1. Works great for classes that have a default no arg constructor.
  2. Works better than using straight reflection if there are no default no arg constructors.
  3. Should play well with Guice allowing you to use the ".getRawType()generatedClassto pass togetInstance()` of an Injector. have not tried this yet, I just thought of it!
  4. You can use Class<T>.cast() to do casting that doesn't need @SuppressWarning("unchecked") all over the place.`
0

You can create a static factory method in Geography (or in any other class):

public static <T> Geography<T> newInstance(Class<T> cls)
throws ReflectiveOperationException {
    return new Geography<T>(cls.newInstance());
}

I made a guess at the Geography class's constructor. If I guessed wrong, edit your question to include the constructor(s) in Geography.

You can create a static factory method in Geography (or in any other class):

public static <T> Geography<T> newInstance(Class<T> cls)
throws ReflectiveOperationException {
    return new Geography<T>(cls.newInstance());
}

I made a guess at the Geography class's constructor. If I guessed wrong, edit your question to include the constructor(s) in Geography.

Update: I'm not sure what the Geography class is meant to do. If it needs a generically typed object, it might look like this:

public class Geography<T> {
    private final T data;

    public Geography(T data) {
        this.data = Objects.requireNonNull(data);
    }
}

If it needs a class, the constructor might look like this:

public class Geography<T> {
    private final Class<T> dataClass;

    public Geography(Class<T> cls) {
        this.dataClass = Objects.requireNonNull(cls);
    }
}
VGR
  • 40,506
  • 4
  • 48
  • 63
  • Thanks for the answer. Actually there is no parameterized constructor in the Geography class's constructor. And the standard constructor doesn't do anything. How should the constructor look like so that I could use the Class to specify T? – Johannes Oct 10 '14 at 00:05