2

I want to create dynamic object so that I call the respective method of the class. All classes and interface is in different file but under the same folder Given:

interface Method
{
    public void display();
}
class Car implements Method
{
     public void display()
     {
       System.out.print("Car method");
     }
}
class Honda implements Method
{
     public void display()
     {
       System.out.print("Honda method");
     }
}

public class Main {

public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
    String className = "Car";
    Class cls = Class.forName(className);
    Method method = (Method) cls.getConstructor().newInstance();
    method.display();
  }
}

Now if pass Honda in the string then I want to string method to get called but if I pass Car in string then I want to get Car method as an output but after compilation this method is not getting called. There is no error but no expected output as well. How to get the desired output. Please help.

sakshi singhal
  • 101
  • 2
  • 9
  • The best option would be to define an interface that defines the `display()` method. Let `Car` and `Bicycle` implement this interface and then, after instantiating the object either via `newInstance()` for the default constructor or the `Constructor` class for non-default constructors, cast the generated object to that interface and use it as a regular object of that type. Note, instead of pure reflection you might also dive into [MethodHandle](https://gist.github.com/thomasdarimont/974bf70fe51bbe03b05e) which is a bit more light-weight in comparison to reflection. – Roman Vottner May 28 '21 at 10:52
  • 2
    Does this answer your question? [Casting a class using reflection in java](https://stackoverflow.com/questions/17321521/casting-a-class-using-reflection-in-java) – Joe May 28 '21 at 10:54
  • Warning: you are using the *raw type* `Class`. Don't use raw types, always provide the necessary type arguments. – MC Emperor May 28 '21 at 10:59
  • @RomanVottner can you please go through the question again. I have made edited the code. – sakshi singhal May 28 '21 at 13:07
  • 2
    First, for clarity I'd name the interface `Displayable` as this is what it basically provides. Next, "Honda" is not a class name. It is a vehicle name. You need to provide the fully-qualified class name to `Class.forName(...)` in order for it to load the actual class definition and create a class object for it. If you'd utilze a package structure like `org.acme.Car` i.e. you need to invoke `Class.forName("org.acme.Car")` or for the default package simply `Class.forName("Car")`. But `Honda` is not a class name, at least not present in your code – Roman Vottner May 28 '21 at 13:15
  • Also, unless you want to load class definitions dynamically later on while your application is running, i.e. as part of a plugin or addon, I'd avoid instantiating objects that way and simply fall back to the more easier to read and robust `new Car()` version. If you want to dynamically create such objects, you'd have an if/else or switch/case statement somewhere that checks the input and then creates the respetive objects directly – Roman Vottner May 28 '21 at 13:19
  • @RomanVottner I had replace Honda with Car and all the three classes are present in the same folder. The thing is which class's method will be called is not known in advance. So I can't write new Car() – sakshi singhal May 28 '21 at 13:28
  • 1
    At some point you do know what object you want/need to create, and this is the moment when you initialize it. `Class.forName(...)` only needs to be used if the class definition is not part of the application class path and thus the bytes of the class not known to the application class loader or any of its parents who are asked to provide the class object for that class. `new XYZ(...)` is basically a short-cut for that whole boilerplate code you need to write to instantiate an object of a class if targeting the application class loader – Roman Vottner May 28 '21 at 13:41
  • This introduces a large security risk! Anyone with access to that external file where the class-names are stored will be able to let you construct any class that is available in your runtime. You should rather build an if-else or switch-case to handle each class explicitly. If not that, AT LEAST define a white-list of class-names that you check against before loading the class – Felix May 28 '21 at 13:45
  • IMO, dynamically loading class definitions or using reflection to instantiate objects of a certain class is not the right scope for new Java developers. I.e. at this point we don't know if you have the code in separate java files, have compiled them to respective .class files and invoked the java process correctly and added the class to the class path. My guess is that the problem is somewhere in that uncertainty. You didn't even add the exception you are faced with and so on. Good luck on your journey though – Roman Vottner May 28 '21 at 14:02

2 Answers2

2

The method class.newInstance() has been deprecated. So you should use

Class<?> clazz = Class.forName(className);
Constructor<?> ctor = clazz.getConstructor(String.class).newInstance();
Object object = ctor.newInstance(ctorArgument);

SN: In this case I assumed the constructor had only one String parameter value. the getConstructor method must be called passing all the class types (correctly ordered) that the desired constructor has. While with newInstance you need to pass the actual constrctor's args' values

At this point you have to get the method with getMethod() which requires both the method name and all the arguments' types. To actually invoke the method, you pass the instance of the object of which to call the method (in this case our object and passing the actual parameters' values to call it with

clazz.getMethod("methodName", String.class, String.class).invoke(object, "parameter1", "parameter2");

EDIT: The OP updated the question implementing a common interface by both classes. In this case you can actually call the method that you KNOW will have every class implementing that interface. This way you don't have to use any reflections magic except for creating the new instance of the object itself

With the common interface it'd be

Method method = (Method) Class.forName(className).getConstructor().newInstance();
method.display();

The last line will call the display method of the object instance that is implementing the interface

L_Cleo
  • 1,073
  • 1
  • 10
  • 26
  • Is writing Constructor> ctor = clazz.getConstructor(String.class).newInstance(); compulsory? Instead I created the Method object and put the same into it but then after calling the display method, I am not getting any error but no required outptu also. – sakshi singhal May 29 '21 at 06:57
  • If you look at the code I have made some changes to it. So can you please provide your explanation acc. to it. – sakshi singhal May 29 '21 at 07:06
  • It's not compulsory but it's common use to not use any deprecated Classes/methods since at some point in the near future they could be dropped. Also usually if it is deprecated it's because it has been replaced usually by a more performing/complete alternative – L_Cleo May 30 '21 at 07:09
1

You can invoke the desired method.

Method method = cls.getMethod("display");
method.invoke(parameters);

If I am allowed to update above code, it would be something like below,

interface Car
{
    public void display();
}
class Honda implements Car
{
     public void display()
     {
       System.out.print("Car method");
     }
}

public class Main {

public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
    String className = "Car";
    Class cls = Class.forName(className);
    Honda honda = (Honda)cls.newInstance()
    honda.display();
}
}

Hopefully this will clear the things about the Method class I mentioned in above answer.

pratap
  • 538
  • 1
  • 5
  • 11
  • What do this Method class will do? Parameters list is also not known in advance. – sakshi singhal May 28 '21 at 10:56
  • You can use this ``Method`` class to invoke that method. It will also return the parameters which are expected in the method using ``method.getParameterTypes()``. It returns the array of objects parameter types. More information on this can be found here https://docs.oracle.com/javase/7/docs/api/java/lang/reflect/Method.html – pratap May 28 '21 at 12:04
  • Note that his interface is named `Method`. His declaration of `Method method = ...` is not referring to `java.lang.reflect.Method` but to his own `Method`-interface – Felix May 28 '21 at 13:41
  • 1
    @codeflush.dev question was later updated with these details. Let me update my answer accordingly. – pratap May 28 '21 at 14:06