Поиск ближайшего общего суперкласса (или суперинтерфейса) набора классов
Учитывая набор классов, какой лучший способ найти ближайший общий суперкласс?
Например, учитывая следующее:
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): алгоритмы топологической сортировки, с которыми я знаком, требуют от вас:
- начинаем с "корневых" узлов
n
без входящих ребер (т.е. в этом сценарии, предположительно Object
и всех интерфейсов без суперинтерфейсов), которые нужно будет исследовать весь набор, плюс все суперклассы/суперинтерфейсы, собирать) и обрабатывать все ребра (n, m)
(т.е. все m extends/implements n
, информацию, которую снова нужно было бы изучить весь набор для сбора), или
- начинаем с "листовых" узлов
m
без исходящих ребер (т.е. в этом случае все классы/интерфейсы m
, для которых не существует класс k extends/implements m
, что снова нужно было бы изучить весь набор собирать) и обрабатывать все ребра (n, m)
(т.е. все классы/интерфейсы m
расширяет/реализует - какую информацию мы имеем).
Возможно один или несколько из этих многопроходных алгоритмов (нормально, предположительно, № 2) - самый эффективный способ сделать это, но это, конечно, не тривиально очевидно. Также вполне возможно наличие однопроходного топологического алгоритма сортировки, с которым я не знаком, или что я просто получил эти алгоритмы неправильно, но в этом случае снова "это в основном своего рода топологическая сортировка" не сразу приводит один для ответа.
Ответы
Ответ 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,
Ответ 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
Попробуйте это.
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
Ответ 4
Для пользователей Spring Framework существует org.springframework.util.ClassUtils#determineCommonAncestor
.
например:
ClassUtils.determineCommonAncestor(Integer.class, LongAdder.class);
Ответ 5
Отражение может предоставить вам информацию, необходимую для написания собственного кода:
Class<?> c = SomeType.class; // or someObject.getClass()
Class<?> superClass = s.getSuperClass();
Class<?>[] interfaces = s.getInterfaces();
Ответ 6
Я также сталкиваюсь с этой проблемой прямо сейчас. Мне нужен только ответ на уровне суперкласса. В основном я нашел, что лучше всего пройти O (n).
Вы должны определить операцию Cmin (A, B), дающую вам ближайший суперкласс класса A и класса B.
Так как Cmin приводит к самому классу, вы можете использовать этот алгоритм:
Результат = Cmin Ai | (классы ai elementIn).
дает вам O (n).
Но помните, что Java делает различия в типах, являющихся интерфейсами или классами.