java - просмотр в реальном времени коллекции, содержащейся в коллекции, содержащейся в и т. д.

У меня есть класс A, который может содержать множество экземпляров класса B, который, в свою очередь, может содержать множество экземпляров класса C, который может содержать множество экземпляров класса D.

Теперь в классе А у меня есть метод getAllD. В настоящее время каждый раз, когда это вызывается, происходит много итераций, и довольно большой список создается и возвращается. Это не может быть очень эффективным.

Мне было интересно, как я могу сделать это лучше. Этот вопрос Объединить несколько коллекций в одну логическую коллекцию? кажется чтобы затронуть подобную тему, но я не совсем уверен, как я мог бы применить это к моей ситуации.

Все комментарии очень ценятся!


person QuakerOat    schedule 16.05.2011    source источник
comment
Вы можете опубликовать фрагмент кода?   -  person Bhushan    schedule 17.05.2011
comment
Вам действительно нужны все D? Возможно, вы могли бы использовать паттерн цепочки ответственности для распределения всех вызовов методов для всех D через иерархию объектов без необходимости собирать все D.   -  person Gabriel Ščerbák    schedule 17.05.2011


Ответы (4)


Я бы объединил Iterables.concat с Iterables.transform для просмотра Ds в реальном времени:

public class A {
    private Collection<B> bs;

    /**
     * @return a live concatenated view of the Ds contained in the Cs
     *         contained in the Bs contained in this A.
     */
    public Iterable<D> getDs() {
        Iterable<C> cs = Iterables.concat(Iterables.transform(bs, BToCsFunction.INSTANCE));
        Iterable<D> ds = Iterables.concat(Iterables.transform(cs, CToDsFunction.INSTANCE));
        return ds;
    }

    private enum BToCsFunction implements Function<B, Collection<C>> {
        INSTANCE;

        @Override
        public Collection<C> apply(B b) {
            return b.getCs();
        }
    }

    private enum CToDsFunction implements Function<C, Collection<D>> {
        INSTANCE;

        @Override
        public Collection<D> apply(C c) {
            return c.getDs();
        }
    }
}


public class B {
    private Collection<C> cs;

    public Collection<C> getCs() {
        return cs;
    }
}

public class C {
    private Collection<D> ds;

    public Collection<D> getDs() {
        return ds;
    }
}

Это хорошо работает, если ваша цель состоит в том, чтобы просто перебрать Ds, и вам действительно не нужно представление коллекции. Это позволяет избежать создания большой временной коллекции.

person Etienne Neveu    schedule 17.05.2011

Ответ на ваш вопрос будет зависеть от специфики вашей ситуации. Являются ли эти коллекции статическими или динамическими? Насколько велика ваша коллекция B в A? Собираетесь ли вы получить доступ только к D из A, или вы иногда захотите оказаться дальше в дереве или вернуть B или C? Как часто вы собираетесь получать доступ к одному и тому же набору D из определенного A? Может ли D (или C или B) быть связан с более чем 1 A?

Если все динамично, то лучший шанс улучшить производительность — это иметь родительские ссылки из C на A, а затем обновлять родителя всякий раз, когда изменяется список C D. Таким образом, вы можете хранить коллекцию D в своем объекте A и обновлять A всякий раз, когда один из C получает новый или удаляется.

Если все статично и существует некоторое повторное использование коллекций D из каждого A, то кэширование может быть хорошим выбором, особенно если имеется много B. A будет иметь карту с ключом B и значением набора D. Метод getAllDs() сначала проверит, есть ли у карты ключ для B, и если да, то вернет свою коллекцию D. Если нет, то он сгенерирует коллекцию, сохранит ее в карте кеша и вернет коллекцию.

Вы также можете использовать дерево для хранения объектов, особенно если они достаточно простые. Например, вы можете создать объект XML DOM и использовать выражения XPath для извлечения нужного вам подмножества D. Это обеспечило бы гораздо более динамичный доступ к интересующим вас наборам объектов.

Каждое из этих решений имеет различные компромиссы с точки зрения стоимости установки, стоимости обслуживания, своевременности результатов, гибкости использования и стоимости получения результатов. Что вы должны выбрать, будет зависеть от вашего контекста.

person sockets-to-me    schedule 16.05.2011

На самом деле, я думаю, что Iterables.concat (или IteratorChain из Apache Commons) отлично подойдет для вашего случая:

class A {
    Collection<B> children;
    Iterator<D> getAllD() {
        Iterator<Iterator<D>> iters = new ArrayList<Iterator<D>>();
        for (B child : children) {
            iters.add(child.getAllD());
        }
        Iterator<D> iter = Iterables.concat(iters);
        return iter;
    }
}
class B {
    Collection<C> children;
    Iterator<D> getAllD() {
        Iterator<Iterator<D>> iters = new ArrayList<Iterator<D>>();
        for (C child : children) {
            iters.add(child.getAllD());
        }
        Iterator<D> iter = Iterables.concat(iters);
        return iter;
    }
}
class C {
    Collection<D> children;
    Iterator<D> getAllD() {
        Iterator<D> iter = children.iterator();
        return iter;
    }
}
person Nathan Ryan    schedule 16.05.2011

Это не может быть очень эффективным.

Итерация в памяти происходит чертовски быстро. Кроме того, эффективность создания ArrayList из 10 тыс. элементов по сравнению с созданием 10 ArrayList с 1 тыс. элементов каждый не будет сильно отличаться. Итак, в заключение, вы, вероятно, должны сначала просто пойти с самой простой итерацией. Скорее всего, это работает просто отлично.

Даже если у вас есть миллион элементов, вероятно, будет разумно реализовать прямую итерацию в любом случае для сравнения. В противном случае вы не знаете, можете ли вы оптимизировать или замедляете работу, делая вещи умно.

Сказав это, если вы хотите оптимизировать последовательный доступ для чтения ко всем D, я бы сохранил «индекс» снаружи. Индекс может быть LinkedList, ArrayList, TreeList и т. д. в зависимости от вашей ситуации. Например, если вы не уверены в длине индекса, вероятно, будет разумнее избегать ArrayList. Если вы хотите эффективно удалять случайные элементы, используя ссылку на этот элемент, OrderedSet может быть намного лучше, чем список и т. д.

Когда вы делаете это, вам нужно беспокоиться о согласованности индекса и фактических ссылок в ваших классах. т.е. больше сложности = больше места для скрытия ошибок. Таким образом, если вы не сочтете это необходимым с помощью тестирования производительности, действительно не рекомендуется пытаться оптимизировать.

(Кстати, отказ от создания экземпляров новых объектов коллекции вряд ли ускорит работу, если только вы не говорите об ЭКСТРЕМАЛЬНО высокопроизводительном коде. Создание объектов в современных JVM занимает всего несколько десятков наносекунд или что-то в этом роде. Кроме того, вы можете по ошибке использовать ArrayList, имеющий небольшая начальная длина или что-то в этом роде и усугубляет ситуацию)

person Enno Shioji    schedule 17.05.2011