【问题标题】:Finding the nearest common superclass (or superinterface) of a collection of classes查找类集合中最近的公共超类(或超接口)
【发布时间】:2012-03-21 00:55:55
【问题描述】:

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

例如,给定以下内容:

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

  1. 从“根”节点n 开始,没有传入边(即,在这种情况下,可能是Object 和所有没有超接口的接口——必须检查整个集合,加上所有超类/超接口,收集)并处理所有边缘(n, m)(即所有m extends/implements n,再次必须检查整个集合以收集的信息),或
  2. 从“叶”节点m 开始,没有出边(即,在这种情况下,所有类/接口m 不存在类k extends/implements m,这又一次必须检查整个集合收集)并处理所有边缘(n, m)(即所有类/接口m扩展/实现——我们确实拥有这些信息)。

这些多遍算法中的一种或另一种可能是最有效的方法(好吧,大概是#2),但它肯定不是很明显。也完全有可能存在我不熟悉的一次性拓扑排序算法,或者我只是把这些算法弄错了,但在那种情况下,“它基本上是一种拓扑排序”不会立即导致一个答案。

【问题讨论】:

    标签: java inheritance


    【解决方案1】:

    尽我所知的完整工作解决方案

    • 每个类层次结构的 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,
    

    【讨论】:

    • 不太有效:给定 AB 和 ABImpl 它返回 A 而不是 AB。但在正确的轨道上。
    • 我稍微调整了逻辑,似乎适用于所有测试用例和 AB、ABImpl。不过,我仍然对此并不完全满意。
    • 特浓浓缩咖啡 + 完全重写,终于满意了。更新了测试和结果。我已经这样做了,所以它返回了所有共同的祖先,这处理了同一级别有很多的情况。如果您想要最近的,只需在列表中选择第一个即可。
    【解决方案2】:

    这是基于亚当的回答。

    首先,我优化了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;
    } 
    

    【讨论】:

      【解决方案3】:

      对于 Spring Framework 用户,有 org.springframework.util.ClassUtils#determineCommonAncestor

      例如:

      ClassUtils.determineCommonAncestor(Integer.class, LongAdder.class);
      

      【讨论】:

      • 通过查看source,我认为这不适用于接口。
      【解决方案4】:

      试试这个。

      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
      

      【讨论】:

      • 有趣!但是,它不是真正可扩展的,因为它不能接受可变的类列表。
      【解决方案5】:

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

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

      【讨论】:

      • 我会添加 Class#isAssignableFrom() 以了解您何时遇到了一个公共超类/接口,是时候遍历类层次结构向下找到最低的公共超类/接口.
      • 是的,我知道可用的 API;我更多的是在寻找一种算法。或现有的实用程序类。
      【解决方案6】:

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

      你必须定义一个操作 Cmin(A, B) 给你最近的 A 类和 B 类的超类。

      由于 Cmin 会产生一个类本身,因此您可以使用此算法:

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

      给你 O(n)。

      但请记住,Java 区分类型是接口还是类。

      【讨论】:

        猜你喜欢
        • 2016-07-30
        • 2010-11-06
        • 1970-01-01
        • 2018-07-03
        • 1970-01-01
        • 2018-04-03
        • 1970-01-01
        • 2012-05-26
        • 2014-11-05
        相关资源
        最近更新 更多