1

I am trying to instantiate a class given a String in Java. Something like this:

// Pseudocode
String name = "MyClass";
SomeClass<name> sc = new SomeClass<name>(parameter);

In my case, the constructor takes a Class<E> type. So I would pass MyClass.class. Ideally, take the string and append .class. It would turn out like this:

SomeClass<name> sc = new SomeClass<name>(name.class);

I was looking at the answer here: Create new class from a Variable in Java but could not get it to work. Using the info from that link, I tried this:

String name = "MyClass";
Class className = Class.forName(name);
className param1;
Constructor con = className.getConstructor(className.class);
SomeClass<className> sc = con.newInstance(param1);

Of course, this doesn't work. How would I go about achieving this?

Community
  • 1
  • 1
syy
  • 687
  • 2
  • 13
  • 32
  • what is the signature of your target constructor? – Nicolas Filotto Jun 01 '16 at 14:01
  • Method signature is: `public SomeClass(Class type) {}` – syy Jun 01 '16 at 14:02
  • Well, seriously: you study the excellent tutorial from Oracle on how to do reflection. And then you keep trying. The thing is: reflection is awfully complicated. But when you just pick up some code somebody else wrote for you, you will not be able to grasp that. Meaning: all of this is documented; you will learn more by studying those sources; than from asking other people to explain it to you. – GhostCat Jun 01 '16 at 14:03
  • Can you explain why you think you need this? – Sotirios Delimanolis Jun 01 '16 at 14:33
  • @SotiriosDelimanolis I am doing some automation testing with some XML files. There are a lot of classes for unmarshalling, so I wanted a way for a developer to just pass the name of the class they wanted to unmarshal the XML to. It saves a lot of coding and modifying. – syy Jun 01 '16 at 14:44

3 Answers3

3

Generics are mostly a compile-time tool helping the compiler spot casting errors. Due to type erasure most of the generic information is lost at runtime - besides some basic information in reflection data (which doesn't really help you here).

Let's assume your constructor looks like this:

public SomeClass( Class<T> param ) { ... }

Due to type erasure at runtime it looks like this (when being called):

public SomeClass( Class param ) { ... }

As you can see there's no generic information so to get it you'd use SomeClass.getConstructor( Class.class ).

Then you can invoke it using newInstance( Class.forName("java.lang.String") ). Again you see that you're basically able to pass in any class as a parameter which is due to generics being a compile-time tool, i.e. the compiler applies those checks while the runtime trusts the compiled code.

If you need to employ any boundaries you'd need to either use a different approach or check for the correct class being passed inside the constructor (e.g. by using the method Class#isAssignableFrom()).

When assigning the created instance to a variable you need to use a wildcard, i.e. SomeClass<?> sc, or a raw type, i.e. SomeClass sc (I'd suggest using the wildcard). Of course the compiler then can't allow you to call any method that is only present in some classes (e.g. in MyClass) since it simply doesn't know (and it can't know the class definition from just a name).

Edit:

In the hope of being able to clarify the last part I'll try and add another example.

Assume MyClass had a method foo() and SomeClass has a method T getTypeInstance(). If your variable type would be SomeClass<MyClass> sc you could do a call sc.getTypeInstance().foo() since the compiler knows that T is bound to MyClass.

However, when using reflection that information is lost, i.e. since you'd have to use SomeClass<?> sc the compiler would not know the return type of getTypeInstance() and thus wouldn't allow calls to foo().

If you know that all classes you could pass as a parameter implement the same interface you could do something like SomeClass<CommonInterface> sc which would compile since Constructor#newInstance() returns a raw type and thus compiler checks are disabled here. In that case you could add foo() to CommonInterface and be able to call it - at least unless you break the code.

The problem with the above is that you could also pass String.class to the constructor, which the compiler and runtime would allow but which is still wrong since String doesn't implement CommonInterface. Hence you'd get exceptions when trying to create the instance, cast it to CommonInterface or call foo() on it - unless you add a check in your constructor that rejects any parameter that isn't a class being assignable to CommonInterface (e.g. by checking CommonInterface.class.isAssignableFrom( param )).

Thomas
  • 87,414
  • 12
  • 119
  • 157
  • Thanks, this was really helpful. So from what you are saying is that the compiler won't know the methods if I continue to use reflection? Sorry if I misunderstood. – syy Jun 01 '16 at 14:42
  • @Flow I'm not sure I understand your question correctly so I'll add an update on how I interpret your question. I hope that makes it clearer. – Thomas Jun 01 '16 at 14:59
  • I'm not sure what you mean by `Class#isAssignableFrom()` or `Constructor#newInstance()`. I have never seen that notation before. In any case, I do know all the classes that can be passed through. However, there are way too many methods that I would need to put in the interface. I'm using this for unmarshalling XML files so there will be a lot of classes and methods. Thank you though, I just need to think of a new way to achieve my goal. – syy Jun 01 '16 at 15:43
  • 1
    @Flow `Class#isAssignableFrom(...)` is JavaDoc notation (or at least similar) meaning method `isAssignableFrom(...)` in class `Class`. I normally use it to not confuse it with static methods, e.g. `String.valueOf(...)` would mean the static method in class `String` while `String#length()` would be the method `length()` without telling whether it is static or not. – Thomas Jun 02 '16 at 11:03
1

Assuming your generic class looks something like this:

public GenericClass(Class<T> param) {
    System.out.println(param.getName());
}

Your code could look like this:

@SuppressWarnings("unchecked")
public static void main(String[] args) throws Exception {
    String type = "java.lang.String";
    Class g = GenericClass.class;
    Constructor c = g.getConstructor(Class.class);
    GenericClass<?> genericClass = (GenericClass<?>) c.newInstance(Class.forName(type));
}

And you'll see in the console that it prints "java.lang.String".

This works because in reflection, everything must have a type, and since generics have no type in compilation, it shows up as java.lang.Class, not java.lang.Class<T>. So, to get the constructor, you must tell it it find the constructor taking a class object. Then, simply use Class.forName to find the class in type, and call the constructor, passing in that class.

Universal Electricity
  • 775
  • 1
  • 12
  • 26
  • I get `ClassNotFoundException` when I do this. I'm assuming it's because of the above explanation by Thomas. Trying to call a method from `GenericClass` can't be done because it doesn't know the class during compile time. – syy Jun 01 '16 at 14:53
0

I see some errors in your code:

What is: className param1; ? You can't use className that is an object of class Class as a type.

The invokation:

Constructor con = className.getConstructor(className.class);

returns a constructor of the class "className" with a parameter of type "className". Is that your request? (For example if your class is ArrayList is the constructor taking 1 argument of type ArrayList)

The code:

SomeClass<className> sc = con.newInstance(param1);

Is syntactically not correct. The method newInstance returns an Object of type T. Try to assign it to a variable of type Object.


Here is how your code should seems:

String name = "MyClass";
Class className = Class.forName(name);
String param1 = "something";
// Using a constructor that use a String as parameter
Constructor con = className.getConstructor(String.class); 
Object sc = con.newInstance(param1);
Davide Lorenzo MARINO
  • 26,420
  • 4
  • 39
  • 56
  • I was following the code in the linked post. I thought `Param1Type` was the class you wanted the parameter to be since, in my code, `Param1Type` could not be resolved. I want to do this basically: `SomeClass sc = new SomeClass(AnotherClass.class)`. `AnotherClass` would be the given String. – syy Jun 01 '16 at 14:09
  • Yes.... you are right. Thank you Sotirios. – Davide Lorenzo MARINO Jun 01 '16 at 14:53