Определить, является ли метод переопределенным.

 
 
 
Сообщения:350
Здравствуйте!

Задача такова: определить, является ли метод переопределенным.

Вроде бы всё просто: есть один метод m1 и есть другой метод m2. m1.getDeclaringType() - это m2.getDeclaringType().getSuperclass(). Иначе говоря, m2 напрямую переопределяет m1. Можно сравнить имена и типы параметров...

А как быть в таком случае?

public class TestClass1<T> {

    protected void method(T argument1, T argument2) {
    }
}

public class TestClass2 extends TestClass1<String> {

    @Override
    protected void method(String argument1, String argument2) {
    }
}


Здесь методы имеют разные параметры типов, однако метод в классе TestClass2 переопределяет метод в классе TestClass1. Или я путаю, и переопределяющий метод класса TestClass2 -- метод с параметрами типа Object, для которого isBridge() вернет true?

Подскажите, как быть с generics.

PS. Проект на Java 6, в ближайшее время переход на семёрку не планируется.

UPD. В ходе обсуждения вопрос был решен. Финальная версия и тесты к ней лежат на GitHub.
Изменен:28 авг 2014 17:25
 
 
Сообщения:854
я не помню доступна ли override аннотация в рантайме.............походу можно посмотреть на наличие этой аннотации в методе.
 
 
Сообщения:1339
Наличие анотации @Override не является обязательным для переопределения метода

datatalks.ru - анализ данных, статистика, машинное обучение
 
 
Сообщения:350
И всё-таки, как же определить? Находил статьи, примеры, но все они нацелены лишь на частные случаи или не работают с generics вообще. Одна из статей на stackoverflow: Reflection on interface overridden methods.
 
 
Сообщения:350
Вот наваял такой код. Судите сами о его корректности. На моих примерах он работает со всеми generics, вроде. Ищите ошибки)

    public boolean matches(Method methodA, Method methodB) {
        if (!methodA.getDeclaringClass().equals(methodB.getDeclaringClass().getSuperclass())) return false;
        Type[] typesA = methodA.getGenericParameterTypes();
        Type[] typesB = methodB.getGenericParameterTypes();
        for (int i = 0; i < typesA.length; i++) {
            if (!Arrays.asList(methodA.getDeclaringClass().getTypeParameters()).contains(typesA[i])
                    || !Arrays.asList(((ParameterizedType)methodB.getDeclaringClass().getGenericSuperclass())
                    .getActualTypeArguments()).contains(typesB[i])) {
                if (typesA[i].equals(typesB[i])) {
                    typesA[i] = null; typesB[i] = null;
                } else return false;
            }
        }
        return matches(typesA, typesB);
    }

    private boolean matches(Type[] typesA, Type[] typesB) {
        for (int i = 0; i < typesA.length; i++) {
            if (!matches(typesA[i], typesB[i])) return false;
        }
        return true;
    }

    private boolean matches(Type typeA, Type typeB) {
        if (typeA == null && typeB == null) return true;
        if (typeA == null || typeB == null) return false;
        if (typeA instanceof Class && typeB instanceof Class) return ((Class)typeA).isAssignableFrom((Class) typeB);
        if (typeA instanceof ParameterizedType && typeB instanceof ParameterizedType)
            return matches(((ParameterizedType)typeA).getActualTypeArguments(), ((ParameterizedType) typeB).getActualTypeArguments());
        if (typeA instanceof TypeVariable)
            return matches(((TypeVariable) typeA).getBounds()[0], typeB);
        if (typeB instanceof TypeVariable)
            return matches(typeA, ((TypeVariable) typeB).getBounds()[0]);
        return false;
    }


UPD: Забыл return поставить в строках 32-33. Добавил.
Изменен:14 июн 2014 12:37
 
 
Сообщения:350
Нашелся контрпример. Будем исправлять.
 
 
Сообщения:350
Кажись до меня дошло! Уж на этот раз вряд ли ошибся где. Анализ на переопределение проводит на ура! Контрпримеров не нашел.

UPD. Добавил краткое описание кода

Опишу механиз действия вкратце (написал доку, но всё же). Что делает код:
  • Итак, сначала идут базовые проверки имени метода, возвращаемого типа, классовой иерархии (не вводил проверку модификаторов)
  • Далее создаётся карта, где ключ -- неизвестный тип, значение -- уже известный тип. Создаётся она так. Берётся класс, где определён второй метод (класс Б). Анализируется его generic суперкласс, т.е. тот, что объявлен через extends. Его параметры -- конкретные параметры. Абстрактные параметры суперкласса класса Б мапятся к его конкретным параметрам. Процесс рекурсивен, переходит на суперкласс класса Б, суперкласс суперкласса и т.д., пока не упрёмся в тип, где первый метод объялен, т.е. до получения полной картины.
  • Имея контекст, мы можем сравнить все параметры первого и второго методов, подставляя вместо неизвестных параметров первого метода конкретные значения из контекста.


/**
 * Contains some utility methods for the Java Reflection API.
 *
 * @author Alexander Vasilevsky
 */
public class ReflectionUtilities {

    /**
     * The private constructor.
     */
    private ReflectionUtilities() {
    }

    /**
     * Determines the <code>methodA</code> is overridden by the
     * <code>methodB</code>.
     *
     * <p/>
     * Supports generics.
     *
     * @param methodA The first method
     * @param methodB The second method
     *
     * @return <code>true</code> if one method is overridden by another,
     *         <code>false</code> otherwise
     */
    public static boolean isOverridden(Method methodA, Method methodB) {
        Class<?> declaringA = methodA.getDeclaringClass();
        Class<?> declaringB = methodB.getDeclaringClass();

        if (!declaringA.isAssignableFrom(declaringB)) return false;
        if (!methodB.getName().equals(methodA.getName())) return false;
        if (!methodB.getReturnType().isAssignableFrom(methodA.getReturnType())) return false;

        Map<Type, Type> parameters = resolveParameters(declaringA, declaringB);
        Type[] typesA = methodA.getGenericParameterTypes();
        Type[] typesB = methodB.getGenericParameterTypes();

        if (typesA.length != typesB.length) return false;

        return compare(typesA, typesB, parameters);
    }

    /**
     * Compares two arrays of types in context of generic parameters
     * by calling the [email protected] #compareRecursive(java.lang.reflect.Type,
     * java.lang.reflect.Type, java.util.Map) compareRecursive()}
     * method for each pair (typeA[i]; typeB[i]).
     *
     * @param typesA     The first array of types
     * @param typesB     The second array of types
     * @param parameters The context of generic parameter
     *
     * @return <code>true</code> two arrays are equivalent in the passed
     *         context of generic parameters, <code>false</code> otherwise
     *
     * @throws NullPointerException           If one of the arguments
     *                                        is <code>null</code>
     * @throws ArrayIndexOutOfBoundsException If arrays differ in length
     */
    private static boolean compare(Type[] typesA, Type[] typesB,
                                   Map<Type, Type> parameters) {
        for (int i = 0; i < typesA.length; i++) {
            if (!compareRecursive(typesA[i], typesB[i], parameters)) return false;
        }
        return true;
    }

    /**
     * Compares two types in the passed context of generic parameters.
     * Checks if the first type is assignable from another.
     *
     * <p/>
     * If both types are <code>null</code>, returns <code>true</code>.
     * If if one of the types is <code>null</code>, returns <code>false</code>.
     *
     * <p/>
     * Otherwise, performs recursive comparison:
     *
     * <ul>
     *     <li>If context of generic parameters contains the
     *         first type, its opponent from context map
     *         will be recursively compared with the other type</li>
     *     <li>If both types are of [email protected] Class} type,
     *         they will be tested on equality</li>
     *     <li>If both types are of [email protected] ParameterizedType} type
     *         and its raw types equal, their actual parameters
     *         will be compared by using the [email protected]
     *         #compare(java.lang.reflect.Type[],
     *         java.lang.reflect.Type[], java.util.Map) compare()} method</li>
     *     <li>If one of the types is of [email protected] TypeVariable}
     *         type, the other type will be recursively compared with
     *         the first bound of variable</li>
     *     <li>In other cases, <code>false</code> will be returned</li>
     * </ul>
     *
     * @param typeA      The first type
     * @param typeB      The second type
     * @param parameters The context of generic parameters
     *
     * @return <code>true</code> if types are equivalent in the passed context
     *         of generic parameters, <code>false</code> otherwise
     *
     * @see Class#isAssignableFrom(java.lang.Class)
     * @see ParameterizedType#getActualTypeArguments()
     * @see TypeVariable#getBounds()
     */
    private static boolean compareRecursive(Type typeA, Type typeB,
                                            Map<Type, Type> parameters) {
        Type type = parameters.get(typeA);
        if (type != null) return compareRecursive(type, typeB, parameters);
        if (typeA instanceof Class && typeB instanceof Class)
            return ((Class<?>) typeA).equals((Class<?>) typeB);
        if (typeA instanceof ParameterizedType
                && typeB instanceof ParameterizedType) {
            if (!((ParameterizedType) typeA).getRawType()
                    .equals(((ParameterizedType) typeB).getRawType()))
                return false;
            return compare(
                    ((ParameterizedType) typeA).getActualTypeArguments(),
                    ((ParameterizedType) typeB).getActualTypeArguments(),
                    parameters);
        }
        if (typeA instanceof TypeVariable)
            return compareRecursive(((TypeVariable) typeA)
                    .getBounds()[0], typeB, parameters);
        if (typeB instanceof TypeVariable)
            return compareRecursive(typeA, ((TypeVariable) typeB)
                    .getBounds()[0], parameters);
        return false;
    }

    /**
     * Resolves generic type parameters of the <code>classA</code>
     * in context of <code>classC</code>.
     *
     * <p/>
     * The method resolves only the simple type variables, it
     * does nothing with parameterized types, which should be
     * expanded manually using the context, returned by this method.
     *
     * <p/>
     * The method starts parameter resolution from the direct
     * superclass of <code>classC</code>, using the <code>classC</code>
     * definition to recognize the actual parameters of its superclass;
     * simultaneously storing already resolved parameters of the superclass
     * in the context.
     *
     * Then, it does the same with superclass of superclass of
     * <code>classC</code>, taking already resolved parameters
     * from the context and storing newly resolved parameters to it.
     *
     * @param classA The upper class in the hierarchy class
     * @param classA The lower class in the hierarchy class
     *
     * @return A map that represents context of generic parameters
     *
     * @throws NullPointerException If any argument is <code>null</code>
     */
    private static Map<Type, Type> resolveParameters(
            Class<?> classA, Class<?> classC) {
        Map<Type, Type> map = new HashMap<Type, Type>(3);
        Class<?> classB = classC;
        while (!classA.equals(classB)) {
            TypeVariable[] theoretical = classB.getSuperclass().getTypeParameters();
            Type[] actual = ((ParameterizedType) classB
                    .getGenericSuperclass()).getActualTypeArguments();
            for (int i = 0; i < theoretical.length; i++) {
                Type processed = map.get(actual[i]);
                map.put(theoretical[i], processed != null ? processed : actual[i]);
            }
            classB = classB.getSuperclass();
        }
        return map;
    }
}
Изменен:14 июн 2014 18:59
 
 
Сообщения:2185
kalaider:
Задача такова: определить, является ли метод переопределенным.
Если задача не выходит за эти рамки, то я не вижу смысла плясок с дженириками. Всегда же будет bridge метод с параметрами типа Object, по присутствию которого можно определить, переопределен ли метод.

Или же задача в определении, есть ли у метода супер.метод?
Изменен:14 июн 2014 19:59
 
 
Сообщения:350
Mam(O)n:
Или же задача в определении, есть ли у метода супер.метод?


Задача -- определить, является ли один метод суперметодом для другого. Иначе говоря известны оба метода, нужно лишь тест сделать.

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

Mam(O)n:
Всегда же будет bridge метод с параметрами типа Object, по присутствию которого можно определить, переопределен ли метод.


UPD: Разве? Если есть generic, определенный через exends, например, ClassA<T extends Number> и метод a(T number), то метод a будет иметь не-generic сигнатуру a(Number number), а не a(Object number). И bridge тоже будет с Number-аргументом.

UPD2: В начале темы был лишь контрпример. Ситуации разные же могут быть. А код должен везде работать.
Изменен:14 июн 2014 20:41
 
 
Сообщения:476
а такое корректно определит?
 static public class TestClass1<T extends A, V extends Collection<String>> {

        protected void method(T argument1, V argument2) {

        }
    }

    static public class TestClass2 extends TestClass1<C, List<String>> {

        protected void method(B argument1, List<String> argument2) {
        }

        protected void method(C argument1, List<String> argument2) {
        }

        protected void method(C argument1, Collection<String> argument2) {
        }

        protected void method(C argument1, ArrayList<String> argument2) {
        }
    }

    static class A { }

    static class C extends A {}

    static class B extends C { }
попробовал подобное написать, правда у меня грязненько получилось.
 
 
Сообщения:2185
kalaider:
Задача -- определить, является ли один метод суперметодом для другого. Иначе говоря известны оба метода, нужно лишь тест сделать.
Понятно. Без второго метода задача имела бы более простое решение.

kalaider:
UPD: Разве? Если есть generic, определенный через exends, например, ClassA<T extends Number> и метод a(T number), то метод a будет иметь не-generic сигнатуру a(Number number), а не a(Object number). И bridge тоже будет с Number-аргументом.
Ваша правда, поспешил глядя на ваш пример. Но основная моя мысль была в том, что в любом случае должен быть метод с типами аргументов суперметода.
 
 
Сообщения:350
Mam(O)n:
Без второго метода задача имела бы более простое решение.


Расскажите поподробнее, если не сложно.

Mam(O)n:
Но основная моя мысль была в том, что в любом случае должен быть метод с типами аргументов суперметода.


Для этого нужно ещё их определить) Опять же через дженерики. И не факт, что будет проще.
 
 
Сообщения:350
house2008:
а такое корректно определит?


Потестим. Расскажу о результатах.
 
 
Сообщения:350
kalaider:
house2008:
а такое корректно определит?


Потестим. Расскажу о результатах.


Отчёт.

        System.out.println(ReflectionUtilities.isOverridden(
                TestClass1.class.getDeclaredMethod("method", A.class, Collection.class),
                TestClass2.class.getDeclaredMethod("method", B.class, List.class)));
        System.out.println(ReflectionUtilities.isOverridden(
                TestClass1.class.getDeclaredMethod("method", A.class, Collection.class),
                TestClass2.class.getDeclaredMethod("method", C.class, List.class)));
        System.out.println(ReflectionUtilities.isOverridden(
                TestClass1.class.getDeclaredMethod("method", A.class, Collection.class),
                TestClass2.class.getDeclaredMethod("method", C.class, Collection.class)));
        System.out.println(ReflectionUtilities.isOverridden(
                TestClass1.class.getDeclaredMethod("method", A.class, Collection.class),
                TestClass2.class.getDeclaredMethod("method", C.class, ArrayList.class)));


Вывод:

false
true
false
false


Вывод верен.
 
 
Сообщения:2185
kalaider:
Расскажите поподробнее, если не сложно.
Я думаю самым подробным рассказом может служить пример кода. Я просто не могу придумать задачу при которой нужно идти от конечного метода. А вот наоборот, например иногда хочется узнать в родительском классе, переопределён ли какой метод у потомка, иногда очень хочется.

Mam(O)n:
Для этого нужно ещё их определить) Опять же через дженерики. И не факт, что будет проще.
Если уже имеется на руках Method, то в данном случае (class ABC<T extends XYZ>) типы параметров T там не Object будут, а как раз граничащий тип из дженерика (XYZ) и никакого bridge метода в классе создано не будет. Вот.
 
Модераторы:Нет
Сейчас эту тему просматривают:Нет