Ответ 1
У Guava (ранее Google Collections) есть Iterators.concat.
Можно ли объединить итераторы в Java? У меня есть два итератора, и я хочу объединить/объединить их, чтобы я мог выполнять итерацию, хотя их элементы за один раз (в том же цикле), а не два шага. Это возможно?
Обратите внимание, что количество элементов в двух списках может быть различным, поэтому один цикл по обоим спискам не является решением.
Iterator<User> pUsers = userService.getPrimaryUsersInGroup(group.getId());
Iterator<User> sUsers = userService.getSecondaryUsersInGroup(group.getId());
while(pUsers.hasNext()) {
User user = pUsers.next();
.....
}
while(sUsers.hasNext()) {
User user = sUsers.next();
.....
}
У Guava (ранее Google Collections) есть Iterators.concat.
Также Apache Commons Collection имеет несколько классов для управления итераторами, например IteratorChain, который обертывает несколько Итераторов.
Вы можете создать собственную реализацию интерфейса Iterator
, который выполняет итерацию по итераторам:
public class IteratorOfIterators implements Iterator {
private final List<Iterator> iterators;
public IteratorOfIterators(List<Iterator> iterators) {
this.iterators = iterators;
}
public IteratorOfIterators(Iterator... iterators) {
this.iterators = Arrays.asList(iterators);
}
public boolean hasNext() { /* implementation */ }
public Object next() { /* implementation */ }
public void remove() { /* implementation */ }
}
(Я не добавлял generics в Iterator для краткости.) Реализация не слишком сложна, но не самая тривиальная, вам нужно отслеживать, какой Iterator
вы в настоящее время выполняете итерации, и вызываете next()
вам нужно выполнить итерацию, насколько это возможно, с помощью итераторов, пока не найдете hasNext()
, который возвращает true
, или вы можете попасть в конец последнего итератора.
Я не знаю никакой реализации, которая уже существует для этого.
Обновление:
Я проголосовал за от Эндрю Даффи - не нужно заново изобретать колесо. Мне действительно нужно глубже изучить Гуаву.
Я добавил еще один конструктор для переменного количества аргументов - почти отвлекаясь от темы, так как способ построения класса здесь не представляет интереса, просто концепция того, как он работает.
Я не писал Java-код через некоторое время, и мне стало интересно, все-таки я его получил.
Первая попытка:
import java.util.Iterator;
import java.util.Arrays; /* For sample code */
public class IteratorIterator<T> implements Iterator<T> {
private final Iterator<T> is[];
private int current;
public IteratorIterator(Iterator<T>... iterators)
{
is = iterators;
current = 0;
}
public boolean hasNext() {
while ( current < is.length && !is[current].hasNext() )
current++;
return current < is.length;
}
public T next() {
while ( current < is.length && !is[current].hasNext() )
current++;
return is[current].next();
}
public void remove() { /* not implemented */ }
/* Sample use */
public static void main(String... args)
{
Iterator<Integer> a = Arrays.asList(1,2,3,4).iterator();
Iterator<Integer> b = Arrays.asList(10,11,12).iterator();
Iterator<Integer> c = Arrays.asList(99, 98, 97).iterator();
Iterator<Integer> ii = new IteratorIterator<Integer>(a,b,c);
while ( ii.hasNext() )
System.out.println(ii.next());
}
}
Конечно, вы могли бы использовать больше классов Collection, а не счетчик чистого индекса + index, но на самом деле это немного лучше, чем альтернатива. Или я просто склонен писать в основном в эти дни?
Во всяком случае, вы идете. Ответ на ваш вопрос: "да, возможно".
переместите свой цикл в метод и передайте итератору метод.
void methodX(Iteartor x) {
while (x.hasNext()) {
....
}
}
итератор приходит из коллекции или набора.
почему бы не использовать уже доступный метод Collection.addAll(Collection c);
а затем создайте свой итератор из последнего объекта.
таким образом, ваш итератор будет перебирать все содержимое обеих коллекций.
public class IteratorJoin<T> implements Iterator<T> {
private final Iterator<T> first, next;
public IteratorJoin(Iterator<T> first, Iterator<T> next) {
this.first = first;
this.next = next;
}
@Override
public boolean hasNext() {
return first.hasNext() || next.hasNext();
}
@Override
public T next() {
if (first.hasNext())
return first.next();
return next.next();
}
}
Вы можете использовать мою версию расширяемого итератора. Он использует двунаправленную очередь итераторов, которая для меня имеет смысл:
import java.util.Deque;
import java.util.Iterator;
import java.util.concurrent.ConcurrentLinkedDeque;
public class ExtendableIterator<T> implements Iterator<T> {
public Deque<Iterator<T>> its = new ConcurrentLinkedDeque<Iterator<T>>();
public ExtendableIterator() {
}
public ExtendableIterator(Iterator<T> it) {
this();
this.extend(it);
}
@Override
public boolean hasNext() {
// this is true since we never hold empty iterators
return !its.isEmpty() && its.peekLast().hasNext();
}
@Override
public T next() {
T next = its.peekFirst().next();
if (!its.peekFirst().hasNext()) {
its.removeFirst();
}
return next;
}
public void extend(Iterator<T> it) {
if (it.hasNext()) {
its.addLast(it);
}
}
}
Я бы реорганизовал исходный дизайн:
Iterator<User> pUsers = userService.getPrimaryUsersInGroup(group.getId());
Iterator<User> sUsers = userService.getSecondaryUsersInGroup(group.getId());
Что-то вроде:
Iterator<User> users = userService.getUsersInGroup(group.getId(), User.PRIMARY, User.SECONDARY, ...);
Объединенный итератор:
import static java.util.Arrays.asList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
public class ConcatIterator<T> implements Iterator<T> {
private final List<Iterable<T>> iterables;
private Iterator<T> current;
@SafeVarargs
public ConcatIterator(final Iterable<T>... iterables) {
this.iterables = new LinkedList<>(asList(iterables));
}
@Override
public boolean hasNext() {
checkNext();
return current != null && current.hasNext();
}
@Override
public T next() {
checkNext();
if (current == null || !current.hasNext()) throw new NoSuchElementException();
return current.next();
}
@Override
public void remove() {
if (current == null) throw new IllegalStateException();
current.remove();
}
private void checkNext() {
while ((current == null || !current.hasNext()) && !iterables.isEmpty()) {
current = iterables.remove(0).iterator();
}
}
}
Метод concat
для создания Iterable
:
@SafeVarargs
public static <T> Iterable<T> concat(final Iterable<T>... iterables) {
return () -> new ConcatIterator<>(iterables);
}
Простой тест JUnit:
@Test
public void testConcat() throws Exception {
final Iterable<Integer> it1 = asList(1, 2, 3);
final Iterable<Integer> it2 = asList(4, 5);
int j = 1;
for (final int i : concat(it1, it2)) {
assertEquals(j, i);
j++;
}
}
Вы можете попробовать ConcatIterator
из Cactoos:
Iterator<String> names = new ConcatIterator<>(
Arrays.asList("Sarah", "Mary").iterator(),
Arrays.asList("Jeff", "Johnny").iterator(),
);
Также проверьте ConcatIterable
, который объединяет Iterable
s.
Начиная с Java 8 и более поздних версий, это можно сделать без внешних зависимостей, используя Stream API. Это также позволяет объединять итератор с другими типами потоков.
Streams.concat(StreamSupport.stream(<iter1>, false), StreamSupport.stream(<iter2>, false));
каждый объект Iterator
сохраняет собственное расположение памяти (адрес), поэтому вы не можете просто "объединить" их. кроме того, если вы расширяете класс Iterator
и записываете свою собственную реализацию там.
Если вы имеете дело с тем же числом объектов в обоих итераторах, альтернативным решением было бы обработать два итератора в одном цикле следующим образом:
while (iterator1.hasNext() && iterator2.hasNext()) {
// code
}
В коллекциях Apache Commons есть public static <E> Iterator<E> org.apache.commons.collections4.IteratorUtils.chainedIterator(Collection<Iterator<? extends E>> iterators)
с надписью
Получает итератор, который перебирает коллекции итераторов один за другим.
что должно быть то, что вы хотите.
import java.util.Arrays;
import java.util.Iterator;
import org.apache.commons.collections4.IteratorUtils;
//also works: import org.apache.commons.collections.IteratorUtils;
class Scratch {
public static void main( String[] args ) {
final Iterator<String> combinedIterator = IteratorUtils.chainedIterator(
Arrays.asList( "a", "b", "c" ).iterator(),
Arrays.asList( "1", "2", "3" ).iterator()
);
while( combinedIterator.hasNext() ){
System.out.println( combinedIterator.next() );
}
// "abc123" will have been printed out
}
}