найти общий класс для всех объектов в коллекции - PullRequest
3 голосов
/ 17 июня 2019

У меня есть функция:

void foo(Collection<? extends Baz> messages) {

Как определить наиболее производный общий подкласс для всех элементов в коллекции?

Возможно, что все элементы расширяются Bar, что расширяет Baz. Некоторые могут расширять Fuz, что расширяет Bar, но не все; в этом случае наиболее производным общим подклассом будет по-прежнему Bar.

Ответы [ 9 ]

2 голосов
/ 17 июня 2019

Выполните итерацию коллекции и преобразуйте каждый элемент в список классов в их иерархии (например, Fuz - Bar - Baz - Object).

Для каждого следующего элемента classList.retainAll() результат той же операции.

Возьмите первый элемент полученного списка.

РЕДАКТИРОВАТЬ:

Поскольку вы отметили это как ответ на свой вопрос, я фактически добавлю еще немного работы.

Вот как я бы написал:

public static Class mostDerived(Collection<?> objects) {
    Optional<List<Class<?>>> mostDerived =
        objects.stream()
               .map(Util::getHierarchy) // class list for each
               .reduce((l1, l2) -> {
                   l1.retainAll(l2); // get intersecting classes
                   return l1;
               });
    return mostDerived.map(l -> l.get(0)).orElse(null);
}

private static List<Class<?>> getHierarchy(Object l) {
    List<Class<?>> result = new ArrayList<>();
    for (Class<?> clz = l.getClass(); clz != null; clz = clz.getSuperclass()) {
        result.add(clz);
    }
    return result;
}

Вот оно с вашими тестами.

0 голосов
/ 17 июня 2019

Существует подход для этого с чистым Stream, рекурсивным просмотром класса до корневого класса Baz , а с groupingBy мы можем получить Class stats распределения , пример:

public static Map<Class, Long> foo(Collection<? extends Baz> messages) {
    return messages.stream()
        .map(Object::getClass)
        .distinct()
        .flatMap(i -> getParentClazzs(i, Baz.class))
        .filter(i -> !Objects.equals(i, Baz.class))
        .collect(
            Collectors.groupingBy(Function.identity(), Collectors.counting())
        );
}

private static Stream<Class> getParentClazzs(Class clazz, Class<Baz> rootClass) {
    if (Objects.equals(clazz, rootClass)) {
        return Stream.of(clazz);
    }

    return Stream.concat(Stream.of(clazz), getParentClazzs(clazz.getSuperclass(), rootClass));
}
foo(Arrays.asList(new Bar(), new Foo(), new Bar(), new C(), new D()))
        .entrySet().stream()
        .forEach(i -> System.out.println(i.getKey().getSimpleName() + " " + i.getValue()));

Выход: C -> Бар -> Баз

D -> Бар -> Баз

Foo -> Баз

C 1

D 1

Foo 1

Бар 3

0 голосов
/ 17 июня 2019

Вы можете найти все распространенные суперклассы, а затем взять один в нижней части иерархии. Однако это не учитывает интерфейсы:

public static void main(final String[] args) {
    final Collection<Object> messages = Arrays.asList(
            new Bar(), new Foo());

    final Set<Class<?>> commonSuperclasses = messages.stream()
            .map(c -> c.getClass())
            .map(c -> getSuperclasses(new HashSet<>(), c))
            .reduce(Sets::intersection) // guava
            .orElse(Collections.emptySet());

    final Class<?> mostSpecificCommonSuperclass =
            commonSuperclasses
                    .stream()
                    .filter(
                            // take the one at the bottom of the hierarchy
                            c -> commonSuperclasses
                                    .stream()
                                    .map(s -> s.getSuperclass())
                                    .noneMatch(c::equals))
                    .findFirst()
                    .orElse(null);

    System.out.println(mostSpecificCommonSuperclass); // prints Bar
}

static Set<Class<?>> getSuperclasses(final Set<Class<?>> acc, final Class<?> clazz) {
    if (clazz == null) {
        return acc;
    }

    return getSuperclasses(Sets.union(acc, Collections.singleton(clazz)), clazz.getSuperclass());
}
0 голосов
/ 17 июня 2019

Вы можете пройти через каждый объект и получить все классы.

List<Class<?>> getClasses(Object o){
    List<Class<?>> classes = new HashSet<>();
    Class<?> c = o.getClass();
    while(c!=null){
        classes.add(c);
        c = c.getSuperClass();
    }
    return classes;
}

Создание вашей коллекции списков.

List<List<Class<?>> allClasses = messages.stream().map(
                                    this::getClasses
                                 ).collect(
                                    Collectors.toList()
                                 );
Set<Class<?>> union = new HashSet<>(allClasses.get(0));
allClasses.forEach(union::retainAll);

На данный момент объединение имеет все общие классы. Чтобы найти самый заданный ... Я бы сделал цикл над первым списком в allClasses, а затем нашел бы первый элемент.

Optional<Class<?>> mostSpecified = allClasses.get(0).filter(
                                        union::contains
                                   ).findFirst();
0 голосов
/ 17 июня 2019
public static <T> Class<? extends T> getMostCommonParent(Collection<T> messages) {
    Map<Class<?>, Integer> map = new HashMap<>();

    for (T message : messages) {
        Class<?> cls = message.getClass();

        while (cls != Object.class) {
            map.compute(cls, (key, total) -> Optional.ofNullable(total).orElse(0) + 1);
            cls = cls.getSuperclass();
        }
    }

    Class<?> cls = messages.iterator().next().getClass();

    while(map.get(cls) != messages.size())
        cls = cls.getSuperclass();

    return (Class<? extends T>)cls;
}
0 голосов
/ 17 июня 2019

Я бы использовал getClass() и сохранил бы количество экземпляров данного Class на карте:

void foo(Collection<? extends Baz> messages) {
    IdentityHashMap<Class, Integer> allCounts = new IdentityHashMap<>();
    for( Baz message : messages )
    {
        int count = allCounts.getOrDefault(message.getClass(), 0);
        allCounts.put((message.getClass(), count++);
    }
    // Query the map as needed.
}
0 голосов
/ 17 июня 2019

Имеется список подклассов predefinedClassList, отсортированный в порядке возрастания (наименьший для большинства подклассов), который доступен во время выполнения:

for (Baz b : messages) {

   predefinedClassList.stream()
      .filter(c -> messages.stream()
         .allMatch(m -> m.getClass().instanceOf(c)))
      .reduce((a, b) -> b).orElse(Baz.class));
   }
}
0 голосов
/ 17 июня 2019

Вот метод, который мне удалось придумать, который возвращает вам ближайших предков коллекции объектов:

static Class<?> getCommonAncestor(Collection<?> messages) {
    return messages.stream().map(Object::getClass)
        .collect(Collectors.reducing(Main::getCommonAncestor))
        .orElseThrow(() -> new RuntimeException("The collection is empty!"));
}

// this method gets the closest common ancestor between 2 classes
static Class<?> getCommonAncestor(Class<?> a, Class<?> b) {
    Stack<Class<?>> aParents = getParents(a);
    Stack<Class<?>> bParents = getParents(b);

    Class<?> commonAncestor = null;

    // I decided to search from the top of the inheritance tree because i know that
    // there will be the same number of classes on top of a common ancestor
    // I feel like there is a faster way, but I can't think of it...
    while (!aParents.isEmpty() && !bParents.isEmpty()) {
        Class<?> aPopped = aParents.pop();
        Class<?> bPopped = bParents.pop();
        if (aPopped == bPopped) {
            commonAncestor = aPopped;
        }
    }
    return commonAncestor;

}

// this method gets the whole inheritance tree of a single class
static Stack<Class<?>> getParents(Class<?> c) {
    Class<?> clazz = c;
    Stack<Class<?>> parents = new Stack<>();
    parents.push(clazz);
    while (parents.peek() != Object.class) {
        parents.push(clazz.getSuperclass());
        clazz = clazz.getSuperclass();
    }
    return parents;
}

Это наиболее общий случай, поэтому он также будет работать для ваших Baz es и Bar s.

0 голосов
/ 17 июня 2019

Похоже, что вы хотите иметь возможность проверить некоторые конкретные случаи, все ли сообщения определенного типа.

Если это так, просто перейдите на:

if(messages.stream().allMatch(i -> i instanceof SomeObject)) {
     ...
}

В противном случае вы могли бы написать вложенный поиск, используя API отражения, возможно ... но я думаю, это быстро станет очень уродливым.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...