Вызов наиболее подходящего метода - PullRequest
5 голосов
/ 09 февраля 2010

В рамках разработки небольшого ScriptEngine я рефлексивно вызываю методы java. Вызов обработчиком сценариев дает мне имя объекта и массив аргументов. Чтобы вызвать метод, я попытался разрешить его с помощью вызова Class.getMethod (имя, типы аргументов).
Это, однако, работает только тогда, когда классы аргументов и ожидаемые методом классы совпадают.

Object o1 = new Object();
Object out = System.out;
//Works as System.out.println(Object) is defined
Method ms = out.getClass().getMethod("println",o1.getClass());
Object o2 = new Integer(4);
//Does not work as System.out.println(Integer) is not defined
Method mo = out.getClass().getMethod("println",o2.getClass());

Я хотел бы знать, существует ли "простой" способ получить правильный метод, если это возможно, с наиболее близким соответствием для типов аргументов, или я должен реализовать это сам.

Ближайшая подгонка будет:

Object o1 = new Integer(1);
Object o2 = new String("");
getMethod(name, o1.getClass())//println(Object)
getMethod(name, o2.getClass())//println(String)  

Обновление:
Чтобы уточнить, что мне нужно: Скриптовый движок - это небольшой проект, который я пишу в свободное время, поэтому нет никаких правил стрикта, которым я должен следовать. Поэтому я подумал, что выбор методов, вызываемых из Engine, так же, как компилятор java выбирает методы во время компиляции только с динамическим типом, а не статическим типом Object, будет работать (с автобоксом или без)
Это то, на что я впервые надеялся, что Class.getMethod () решит проблему. Но для Class.getMethod () требуются те же классы, что и для типов аргументов, которые объявляет метод, при использовании подкласса исключение метода не будет. Это может происходить по уважительным причинам, но делает метод бесполезным для меня, поскольку я заранее не знаю, какие типы аргументов подойдут.
Альтернативой было бы вызвать Class.getMethods () и выполнить итерацию по возвращенному массиву и попытаться найти подходящий метод. Однако это будет сложно, если я не просто захочу воспользоваться первым «хорошим» методом, с которым я столкнусь, поэтому я надеялся, что будет существующее решение, которое хотя бы обрабатывает:

  • наиболее близкое соответствие: если arg.getClass () == подкласс и методы m (суперкласс), m (подкласс), затем вызвать m (подкласс)
  • переменные аргументы: System.out.printf (String, String ...)

Было бы неплохо также поддерживать автобокс.
Если вызов не может быть разрешен, он может выдать исключение (ma (String, Object), ma (Object, String), args = String, String)
(Если вы сделали это до здесь, спасибо, что нашли время, чтобы прочитать это: -))

Ответы [ 3 ]

2 голосов
/ 09 февраля 2010

Я бы предложил вам использовать getMethods(). Возвращает массив всех открытых методов (Method[]).

Самое важное здесь:
" Если класс объявляет несколько открытых методов-членов с одинаковыми типами параметров, они все включаются в возвращаемый массив. "

Затем вам нужно будет использовать результаты в этом массиве, чтобы определить , какой из них (если есть) является ближайшим совпадением. Поскольку наиболее близкое совпадение во многом зависит от ваших требований и конкретного приложения, имеет смысл написать его самостоятельно.


Пример кода, иллюстрирующий один из подходов к этому:

public Method getMethod(String methodName, Class<?> clasz)
{
    try
    {
        Method[] methods = clasz.getMethods();
        for (Method method : methods)
        {
            if (methodName.equals(method.getName()))
            {
                Class<?>[] params = method.getParameterTypes();
                if (params.length == 1)
                {
                    Class<?> param = params[0];
                    if ((param == int.class) || (param == float.class) || (param == float.class))
                    {
                        //method.invoke(object, value);
                        return method;
                    }
                    else if (param.isAssignableFrom(Number.class))
                    {
                        return method;
                    }
                    //else if (...)
                    //{
                    //    ...
                    //}
                }
            }
        }
    }
    catch (Exception e)
    {
        //some handling
    }
    return null;
}

В этом примере метод getMethod(String, Class<?>) вернет метод, который имеет только один параметр: int, float, double или суперкласс Number.

Это элементарная реализация - она ​​возвращает первый метод, который соответствует требованиям. Вам потребуется расширить его, чтобы создать список всех подходящих методов, а затем отсортировать их по какому-либо критерию и вернуть метод наилучшего соответствия.

Затем вы можете пойти еще дальше, создав более общий метод getMethod(String, Class<?>) для обработки большего количества возможных сценариев "близкого совпадения" и, возможно, даже более одного параметра

НТН


Редактировать: Как указывал @finnw, будьте осторожны при использовании Class#isAssignableFrom(Class<?> cls) из-за его ограничений, как я указал в моем примере кода, при тестировании примитивов отдельно от объектов Number.

2 голосов
/ 09 февраля 2010

Как уже отмечали другие, нет стандартного метода, который делает это, поэтому вам придется реализовать собственный алгоритм разрешения перегрузки.

Возможно, имеет смысл как можно точнее следовать правилам разрешения перегрузки javac:
http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#292575
Возможно, вы можете игнорировать обобщенные значения для языка сценариев с динамической типизацией, но вы все равно можете воспользоваться методами моста , которые компилятор генерирует автоматически.

Некоторые подводные камни, на которые стоит обратить внимание:

  • Class.isAssignableFrom не знает об автоматическом расширении примитивных преобразований, потому что это синтаксический сахар, реализованный в компиляторе; Они не встречаются в VM или иерархии классов. например int.class.isAssignableFrom(short.class) возвращает false.
  • Аналогично Class.isAssignableFrom не знает об автобоксе. Integer.class.isAssignableFrom(int.class) возвращает false.
  • Class.isInstance и Class.cast принимают Object в качестве аргумента; Вы не можете передать им примитивные значения. Они также возвращают Object, поэтому их нельзя использовать для распаковки ((int) new Integer(42) допустимо в исходном коде Java, но int.class.cast(new Integer(42)) вызывает исключение.)
1 голос
/ 09 февраля 2010

AFAIK, не существует простого способа сделать подобные вещи. Конечно, в стандартных библиотеках классов Java нет ничего, что могло бы сделать это.

Проблема в том, что нет единого «правильного» ответа. Вам необходимо рассмотреть все ваши варианты использования, решить, каким должен быть «правильный метод», и соответствующим образом реализовать код отражения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...