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 {}
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;
}
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.
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
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();
For Spring Framework users there is org.springframework.util.ClassUtils#determineCommonAncestor
.
E.g:
ClassUtils.determineCommonAncestor(Integer.class, LongAdder.class);
Full working solution to best of my knowledge
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,