Нахождение ближайшего общего суперкласса (или суперинтерфейса) коллекции классов - PullRequest
27 голосов
/ 21 марта 2012

При наличии набора классов, как лучше всего найти ближайший общий суперкласс?

Например, учитывая следующее:

interface A {}
interface B {}
interface AB extends A, B {}
interface C {}
class AImpl implements A {}
class ABImpl implements AB {}
class ABImpl2 implements A, B {}
class BCImpl implements B, C {}

Я ожидал бы следующее (не исчерпывающее):

commonSuperclass(A, AImpl) == A
commonSuperclass(A, B, C) == Object or null, I'm not picky
commonSuperclass(A, AB) == A
commonSuperclass(AImpl, ABImpl) == A
commonSuperclass(ABImpl, ABImpl2) == either A or B or both, I'm not picky
commonSuperclass(AImpl, ABImpl, ABImpl2) == A
commonSuperclass(ABImpl, ABImpl2, BCImpl) == B
commonSuperclass(AImpl, ABImpl, ABImpl2, BCImpl) == Object

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


ETA: Я знаю об API отражения.Я ищу этот алгоритм (или реализацию такого алгоритма).

ETA: И я знаю, что это DAG.Спасибо.Вы очень умны.


ETA: О топологической сортировке (в ответе EJP ): вам нужны знакомые мне алгоритмы топологической сортировкилибо:

  1. начинать с "корневых" узлов n без входящих ребер (т. е. в этом сценарии, предположительно, Object и всех интерфейсов без суперинтерфейсов - какой из них должен был бы изучитьвесь набор, плюс все суперклассы / суперинтерфейсы, чтобы собрать) и обработать все ребра (n, m) (т. е. все m extends/implements n, информацию, которую снова нужно будет изучить весь набор, чтобы собрать), или
  2. startиз «конечных» узлов m без исходящих ребер (т. е. в этом сценарии все классы / интерфейсы m, для которых не существует класса k extends/implements m, который опять-таки придется исследовать весь набор для сбора) и обработатьвсе ребра (n, m) (т. е. все классы / интерфейсы m расширяются / реализуются - какая информация у нас есть).

Возможен один или другой из этих многопроходных алгоритмов (хорошопредположительно# 2) самый эффективный способ сделать это, но это, конечно, не тривиально очевидно.Также вполне возможно, что существует однопроходный алгоритм топологической сортировки, с которым я не знаком, или что я просто неправильно понял эти алгоритмы, но в этом случае, опять же, «это по сути своего рода топологическая сортировка» не сразу приводитодин к ответу.

Ответы [ 6 ]

30 голосов
/ 21 марта 2012

Полное рабочее решение, насколько мне известно

  • BFS каждой иерархии классов идет "вверх" - результат в LinkedHashSet (сохранить порядок + без дубликатов)
  • Пересекайте каждый набор со следующим, чтобы найти что-то общее, снова LinkedHashSet для сохранения порядка
  • Оставшийся «упорядоченный» набор - это общие предки, первый в списке «ближайший», последний - самый дальний.
  • Пустой список не подразумевает никаких предков (кроме объекта)

Код

private static Set<Class<?>> getClassesBfs(Class<?> clazz) {
    Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
    Set<Class<?>> nextLevel = new LinkedHashSet<Class<?>>();
    nextLevel.add(clazz);
    do {
        classes.addAll(nextLevel);
        Set<Class<?>> thisLevel = new LinkedHashSet<Class<?>>(nextLevel);
        nextLevel.clear();
        for (Class<?> each : thisLevel) {
            Class<?> superClass = each.getSuperclass();
            if (superClass != null && superClass != Object.class) {
                nextLevel.add(superClass);
            }
            for (Class<?> eachInt : each.getInterfaces()) {
                nextLevel.add(eachInt);
            }
        }
    } while (!nextLevel.isEmpty());
    return classes;
}

private static List<Class<?>> commonSuperClass(Class<?>... classes) {
    // start off with set from first hierarchy
    Set<Class<?>> rollingIntersect = new LinkedHashSet<Class<?>>(
            getClassesBfs(classes[0]));
    // intersect with next
    for (int i = 1; i < classes.length; i++) {
        rollingIntersect.retainAll(getClassesBfs(classes[i]));
    }
    return new LinkedList<Class<?>>(rollingIntersect);
}

Методы поддержки и испытания

private static void test(Class<?>... classes) {
    System.out.println("Common ancestor for "
            + simpleClassList(Arrays.asList(classes)) + ", Result =>  "
            + simpleClassList(commonSuperClass(classes)));
}

private static String simpleClassList(Collection<Class<?>> classes) {
    StringBuilder builder = new StringBuilder();
    for (Class<?> clazz : classes) {
        builder.append(clazz.getSimpleName());
        builder.append(",");
    }
    return builder.toString();
}

public static void main(String[] args) {
    test(A.class, AImpl.class);
    test(A.class, B.class, C.class);
    test(A.class, AB.class);
    test(AImpl.class, ABImpl.class);
    test(ABImpl.class, ABImpl2.class);
    test(AImpl.class, ABImpl.class, ABImpl2.class);
    test(ABImpl.class, ABImpl2.class, BCImpl.class);
    test(AImpl.class, ABImpl.class, ABImpl2.class, BCImpl.class);
    test(AB.class, ABImpl.class);
}

выход

Common ancestor for A,AImpl,, Result =>  A,
Common ancestor for A,B,C,, Result =>  
Common ancestor for A,AB,, Result =>  A,
Common ancestor for AImpl,ABImpl,, Result =>  A,
Common ancestor for ABImpl,ABImpl2,, Result =>  A,B,
Common ancestor for AImpl,ABImpl,ABImpl2,, Result =>  A,
Common ancestor for ABImpl,ABImpl2,BCImpl,, Result =>  B,
Common ancestor for AImpl,ABImpl,ABImpl2,BCImpl,, Result =>  
Common ancestor for AB,ABImpl,, Result =>  AB,A,B,
8 голосов
/ 04 сентября 2013

Это основано на ответе Адама.

Во-первых, я оптимизировал getClasses, чтобы он создавал меньше временных объектов, то есть только один ArrayDeque вместо LinkedHashSet для каждого уровня.

public static Set<Class<?>> getSuperclasses(Class<?> clazz) {
    final Set<Class<?>> result = new LinkedHashSet<>();
    final Queue<Class<?>> queue = new ArrayDeque<>();
    queue.add(clazz);
    if (clazz.isInterface()) {
        queue.add(Object.class); // optional
    }
    while (!queue.isEmpty()) {
        Class<?> c = queue.remove();
        if (result.add(c)) {
            Class<?> sup = c.getSuperclass();
            if (sup != null) queue.add(sup);
            queue.addAll(Arrays.asList(c.getInterfaces()));
        }
    }
    return result;
}

Чтобы найти общие суперклассы, вызовы retainAll(getClasses()) можно заменить на if (!isAssignableFrom()) remove(), так что довольно дорогой getClasses будет вызываться только один раз. Этот метод выглядит сложнее, чем исходное решение, из-за вложенных циклов, но это только потому, что в исходном решении внутренний цикл скрыт в retainAll.

public static Set<Class<?>> commonSuperclasses(Iterable<Class<?>> classes) {
    Iterator<Class<?>> it = classes.iterator();
    if (!it.hasNext()) {
        return Collections.emptySet();
    }
    // begin with set from first hierarchy
    Set<Class<?>> result = getSuperclasses(it.next());
    // remove non-superclasses of remaining
    while (it.hasNext()) {
        Class<?> c = it.next();
        Iterator<Class<?>> resultIt = result.iterator();
        while (resultIt.hasNext()) {
            Class<?> sup = resultIt.next();
            if (!sup.isAssignableFrom(c)) {
                resultIt.remove();
            }
        }
    }
    return result;
}

Наконец, в вашем вопросе кажется, что вас интересует только самый низкий суперкласс, поэтому мы используем упорядоченный набор. Но мы также можем легко удалить неконечные классы. Сложность здесь O (n) в лучшем случае (если есть только один результат) и O (n ^ 2) в худшем случае.

public static List<Class<?>> lowestCommonSuperclasses(Iterable<Class<?>> classes) {
    Collection<Class<?>> commonSupers = commonSuperclasses(classes);
    return lowestClasses(commonSupers);
}

public static List<Class<?>> lowestClasses(Collection<Class<?>> classes) {
    final LinkedList<Class<?>> source = new LinkedList<>(classes);
    final ArrayList<Class<?>> result = new ArrayList<>(classes.size());
    while (!source.isEmpty()) {
        Iterator<Class<?>> srcIt = source.iterator();
        Class<?> c = srcIt.next();
        srcIt.remove();
        while (srcIt.hasNext()) {
            Class<?> c2 = srcIt.next();
            if (c2.isAssignableFrom(c)) {
                srcIt.remove();
            } else if (c.isAssignableFrom(c2)) {
                c = c2;
                srcIt.remove();
            }
        }
        result.add(c);
    }
    result.trimToSize();
    return result;
} 
2 голосов
/ 20 июля 2017

Для пользователей Spring Framework есть org.springframework.util.ClassUtils#determineCommonAncestor.

Например:

ClassUtils.determineCommonAncestor(Integer.class, LongAdder.class);
2 голосов
/ 04 февраля 2016

Попробуйте это.

static <T> Class<T> commonSuperClass(Class<? extends T> c1, Class<? extends T> c2, T... args) {
    return (Class<T>)args.getClass().getComponentType();
}

static <T> Class<T> commonSuperClass(Class<? extends T> c1, Class<? extends T> c2, Class<? extends T> c3, T... args) {
    return (Class<T>)args.getClass().getComponentType();
}

static <T> Class<T> commonSuperClass(Class<? extends T> c1, Class<? extends T> c2, Class<? extends T> c3, Class<? extends T> c4, T... args) {
    return (Class<T>)args.getClass().getComponentType();
}

Результаты

System.out.println(commonSuperClass(A.class, AImpl.class));                                     // -> A
System.out.println(commonSuperClass(A.class, B.class, C.class));                                // -> Object
System.out.println(commonSuperClass(A.class, AB.class));                                        // -> A
System.out.println(commonSuperClass(AImpl.class, ABImpl.class));                                // -> A
System.out.println(commonSuperClass(ABImpl.class, ABImpl2.class));                              // -> A
System.out.println(commonSuperClass(AImpl.class, ABImpl.class, ABImpl2.class));                 // -> A
System.out.println(commonSuperClass(ABImpl.class, ABImpl2.class, BCImpl.class));                // -> B
System.out.println(commonSuperClass(AImpl.class, ABImpl.class, ABImpl2.class, BCImpl.class));   // -> Object
0 голосов
/ 01 августа 2016

Я тоже сталкиваюсь с этой проблемой прямо сейчас.Мне нужен только ответ на уровне суперкласса.По сути, я считаю, что лучше всего пройти только O (n).

Вы должны определить операцию Cmin (A, B), которая даст вам ближайший суперкласс класса A и класса B.

Поскольку Cmin приводит к самому классу, вы можете использовать этот алгоритм:

Result = Cmin Ai |(ai elementIn классы).

дает вам O (n).

Но помните, что Java различает типы как интерфейсы или классы.

0 голосов
/ 21 марта 2012

Отражение может дать вам информацию, необходимую для написания собственного кода:

Class<?> c = SomeType.class; // or someObject.getClass()
Class<?> superClass = s.getSuperClass();
Class<?>[] interfaces = s.getInterfaces();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...