Finding the nearest common superclass (or superinterface) of a collection of classes

后端 未结 6 1821
青春惊慌失措
青春惊慌失措 2020-12-08 14:23

Given a collection of classes, what\'s the best way to find the nearest common superclass?

E.g., given the following:

interface A {}
interface B {}
         


        
相关标签:
6条回答
  • 2020-12-08 14:47

    This is based on Adam's answer.

    Firstly, I optimized getClasses so that it will create fewer temporary objects, i.e. only one ArrayDeque instead of a LinkedHashSet for each level.

    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;
    }
    

    To find the common superclasses, calls to retainAll(getClasses()) can be replaced with if (!isAssignableFrom()) remove(), so that the quite expensive getClasses will be called only once. This method looks like it has worse complexity than the original solution, because of the nested loops, but that's only becaus in the original solution, the inner loop is hidden in 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;
    }
    

    Finally, in your question it seems that you are only interested in the lowest superclass, which is why we use an ordered set. But we can also remove non-leaf classes easily. The complexity here is O(n) best-case (if there is only one result) and O(n^2) worst-case.

    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;
    } 
    
    0 讨论(0)
  • 2020-12-08 14:57

    I am also facing this problem right now. I only need the answer on the superclass level. Basically I found it best to only do a O(n) walk through.

    You have to define an operation Cmin(A, B) giving you the nearest superclass of class A and class B.

    Since Cmin results in a class itself you can use this algorithm:

    Result = Cmin Ai | (ai elementIn classes).

    gives you O(n).

    But remember Java makes a distinction in types being interfaces or classes.

    0 讨论(0)
  • 2020-12-08 15:00

    Try this.

    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();
    }
    

    results are

    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 讨论(0)
  • 2020-12-08 15:02

    Reflection can give you the info you need to write your own code:

    Class<?> c = SomeType.class; // or someObject.getClass()
    Class<?> superClass = s.getSuperClass();
    Class<?>[] interfaces = s.getInterfaces();
    
    0 讨论(0)
  • 2020-12-08 15:11

    For Spring Framework users there is org.springframework.util.ClassUtils#determineCommonAncestor.

    E.g:

    ClassUtils.determineCommonAncestor(Integer.class, LongAdder.class);
    
    0 讨论(0)
  • 2020-12-08 15:12

    Full working solution to best of my knowledge

    • BFS of each class hierarchy going "upwards" - result into LinkedHashSet (preserve order + no duplicates)
    • Intersect each set with the next to find anything in common, again LinkedHashSet to preserve order
    • The remaining "ordered" set is the common ancestors, first in list is "nearest", last is furthest.
    • Empty list implies no ancestors (apart from object)

    Code

    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);
    }
    

    Supporting methods and test

    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);
    }
    

    Output

    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,
    
    0 讨论(0)
提交回复
热议问题