Kelly
Kelly

Reputation: 649

Java Class.forName won't compile. Getting "cannot find symbol symbol : method"

I'm trying to use Class.forName and my Intellij is throwing a compile error. My IntelliJ highlights "theResponse" in red (in testMethod) and gives me this error:

cannot find symbol symbol : method

Here is the code (and test) I'm working with...

package http.response;

public class TestClass {
  public TestClass() {
    PublicRoute publicRoute = new PublicRoute();
  }

  public String testMethod() throws ClassNotFoundException {
    Class c = Class.forName("http.response.PublicRoute");
    return c.theResponse("hi");
  }

}

package http.response;

import org.junit.Test;

import static junit.framework.Assert.assertEquals;

public class TestClassTest {
  @Test
  public void test() throws ClassNotFoundException {
    TestClass testClass = new TestClass();
    assertEquals("public", testClass.testMethod());
  }
}

UPDATE: What I was trying to do was "polymorphically" call theResponse from the class that is returned as a String from a HashMap. How would I do this? I'm (loosely) following this example but I didn't understand it fully (http://sourcemaking.com/refactoring/replace-conditional-with-polymorphism). Here is a simplified version of what I'm trying to do. Hopefully that makes sense.

package http.response;

import java.util.HashMap;

public class TestClass {
  HashMap map;

  public TestClass(HashMap map) {
    this.map = map;
  }

  public String testMethod(String lookupValue) throws ClassNotFoundException {
    String className = map.get(lookupValue);
    Class c = Class.forName("http.response." + className);
    return c.theResponse();
  }

}

Upvotes: 4

Views: 6146

Answers (4)

MrRaymondLee
MrRaymondLee

Reputation: 536

Let me see if I understand what you're trying to do. You've got a hashmap that will contain a list of classes that you're going to try to call the theResponse(String response) method on, right? I'm assuming you won't know the String that will be put into the hashmap either, right?

Others are right in that you can't just do:

Class c = Class.forName("http.response.PublicRoute");
c.theResponse("hi"); // errors because c has no knowledge of theResponse()

You'll need to cast c to http.response.PublicRoute but then as @Ravi Thapliyal pointed out, you won't need Class.forName anymore! You've got a hashmap of names that could potentially be anything so this won't work.

If I'm understanding you correctly to do what you need, you'll need to use reflection in order to attempt to instance the class then call the method on it.

Here's how you'd do it assuming the theResponse method is a public non-static method and has only 1 parameter.

// Declare the parameter type
Class[] paramString = new Class[1]; 
paramString[0] = String.class;

String className = map.get(lookupValue);

// Instance the class
Class cls = Class.forName("http.response." + className);
Object obj = cls.newInstance();

// Call the method and pass it the String parameter
method = cls.getDeclaredMethod("theResponse", paramString);
method.invoke(obj, new String("hi"));

Of course you'll need to handle Exceptions but you'd surround the above code with the loop for your hashmap.

I hope this helps!

Upvotes: 1

Daniel Pryden
Daniel Pryden

Reputation: 60997

Class.forName() returns an object of type java.lang.Class. java.lang.Class has no method theResponse, as you can see from its Javadoc.

It sounds like what you actually want to do is construct an instance of the PublicRoute class, and call the method on the instance. But you've already constructed such an instance: it's the publicRoute variable you create in your constructor. Why not just use that object instead?


Edit: Ah, I see what you're trying to do. You basically want a form of the Service Locator pattern.

Create an interface, like so:

public interface ResponseProvider {
  String theResponse();
}

Then make all your classes implement that interface:

public class PublicRoute implements ResponseProvider {
  @Override
  public String theResponse() {
    // do whatever
  }
}

Then, when you load your Class<?>, you can use the asSubclass() method to turn your Class<?> into a Class<? extends ResponseProvider> -- then newInstance() will give you back a ResponseProvider object that you can call theResponse() on, like so:

String className = ...;
Class<?> klass = Class.forName(className);
Class<? extends ResponseProvider> responseProviderClass
    = klass.asSubclass(ResponseProvider.class);
ResponseProvider responseProvider = responseProviderClass.newInstance();
return responseProvider.theResponse();

But don't do that by hand -- instead, use the java.util.ServiceLoader class, which is designed for exactly this purpose. You create a special META-INF/services/com.my.package.ResponseProvider file, with a list of all the possible classes that implement that interface, and then ServiceLoader can give you back instances of each of them.

But... consider not doing that, either. The types of problems that you can solve with the Service Locator pattern are often better solved by using Dependency Injection (see also my answer to another question about Dependency Injection). The Guice DI framework, for example, offers a feature called multibindings which looks like exactly what you need.

Upvotes: 6

ruakh
ruakh

Reputation: 183602

The class Class does not have a method named theResponse. From the rest of your code, it doesn't look like you should be using reflection here; you're already referring statically to the PublicRoute class, so there's no point loading it dynamically.

I think you just want to write either this:

return PublicRoute.theResponse("hi");

or this:

return new PublicRoute().theResponse("hi");

(depending whether theResponse is a static method or an instance method).

Upvotes: 2

Ravi K Thapliyal
Ravi K Thapliyal

Reputation: 51721

If theResponse() belongs to http.response.PublicRoute then it should have been

Class c = Class.forName("http.response.PublicRoute");
return ((PublicRoute) c.newInstance()).theResponse("hi");

But, then there's really no need for Class.forName() as you could use constructor as

return new PublicRoute().theResponse("hi");

Upvotes: 2

Related Questions