Вызов наиболее подходящего метода

В рамках разработки небольшого 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)  

Обновить:
Чтобы уточнить, что мне нужно: Script Engine - это небольшой проект, который я пишу в свободное время, поэтому нет никаких правил стрикта, которым я должен следовать. Поэтому я подумал, что выбор методов, вызываемых из 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 ответа

Решение

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

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

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

  • 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)) выбрасывает исключение.)

Я хотел бы предложить вам использовать 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 объекты.

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

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

Другие вопросы по тегам