Как обрабатывать коллекцию базового класса, если требуются ссылки на подтипы, без двойного сохранения? - PullRequest
0 голосов
/ 28 мая 2020

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

Я заканчиваю вверх со списком allAs. Во время выполнения должно выполняться несколько алгоритмов, которые работают с подклассами A, т.е. у меня есть некоторые функции al go (List bs), al go (List cs), ..., и для их вызова мне нужно извлечь все B, C или все, что мне нужно из Списка. С другой стороны, есть алгоритмы, работающие с коллекциями A.

Следующее:

  • В какой-то момент мне нужно сохранить ссылки на экземпляры A
  • В какой-то момент мне нужно сохранить ссылки на экземпляры подклассов
  • Все эти данные должны быть синхронизированы

Вопрос в том, какие лучшие / плохие практики для этого? На ум приходят следующие решения:

  • Придерживайтесь List allAs и вызывайте as.stream (). Filter () для извлечения подсписок заданных подклассов каждый раз, когда мне нужно вызвать алгоритм подкласса
  • Создайте отдельный список для каждого подкласса и объедините их все, если мне нужно вызвать алгоритм
    , который действует на экземпляры базового класса
  • Сделайте и то, и другое, но затем при обновлении / добавлении / удалить действия, которые мне нужно выполнить для каждого из списков
    (которые мне в любом случае нужны для второго решения)

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

1 Ответ

2 голосов
/ 28 мая 2020

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

Collection<A> data = externalSource();

// preparation step
class State<T> {
    final Class<T> type;
    int s, e;
    List<?> list;
    State(Class<T> c) {
        type = c;
    }
    @SuppressWarnings("unchecked") <U> List<U> as(Class<U> c) {
        if(c != type) throw new ClassCastException(c+" != "+type);
        return (List<U>)list;
    }
}
Map<Class<? extends A>,State<?>> tmp = new HashMap<>();
for(A a: data) tmp.computeIfAbsent(a.getClass(), State::new).s++;
int size = 0;
for(State<?> s: tmp.values()) {
    int next = s.s;
    s.s = s.e = size;
    size += next;
}
A[] all = new A[size];
for(A a: data) all[tmp.get(a.getClass()).e++] = a;
List<A> listOfAll = Arrays.asList(all);
for(State<?> s: tmp.values())
    s.list = listOfAll.subList(s.s, s.e);

// now, all lists are available, listOfAll for all As and:
List<B> listOfB = tmp.get(B.class).as(B.class);
List<C> listOfC = tmp.get(C.class).as(C.class);
List<D> listOfD = tmp.get(D.class).as(D.class);
List<E> listOfE = tmp.get(E.class).as(E.class);

System.out.println("B: "+listOfB+",\nC: "+listOfC+",\nD: "+listOfD+",\nE: "+listOfE);

Это выполняет сортировку с подсчетом в массив A[] all, тогда все списки создаются как view в массиве, не имея собственного хранилища. Исходную коллекцию можно отбросить, оставив ее для сборки мусора.

Существует одна неизбежная непроверенная операция, но обратите внимание, что она имеет явную проверку времени выполнения для обеспечения безопасности, предполагая инициализацию код, показанный выше.

Этот код легко применим к другому количеству подклассов.

Демо на Ideone

...