#java #android
В текущем классе необходимо программно определять имя другого класса (вида "ClassNumPath + i" ) и далее вызывать его методы. Файлы классов ClassNumPath0.java (а так же ClassNumPath1.java .. ClassNumPath5.java) существуют и их методы вызываются без ошибок, если обращаться к ним в коде напрямую, например: ClassNumPath0.initPositions(); или ClassNumPath4.initPositions(); .. то никаких проблем нет - все работает как надо, отрисовка в onDraw производится и прочее... Но мне нужно определять имя класса ClassNumPathХ динамически и иметь возможность менять это имя имя класса по ходу действия. Часть кода над которым я бьюсь: 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(); // А это работает правильно ... } Итак, вопрос: Каким образом можно обращаться к методам класса, чье имя было задано динамически? Или, пожалуйста, подскажите вариант как по-другому решить задачу. Не хочется выстраивать большую конструкцию с операторами типа Case с выбором нужного имени класса в зависимости от значения currentNum, так как классов ClassNumPathХ может быть более 100 штук.
Ответы
Ответ 1
Начнем со строки Class cClass; Класс Class в Java представляет собой метаинформацию о некотором классе - о его полях, методах и прочем. У любого класса есть поле class, которое содержит экземпляр типа Class. Когда вы вызываете Class.forName вы создаете не экземпляр целевого класса, а экземпляр такого метаописания целевого класса. Поэтому вы и не можете вызвать ни initPositions(), ни checkState() - таких методов у Class попросту нет. Мы всегда можем создать новый экземпляр целевого класса из его метаописания, при помощи метода Class.newInstance(). Это выглядит так: Class clazz = Class.forName(currentClassName); Object a = clazz.newInstance(); Поскольку в момент написания кода и компиляции ни мы ни jvm еще не знает, что это будет за класс, мы вынуждены сохранить экземпляр в переменной типа Object. Однако это лишает нас возможности "дотянуться" до нужных методов. Решение 1. Грубо и "в лоб" Разберемся, что можно сделать. Из приведенного вами кода следует, что методы initPositions() и checkState() статические. Это не очень здорово, потому что единственный способ вызвать их - рефлексия и в общем случае в клиентском (не системном/библиотечном) коде не стоит так делать. Итак, нам нужно получить из экземпляра Class метаинформацию о методе initPositions() и заставить метод исполниться. Для этого у нас в распоряжении есть Class.getDeclaredMethod(), возвращающий экземпляр типа Method. В свою очередь, в классе Method есть метод invoke(Object obj, Object... args), позволяющий выполнить описываемый классом метод. Все вместе это будет выглядеть так: Class clazz = Class.forName(currentClassName); Method m = clazz.getDeclaredMethod("initPositions"); m.invoke(null); // параметр null - т.к. метод статический и не требует экземпляр класса Метод выполнится, но все это не очень здорово, т.к. мы напрямую работаем с кишками jvm. Решение 2. Пересмотр дизайна классов Вообще говоря, потребность вызывать методы классов через рефлексию должна натолкнуть на мысль в дизайне классов. Я не знаю, решаемой вами задачи, но для начала стоит избавиться от статических методов и вынести их в общий интерфейс. То есть, объявить интерфейс, что-то вроде: public interface IClassNumPath { void initPositions(); void checkState(); // ... другие методы общие для классов } и реализовать этот интерфейс в ваших классах ClassNumPath1, ClassNumPath2, и т.д. Теперь при создании экземпляра из Class мы сможем присвоить его переменной типа IClassNumPath и вызывать методы напрямую: Class clazz = Class.forName(currentClassName); IClassNumPath classNumPath = (IClassNumPath) clazz.newInstance(); classNumPath.initPositions(); // все честно и без рефлексии. Если статическое состояние крайне необходимо - рассмотрите возможность вынесения его в отдельный общий класс и передачу в экземпляры IClassNumPath через сеттер.Ответ 2
Благодарю @nofate за помощь! Я использовал предложенное им Решение №2 с объявлением интерфейса. Действительно, все методы в пронумерованных классах ClassNumPathX одинаковы и вовсе не обязаны быть статическими. Единственное замечание, в строке: IClassNumPath classNumPath = (IClassNumPath) clazz.newInstance(); Android Studio выдавал ошибку на IClassNumPath, поэтому я заменил его на название интерфейса ClassNumPath (без префикса I): В итоге решение получилось такое: В классе ClassNumPath объявил интерфейс NumPath с набором общих методов для всех нумерованных классов '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(); ... } } В самих нумерованных классах ClassNumPathХ даже и не пришлось ничего особо менять. Только добавил implements ClassNumPath.NumPath и @Override к методам и убрал из них static: public class ClassNumPath4 implements ClassNumPath.NumPath { ... @Override public void initPositions() { ... } @Override public void checkState() { ... } }
Комментариев нет:
Отправить комментарий