Что делать, если нужно вызвать метод объекта, о котором мы почти ничего не знаем: ни название, ни его методы (на самом деле знаем, но об этом ниже).
Содержание
- Постановка задачи
- Варианты неверных решений
- Идея решения
- Решение
- Использование библиотеки
- Дополнение
Постановка задачи
Есть некоторый класс какой-нибудь библиотеки, который мы сейчас проектируем, LibraryClass с методом doIt, который принимает произвольный объект.
1 2 3 4 5 |
class LibraryClass { void doIt(Object object) { } } |
Мы знаем, что у принимаемого объекта возможно есть какой-то нужный нам метод, который возвращает строку String. Имя метода мы не знаем.
Точнее мы не знаем это на этапе разработки нашей библиотеки. А на этапе разработки передаваемого класса мы это знать будем.
Нам нужно вызывать этот метод, вытащить оттуда возвращаемую строчку и вывести на экран
1 2 3 4 5 6 7 |
class LibraryClass { void doIt(Object object) { String s = ""; //Вызвать как-то метод и закинуть значение в s System.out.println(s); } } |
Варианты неверных решений
Прошу обратить внимание на то, что это решения неверные строго с точки зрения поставленной задачи. Если на практике возникнет подобная (не такая же) задача, то лучше попробовать какое-нибудь из «неверных» решений.
Мы могли бы обязать передавать объект в doIt только как наследника абстрактного класса с нужным нам методом.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
abstract class Parent { abstract String method(); } class LibraryClass { void doIt(Parent object) { String s = ""; s = object.method(); System.out.println(s); } } class Child extends Parent { @Override String method() { return "Bla"; } } LibraryClass library = new LibraryClass(); library.doIt(new Child()); |
Или через интерфейс.
1 2 3 |
interface Parent { String method(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class LibraryClass { void doIt(Parent object) { String s = ""; s = object.method(); System.out.println(s); } } class Child implements Parent { @Override public String method() { return "Bla"; } } LibraryClass library = new LibraryClass(); library.doIt(new Child()); |
Или даже через указание имени метода.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
class LibraryClass { void doIt(Object object) { String s = ""; Class classObject = object.getClass(); for (Method method : classObject.getDeclaredMethods()) { String nameMethod = method.getName(); if (nameMethod.equals("method")) { try { s = (String) method.invoke(object); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } System.out.println(s); } } class Child { String method() { return "Bla"; } } LibraryClass library = new LibraryClass(); library.doIt(new Child()); |
Но все эти способы подразумевают, что мы знаем, как называется метод, который нам нужен: либо заставляя нужный метод реализовать, либо перебирая все методы в поисках нужного имени. Но по условии задачи мы это не знаем.
То есть все эти способы не подойдут. Кстати, последний пример содержит много кода, который мы использует в решении с аннотацией.
Идея решения
А давайте мы в классе, объект которого будем передавать библиотеке, метод, возвращающий нужную нам строку, просто пометим. Эту метку и будет искать библиотечный класс для вызова метода.
Решение
Создадим аннотацию NeedMethod (имя взято произвольно), которая будет представлять собой метку. Причем аннотация будет являться частью библиотеки.
1 2 3 4 |
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface NeedMethod { } |
@Target(ElementType.METHOD) — означает, что метка будет действовать на метод.
@Retention(RetentionPolicy.RUNTIME) — означает, что метка будет работать и при выполнении программы. Без этой строчки наш способ работать не будет — можете проверить.
Теперь библиотечный класс пропишем такой.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
class LibraryClass { void doIt(Object object) { String s = ""; Class classObject = object.getClass(); for (Method method : classObject.getDeclaredMethods()) { NeedMethod annotation = (NeedMethod) method.getAnnotation(NeedMethod.class); if (annotation != null) { try { s = (String) method.invoke(object); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } System.out.println(s); } } |
Разберем его.
Java, в отличии от того же C++ (на самом деле что-то можно и в C++, но в ограниченном виде), позволяет узнавать информацию о классе объекта: имя, его методы и так далее. И мы из присылаемого объекта достаем информацию о классе.
1 |
Class classObject = object.getClass(); |
Теперь мы можем в цикле пробежаться по всем методам класса (наследуемые методы не учитываются).
1 2 |
for (Method method : classObject.getDeclaredMethods()) { } |
Теперь у каждого метода попытаемся вытащить аннотацию и привести её к типу нашей аннотации NeedMethod.
1 |
NeedMethod annotation = (NeedMethod) method.getAnnotation(NeedMethod.class); |
Если нужной отметки нет, то annotation будет приравнен к null. Что и используем
1 2 3 4 5 |
NeedMethod annotation = (NeedMethod) method.getAnnotation(NeedMethod.class); if (annotation != null) { } |
Теперь внутри этого условия переменная method содержит нужный нам метод. И нам нужно его вызвать. Это можно сделать через invoke, указывая кто вызывает этот метод.
1 |
s = (String) method.invoke(object); |
Обратите внимание на интересное поведение. В обычных случаях мы у объекта вызываем метод, а тут, наоборот, методу говорим, кто его будет вызывать.
Так как не факт, что метод нашелся (это мы знаем, но Java не знает), то обрамляем вызов метода в try catch.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
Class classObject = object.getClass(); for (Method method : classObject.getDeclaredMethods()) { NeedMethod annotation = (NeedMethod) method.getAnnotation(NeedMethod.class); if (annotation != null) { try { s = (String) method.invoke(object); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } |
Использование библиотеки
Класс библиотеки с аннотацией написаны. Теперь можем проверить в деле.
Создадим класс, например, WorkClass.
1 2 3 4 5 6 7 8 9 |
class WorkClass { String write() { return "text"; } String writeOther() { return "bla"; } } |
Создадим экземпляр нашего класса WorkClass, библиотечного класса LibraryClass и вызовем метод doIt.
1 2 3 |
WorkClass workObject = new WorkClass(); LibraryClass library = new LibraryClass(); library.doIt(workObject); |
И в консоли ничего не появилось. Что правильно.
Поставим отметку, то есть аннотацию @NeedMethod, около метода write.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
package com.example; import java.lang.annotation.*; import java.lang.reflect.*; public class Main { @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface NeedMethod { } public static void main(String[] args) { class WorkClass { @NeedMethod String write() { return "text"; } String writeOther() { return "bla"; } } class LibraryClass { void doIt(Object object) { String s = ""; Class classObject = object.getClass(); for (Method method : classObject.getDeclaredMethods()) { NeedMethod annotation = (NeedMethod) method.getAnnotation(NeedMethod.class); if (annotation != null) { try { s = (String) method.invoke(object); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } System.out.println(s); } } WorkClass workObject = new WorkClass(); LibraryClass library = new LibraryClass(); library.doIt(workObject); } } |
Вот теперь на экране высветится text.
Поставим отметку около метода writeOther, и высветится bla.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
package com.example; import java.lang.annotation.*; import java.lang.reflect.*; public class Main { @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface NeedMethod { } public static void main(String[] args) { class WorkClass { String write() { return "text"; } @NeedMethod String writeOther() { return "bla"; } } class LibraryClass { void doIt(Object object) { String s = ""; Class classObject = object.getClass(); for (Method method : classObject.getDeclaredMethods()) { NeedMethod annotation = (NeedMethod) method.getAnnotation(NeedMethod.class); if (annotation != null) { try { s = (String) method.invoke(object); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } System.out.println(s); } } WorkClass workObject = new WorkClass(); LibraryClass library = new LibraryClass(); library.doIt(workObject); } } |
Дополнение
Усложненный пример. Можно не читать.
В примере ниже я в метку ввел параметр type, который по умолчанию равен 1. А в библиотечном классе поставил проверку, что нам нужен метод со значением параметра равным 2 (просто для демонстрации). Ввел также второй класс, в котором метку поставил со значением 2. В итоге, в первом классе метка не сработает, так как значение в метке равно 1, а во втором классе сработает.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
package com.example; import java.lang.annotation.*; import java.lang.reflect.*; public class Main { @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface NeedMethod { int type() default 1; } public static void main(String[] args) { class WorkClass { String write() { return "text"; } @NeedMethod String writeOther() { return "bla"; } } class WorkClassTwo { String write() { return "TEXT"; } @NeedMethod (type = 2) String writeOther() { return "BLA"; } } class LibraryClass { void doIt(Object object) { String s = ""; Class classObject = object.getClass(); for (Method method : classObject.getDeclaredMethods()) { NeedMethod annotation = (NeedMethod) method.getAnnotation(NeedMethod.class); if ((annotation != null) && (annotation.type() == 2)) { try { s = (String) method.invoke(object); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } System.out.println(s); } } WorkClass workObject = new WorkClass(); WorkClassTwo workObjectTwo = new WorkClassTwo(); LibraryClass library = new LibraryClass(); library.doIt(workObject); library.doIt(workObjectTwo); } } |
Вот и всё, что хотел рассказать.
P.S. Жаль, что нельзя аннотацию прикреплять к методу динамически, а не в коде, как мы делали.