У меня есть реализация java.util.Iterator
который требует что вызов к next()
должен всегда продолжаться вызовом к hasNext()
. (Это вызвано тем, что результаты возвращаются asynchronosly в многопоточной среде, и это никогда не ясно насколько больше результатов, там мог бы быть).
Это было бы 'корректно', чтобы правильно зарегистрировать это в JavaDoc и затем бросить a RuntimeException
если это было нарушено. Или это расширяет интерфейс Iterator немного слишком далеко?
Все мысли ценятся?
Возможно, здесь что-то не хватает, но почему бы не вызвать hasNext ()
внутри вашей реализации?
Мне кажется, ты делаешь что-то вроде этого:
class IteratorImpl<T> implements Iterator<T> {
private Source<T> source = ...
private T next = null;
public boolean hasNext() {
if(next == null) {
next = source.poll();
}
return next != null;
}
Звучит неплохо. Я не могу представить себе ситуацию, в которой вы захотели бы использовать следующий
без hasNext
- это был бы рецепт исключений.
EDIT:
В doc для hasNext()
написано:
Возвращает true, если в итерации больше элементов. (Другими словами, возвращает true, если следующая вернет элемент, а не бросает исключение.)
Для меня реализация не нарушает договора. Тем не менее, я бы (как предполагает Фабиан Стиг) все равно реализовал next()
, так как:
public T next() {
if(!hasNext()) {
throw new NoSuchElementException();
}
T ret = next;
next = null;
return ret;
}
Я имею в виду, сколько на самом деле стоит эта проверка?
Вы должны проверить и бросить NoSuchElementException
в соответствии с контрактом API. Либо тестирование на !hasNext()
или next == null
будет соответствовать этому критерию, я полагаю, но я бы отдал предпочтение первому.
Если кто-то ловит NoSuchElementException
вместо вызова hasNext()
, то, вероятно, у вас проблемы посерьезнее.
В нашем проекте мы используем репозиторий, чтобы вести себя точно так же, как коллекция сущностей, и UnitOfWork используется для отслеживания изменений в этих сущностях и для записи их обратно в хранилище данных.
Если вы используете LinqToSql или какой-либо другой ИЛИ Mapper, то это, скорее всего, само по себе реализует образец UnitOfWork, поэтому часто мы просто используем экземпляр ORMapper в нашем собственном IUnitOfWork.
Наш интерфейс репозитория, как правило, что-то вроде..
IEnumerable<Order> FindByCustomerId(string customerId);
void Add(Order order);
void Remove(Order order);
У нас нет метода сохранения в репозитории. Если UnitOfWork не требуется, методы Add/Remove действуют непосредственно в хранилище данных.
Если нам нужен UnitOfWork, то общедоступный интерфейс будет чем-то вроде...
void Commit();
void Rollback();
Репозиторий имеет внутренний интерфейс с UnitOfWork, поэтому при запросе репозитория возвращенные объекты отслеживаются UnitOfWork на предмет изменений. Метод фиксации записывает изменения обратно в хранилище данных, метод отката просто очищает их.
Когда мы используем LinqToSql, DataContext заботится об отслеживании изменений, при откате мы просто создаем экземпляр нового контекста. Стойкость обрабатывается в корне и его потомках. Один экземпляр UnitOfWork является общим для всех репозиториев.
Когда мы не используем LinqToSql, тогда мы реализуем наш собственный UnitOfWork, возможно, он вызывает веб-службу или что-то подобное, в этом случае мы изменяем отслеживание в самих классах сущностей, используя класс EntityBase.
У нас есть репозиторий для каждого корня, но иногда потомки одного корня используются в качестве самих корней, поэтому нам часто нужно что-то вроде OrderLineRepository, потому что у нас есть пример использования в нашей системе, если пользователь хочет искать строки заказа.
-121--2225243- Если вызовы hasNext ()
и next ()
не находятся в синхронизированном блоке/методе, не гарантируется наличие элементов даже при вызове hasNext ()
перед next ()
.
Контракт интерфейса Iterator
заключается в том, что NoSuchElureException
должен быть создан, если больше нет элементов. Поэтому продолжайте использовать метод next ()
до тех пор, пока не возникнет такое исключение.
Поэтому взгляните на пакет java.util.concurrent
- он имеет параллельные коллекции, итераторы которых могут вам помочь - т.е. вы можете использовать эти коллекции и итераторы вместо реализации собственных.
Я бы предпочел сделать исключение из next ()
, когда больше нет элементов. В многопоточной среде hasNext ()
в любом случае бесполезен.
Если ваши вызовы hasNext ()
и next ()
не находятся в синхронизированном блоке / методе, не гарантируется, что у вас будут элементы, даже если вы вызовете hasNext ()
перед next ()
.
Контракт интерфейса Iterator
заключается в том, что NoSuchElementException
должно выдаваться, если больше нет элементов. Поэтому продолжайте использовать метод next ()
, пока не возникнет такое исключение.
Тем не менее, обратите внимание на пакет java.util.concurrent
- в нем есть параллельные коллекции, итераторы которых могут вам помочь, т.е. вы можете использовать эти коллекции и итераторы вместо реализации своих собственных.
Вы можете сделать нечто подобное описанному ниже, при котором вы делегируете получение базовых данных закрытому методу, и реализовать hasNext()
и next()
, чтобы по-другому реагировать на отсутствие данных. Это имеет то преимущество, что можно многократно вызывать next()
, не вызывая сначала hasNext()
, и, следовательно, не нарушать договор Iterator.
public class IteratorImpl<T> implements Iterator<T> {
private final Source<T> source;
private T next;
public synchronized boolean hasNext() {
tryGetNext();
return next != null;
}
public synchronized T next() {
tryGetNext();
if (next == null) {
throw new NoSuchElementException();
}
return next;
}
private void tryGetNext() {
if (next != null) {
next = source.poll();
}
}
}
Требование вызова hasNext ()
перед next ()
нарушает итератор
договор. Вам действительно следует переписать его так, чтобы next ()
просто генерировало исключение NoSuchElementException
, если нет элемента для возврата.
Что вы можете сделать, так это реализовать интерфейс Iterator
самостоятельно (если вам нужно взаимодействовать с другим API) и попросить своих клиентов реализовать свой собственный более строгий интерфейс.
Я создал такой класс в своей библиотеке функционального программирования, чтобы легко реализовать Итератор
вокруг ResultSet
в рабочем проекте.
Мой класс EasierIterator реализует интерфейс Iterator, требуя, чтобы клиент реализовал более простой интерфейс, основанный на moveNext ()
/ getCurrent ()
. Фактически он кэширует текущий элемент за вас.