Harrix Блог

  • Списки статей
    • Все статьи
    • IT
    • Qt
    • C++
    • Сложение двух чисел
    • Web программированиe
    • FAQ
    • Latex
    • Установка программ
    • Мифы
    • Видео
    • Про фото
  • Проекты
  • Harrix.org
  • RSS
  • Контакты

Как вызвать метод класса из другого класса при недостатке информации через аннотации в Java

Один из примеров использования аннотации.

08.05.2017 Leave a Comment 10 694 просмотров

Что делать, если нужно вызвать метод объекта, о котором мы почти ничего не знаем: ни название, ни его методы (на самом деле знаем, но об этом ниже).

Содержание

  • Постановка задачи
  • Варианты неверных решений
  • Идея решения
  • Решение
  • Использование библиотеки
  • Дополнение

Постановка задачи

Есть некоторый класс какой-нибудь библиотеки, который мы сейчас проектируем, 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. Жаль, что нельзя аннотацию прикреплять к методу динамически, а не в коде, как мы делали.


Статьи по теме:

  1. Создание простейшего Java приложения в Eclipse
  2. Сложение двух чисел в Eclipse на Java (консольное приложение)
  3. Чтение и запись данных из файла в файл в консольном приложении под Java в IntelliJ IDEA
  4. Задача на Java: Список покупок

IT Java

© 2014 Harrix