查找类集合的最近公共超类(或超级接口)

时间:2012-03-21 00:55:56

标签: java inheritance

鉴于一组类,找到最近的公共超类的最佳方法是什么?

例如,给出以下内容:

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:关于拓扑排序(在re EJP's answer中):我熟悉的拓扑排序算法要求您:

  1. 从“root”节点n开始,没有传入边缘(即,在这种情况下,大概是Object和所有没有超级接口的接口 - 哪一个必须检查整个集合,加上所有超类/超接口,收集)并处理所有边(n, m)(即所有m extends/implements n,再次需要检查整个集合的信息),或
  2. 从“leaf”节点m开始,没有传出边(即,在这种情况下,所有类/接口m都没有类k extends/implements m,这也是检查整个集合以收集)并处理所有边(n, m)(即所有类/接口m扩展/实现 - 我们拥有哪些信息。)
  3. 这些多次通过算法中的一种或另一种(好的,可能是#2)是最有效的方法,但它肯定不是显而易见的。它也完全有可能是我不熟悉的单程拓扑排序算法,或者我只是简单地得到了这些算法,但在这种情况下,再次,“它基本上是一种拓扑排序”并不会立即导致一个答案。

6 个答案:

答案 0 :(得分:30)

据我所知,完整的解决方案

  • 每个班级的BFS进入"向上" - 结果到LinkedHashSet(保留顺序+没有重复)
  • 将每个集与下一个集相交以查找任何共同点,再次使用LinkedHashSet保存顺序
  • 剩下的"命令" set是常见的祖先,列表中的第一个是"最近的",最后是最远的。
  • 空列表表示没有祖先(除了对象)

代码

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,

答案 1 :(得分:8)

这是基于亚当的答案。

首先,我对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 :(得分:2)

试试这个。

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

答案 3 :(得分:2)

对于Spring Framework用户,有0.7751083469484001 0.5140565759502351

E.g:

org.springframework.util.ClassUtils#determineCommonAncestor

答案 4 :(得分:0)

反射可以为您提供编写自己的代码所需的信息:

Class<?> c = SomeType.class; // or someObject.getClass()
Class<?> superClass = s.getSuperClass();
Class<?>[] interfaces = s.getInterfaces();

答案 5 :(得分:0)

我现在也面临这个问题。我只需要超类级别的答案。基本上我发现最好只做O(n)步行。

您必须定义一个操作Cmin(A,B),为您提供最近的A类和B类超类。

由于Cmin会导致类本身,因此您可以使用此算法:

结果= Cmin Ai | (ai elementIn classes)。

给你O(n)。

但请记住,Java在接口或类的类型上有所区别。