Подтвердить что ты не робот

Любое решение для отражения и autoboxing класса Class.getMethod()?

Я хочу использовать

Class.getMethod(String name, Class... parameterTypes)

чтобы найти метод, который мне нужно вызвать с заданными параметрами, но, по-видимому, как описано в Bug 6176992 В Java не включается автобоксинг. Поэтому, если у моего отраженного класса есть метод с сигнатурой (String, int), вы все равно получаете исключение NoSuchMethodException с массивом {String.class, Integer.class} в качестве пареметра.

Есть ли какое-либо решение для этого? Единственный способ, с помощью которого я мог бы называть getMethod() с каждой перестановкой примитивных и не примитивных типов, которые я действительно не хочу делать.

Изменить: сделать это более ясным: я хорошо знаю классы примитивного типа, но я не вижу, как они могут помочь решить мою проблему. Мой массив parameterTypes приходит откуда-то, и я знаю, что он будет возвращать не примитивные типы. Я не могу предположить, что интерфейс будет объявлен только с примитивными типами и что именно моя проблема:

public interface TestInterface()
{
    public void doTest(Integer i1, int i2, double d3, Double d);
}

Class<?>[] classes = { Integer.class, Integer.class, Double.class, Double.class }
// Due to autoboxing I should become the doTest method here, but it doesn't work
TestInterface.class.getMethod("doTest", classes);
4b9b3361

Ответ 1

Как упоминает @Stephen C, ваша единственная надежда - это выполнить поиск самостоятельно. Все его оговорки держатся, но я бы сказал, что небольшая гибкость может значительно помочь охватить большую часть gotchas, пока вызывающие абоненты знают о предостережениях... по сравнению с тем, что ваши абоненты всегда будут болезненно конкретными.

Для кода, который на самом деле что-то делает, вы можете посмотреть здесь: http://meta-jb.svn.sourceforge.net/viewvc/meta-jb/trunk/dev/src/main/java/org/progeeks/util/MethodIndex.java?revision=3811&view=markup

Вызов findMethod() является точкой входа, но он делегирует (после некоторого кэширования и т.д.) этот метод:

private Method searchForMethod( String name, Class[] parms ) {
    Method[] methods = type.getMethods();
    for( int i = 0; i < methods.length; i++ ) {
        // Has to be named the same of course.
        if( !methods[i].getName().equals( name ) )
            continue;

        Class[] types = methods[i].getParameterTypes();

        // Does it have the same number of arguments that we're looking for.
        if( types.length != parms.length )
            continue;

        // Check for type compatibility
        if( InspectionUtils.areTypesCompatible( types, parms ) )
            return methods[i];
        }
    return null;
}

InspectionUtils.areTypesCompatible() принимает два списка типов, нормализует их примитивы, а затем проверяет, что он "присваивается" другому. Поэтому он будет обрабатывать случай, когда у вас есть Integer и пытается вызвать метод, который принимает int, а также случай, когда у вас есть String, и пытается вызвать метод, который принимает Object. Он не обрабатывает случай наличия int и вызова метода, который принимает float. Должна быть какая-то определенность.

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

Вот проверка совместимости для справки:   public static boolean areTypesCompatible (Цели класса [], Class [] sources) {       if (target.length!= sources.length)           return false;

    for( int i = 0; i < targets.length; i++ ) {
        if( sources[i] == null )
            continue;

        if( !translateFromPrimitive( targets[i] ).isAssignableFrom( sources[i] ) )
            return false;
        }
    return( true );
}

Код - это BSD и мой, поэтому фрагменты являются законными для использования. Если вы решите, что лучше использовать этот пакет util непосредственно, последний публичный выпуск находится здесь: https://meta-jb.svn.sourceforge.net/svnroot/meta-jb/trunk/dev/m2-repo/org/meta-jb/meta-jb-util/0.17.1/

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

Ответ 2

Если у вас есть commons-lang с версией >= 2.5, вы можете использовать MethodUtils.getMatchingAccessibleMethod(...), который может справляться с проблемами типов бокса.

Ответ 3

Да, вам нужно использовать Integer.TYPE или (эквивалентно) int.class.

Update: "Мой массив parameterTypes приходит откуда-то, и я знаю, что он будет возвращать не примитивные типы". Ну, тогда это эта "где-то" проблема. Если они не дают вам правильную подпись метода, который они хотят, тогда как вы его найдете? Что делать, если есть два перегруженных метода, которые отличаются только одним из них, принимает примитив, а другой принимает класс оболочки? Какой из них он выбирает? Я имею в виду, если у вас действительно нет выбора, я думаю, вы могли бы просто пропустить все методы и искать имя с правильным именем и вручную проверять все типы параметров, являются ли они правильными или являются примитивными эквивалентами.

Ответ 4

Integer.class представляет тип объекта Integer. Integer.TYPE представляет собой примитивный тип int. Это работает?

Ответ 5

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

void f(Integer i){..}
void f(int i){...}

когда аргументом является тип Integer, который вы можете выбрать? Еще сложнее:

void f(Integer i, List l){...}
void f(Object o, LinkedList l){...}

у компилятора есть набор правил для выбора "наиболее специфического метода" на основе статической информации; если он не может определить, он сразу предупредит вас.

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

Ответ 6

Единственный ответ на данный момент - написать код для имитации правил продвижения типа компилятора Java, отражающий наиболее подходящий метод. Autoboxing и unboxing - это просто примеры рекламных акций, которые компилятор знает о...

Почему API-интерфейсы Java не отражают это? Я могу придумать ряд причин.

  • До Java 1.5 класс getMethod и друзья не понимали, как сделать (например) продвижение int до float. Если бы не было необходимости до 1.5, почему сейчас?

  • Добавление такого рода вещей приведет к еще более медленному обращению к рефлексивному методу.

  • Автобоксирование и распаковка могут приводить в замешательство с вызовом неотражающего метода. Добавление отражения только добавит больше путаницы.

  • Внедрение правил продвижения будет включать класс новых ошибок при выполнении, которые должны быть сопоставлены с исключениями и диагностированы по коду пользователя. Эти будут особенно сложными. Например, это должно было бы иметь дело с рефлексивным эквивалентом неоднозначного вызова метода.

  • Всеобъемлющее требование обратной совместимости означает, что Sun придется реализовать это как новые методы. Они не могут изменять поведение существующих методов, потому что это потенциально может сломать тысячи существующих приложений клиентов.

Существует один последний момент, который относится конкретно к случаю использования OP (как описано). OP говорит, что его код не знает, ожидать ли (например) метода с параметром int или Integer в целевом классе. Предположим, что человек, который написал целевой класс, обеспечил обе перегрузки... и семантика тонко (или несовместимо) отличается? Независимо от того, что вы делаете, будут ситуации, когда код OP выбирает перегрузку, которую клиент не ожидает. Плохо.

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

Ответ 7

Вы можете добавить некоторую дополнительную логику вокруг своего рефлекторного вызова, который пытается преобразовать ваш Integer.class (или что-то еще) в соответствующий примитивный класс, а затем повторно искать метод до тех пор, пока вы не получите совпадение. Если у вас есть Apache Commons Lang, метод wrapperToPrimitive сделает этот разговор для вас, но писать его самостоятельно тривиально.

  • Выполните getMethod как обычно
  • Если ничего не найдено, найдите все типы параметров, которые имеют соответствующие примитивы
  • Для каждой комбинации из них выполните другой поиск, пока что-то не встанет.

Не элегантный, а для методов с множеством примитивных параметров он может даже быть медленным.

Ответ 8

Попробуйте использовать Class.isPrimitive(), чтобы определить, является ли он примитивным типом, затем, если это так, используйте рефлексы для извлечения поля TYPE и проверьте, равно ли это. Итак, в очень либеральном псевдокоде:

for(Method m:getDeclaredMethods())
  for(Class c:m.getParameterTypes() && Class desired:desiredMethodArgTypes)
    if(c.isAssignableFrom(desired))
      //matches
    if(c.isPrimitive() && c==desired.getDeclaredField("TYPE").get(desiredObject))
      //matches