In the current class, you must programmatically determine the name of another class (of the form "ClassNumPath + i" ) and then call its methods.

ClassNumPath0.java class ClassNumPath0.java (as well as ClassNumPath1.java .. ClassNumPath5.java ) exist and their methods are called without errors if you directly access them in code, for example:

 ClassNumPath0.initPositions(); 

or

 ClassNumPath4.initPositions(); 

.. then there are no problems - everything works as it should, drawing on onDraw done and so on ... But I need to determine the class name ClassNumPathХ dynamically and be able to change this class name in the course of the action.

Part of the code over which I fight:

 public class ClassNumPath extends SurfaceView implements SurfaceHolder.Callback { ... int currentNum = 5; String currentClassName; Class cClass; ... @Override protected void onDraw(Canvas canvas) { if (needInitialazation) { currentClassName = "com.mytestapp.testapp.ClassNumPath" + currentNum; cClass = Class.forName(currentClassName); cClass.initPositions(); // Ошибка: Cannot resolve method 'initPositions()' ClassNumPath5.initPositions(); // А это работает правильно Log.d("INF", "currentClassName = " + currentClassName); Log.d("INF", "cClass = " + cClass); // Эти логи выдают правильное имя класса com.mytestapp.testapp.ClassNumPath5 } ... cClass.checkState(); // Ошибка: Cannot resolve method 'checkState()' ClassNumPath5.checkState(); // А это работает правильно ... } 

So the question is:

How can I access the methods of a class whose name was dynamically set?

Or, please, tell me how to solve the problem differently. I do not want to build a large structure with Case-type operators with the choice of the desired class name depending on the currentNum value, since the ClassNumPathХ classes can be more than 100 pieces.

  • Do they have a common interface? - etki
  • Not. What should be done? - Nesterov
  • Create an interface that all of these classes ClassNumPath1 - ClassNumPathN . Then, for example, put one object from each class into an array, then call myClasses[currentNum].initPositions() . It is possible to approach this decision more accurately, but "at least so." - Regent

2 answers 2

Let's start with the line

 Class cClass; 

The class Class in Java is meta information about a certain class — about its fields, methods, and so on. Any class has a class field that contains an instance of type Class .

When you call Class.forName you are not creating an instance of the target class, but an instance of such a meta description of the target class. Therefore, you cannot call initPositions() or checkState() - Class simply does not have such methods.

We can always create a new instance of the target class from its meta description, using the Class.newInstance() method. It looks like this:

 Class clazz = Class.forName(currentClassName); Object a = clazz.newInstance(); 

Since at the time of writing and compiling neither we nor jvm yet know what this class will be, we have to save an instance in a variable of type Object . However, this makes it impossible for us to “reach out” to the necessary methods.

Solution 1. Rough and "in the forehead"

We will understand what can be done. From the code you give, it follows that the initPositions() and checkState() methods are static. This is not very good, because the only way to cause them is reflection and, in general, in client (non-system / library) code you should not do so.

So, we need to get meta information from the Class instance about the initPositions() method and make the method execute. To do this, we have Class.getDeclaredMethod() , which returns an instance of the Method type. In turn, the Method class has the invoke(Object obj, Object... args) method invoke(Object obj, Object... args) , which allows you to perform the method described by the class. All together it will look like this:

 Class clazz = Class.forName(currentClassName); Method m = clazz.getDeclaredMethod("initPositions"); m.invoke(null); // параметр null - т.к. метод статический и не требует экземпляр класса 

The method will be executed, but all this is not very good, because we work directly with jvm guts.


Solution 2: Revise Class Design

Generally speaking, the need to invoke class methods through reflection should give rise to an idea in class design. I do not know the problem you are solving, but first you should get rid of static methods and put them into a common interface.

That is, declare an interface, something like:

 public interface IClassNumPath { void initPositions(); void checkState(); // ... другие методы общие для классов } 

and implement this interface in your ClassNumPath1 , ClassNumPath2 , etc. classes.

Now, when creating an instance from Class we will be able to assign it to a variable of type IClassNumPath and call methods directly:

 Class clazz = Class.forName(currentClassName); IClassNumPath classNumPath = (IClassNumPath) clazz.newInstance(); classNumPath.initPositions(); // все честно и без рефлексии. 

If the static state is extremely necessary - consider the possibility of putting it into a separate general class and passing it to the IClassNumPath instances through the setter.

  • Regarding the second decision, I am worried about at least three points: 1. Do not pass arguments to the object constructor. 2. Every time a new object is created - the state will not be saved. 3. There is no certainty that such a class (for example, ClassNumPath14 ) exists at all. If at the initial stage everything can be debugged so that there is no ClassNotFoundException , then with further changes in the code it is quite possible to forget to change the logic here, because there will not be any compilation errors. I'm not saying that this is not the answer to the question - the question is solved by both options. - Regent
  • I proceeded from the hypothesis that class names and their number are not known at the compilation stage. The comments are quite logical and follow from the very formulation of the question. - Nofate
  • @Nofate is very grateful for the hint! I used Solution # 2 with an ad interface. Indeed, all the methods in the numbered ClassNumPath classes ClassNumPath same and do not have to be static at all. The only remark in the line: IClassNumPath classNumPath = (ClassNumPath) clazz.newInstance (); Android Studio produced an error on IClassNumPath , so I replaced it with the name of the interface ClassNumPath (without the prefix I). - Nesterov

Thanks to @nofate for the help!
I used the solution No.2 proposed by him with the interface declaration.
Indeed, all methods in the numbered classes of ClassNumPathX same and are not required to be static.
The only remark in the line:

  IClassNumPath classNumPath = (IClassNumPath) clazz.newInstance(); 

Android Studio produced an error on IClassNumPath , so I replaced it with the name of the interface ClassNumPath (without the prefix I ):


As a result, the decision turned out this:

In the ClassNumPath class ClassNumPath declared the NumPath interface with a set of common methods for all the numbered classes' ClassNumPathX`

  public class ClassNumPath extends SurfaceView implements SurfaceHolder.Callback { ... public static NumPath cNumPath; ... // дать имя ClassNumPath интерфейсу внутри класса ClassNumPath уже нельзя public interface NumPath { void initPositions(); void checkState(); } ... @Override protected void onDraw(Canvas canvas) { if (needInitialazation) { currentClassName = "com.mytestapp.testapp.ClassNumPath" + currentNum; Class clazz = Class.forName(currentClassName); NumPath cNumPath = (NumPath) clazz.newInstance(); cNumPath.initPositions(); } ... // Поскольку cNumPath объявлен как public, то можно пользоваться им в любом месте cNumPath.checkState(); ... } } 

In the very numbered classes of ClassNumPathХ they didn’t even have to change anything much. Just added the implements ClassNumPath.NumPath and @Override to the methods and removed the static from them:

 public class ClassNumPath4 implements ClassNumPath.NumPath { ... @Override public void initPositions() { ... } @Override public void checkState() { ... } }