Закрытие java.util.Iterator
Я реализовал пользовательский java.util.Iterator, используя ресурс, который должен быть выпущен в конце, используя метод close()
. Этот ресурс может быть java.sql.ResultSet, java.io.InputStream и т.д.
public interface CloseableIterator<T> extends Iterator<T>
{
public void close();
}
Некоторые внешние библиотеки, использующие этот итератор, могут не знать, что он должен быть закрыт. например:
public boolean isEmpty(Iterable<T> myiterable)
{
return myiterable.iterator().hasNext();
}
В этом случае существует ли способ закрыть этот итератор?
Обновление: большое спасибо за текущие ответы. Я дам (+1) всем. Я уже закрываю Iterator, когда hasNext() возвращает false. Моя проблема заключается в том, что повторение цикла прерывается до последней итерации, как показано в моем примере.
Ответы
Ответ 1
Проблема заключается в условии в конце. Часто мы перебираем полный набор или набор данных, поэтому мы в конце на данный момент, нет данных для чтения.
Но если мы установим разрыв цикла, прежде чем мы достигнем конца данных, итератор не остановится и не будет закрыт.
Выход может состоять в том, чтобы кэшировать содержимое источника данных в итераторе во время построения и закрывать ресурс. Таким образом, итератор не будет работать с открытым ресурсом, кроме кэшированных данных.
Ответ 2
В вашей реализации вы могли бы закрыть его самостоятельно, если когда итератор исчерпан.
public boolean hasNext() {
....
if( !hasNext ) {
this.close();
}
return hasNext;
}
И ясно документ:
Этот итератор будет вызывать close(), когда hasNext() возвращает false, если вам нужно удалить итератор, прежде чем вы убедитесь, что вы запустили self
Пример:
void testIt() {
Iterator i = DbIterator.connect("db.config.info.here");
try {
while( i.hasNext() {
process( i.next() );
}
} finally {
if( i != null ) {
i.close();
}
}
}
Btw, вы могли бы, пока вы там, вы могли бы реализовать Iterable и использовать расширенный цикл.
Ответ 3
Вы можете закрыть его в финализаторе, но он не даст вам поведения, которое вы хотите. Ваш финализатор вызывается только тогда, когда сборщик мусора хочет очистить ваш объект, чтобы ваш ресурс оставался открытым. Хуже того, если кто-то держит ваш итератор, он никогда не будет закрыт.
Есть возможность закрыть поток при первом вызове hasNext(), который возвращает false. Это все еще не гарантировано, потому что кто-то может перебирать только первый элемент и никогда больше не беспокоится об этом.
На самом деле, я думаю, вам нужно будет управлять им самостоятельно, когда имеете дело с внешней библиотекой. Вы собираетесь делать эти призывы к методам, которые используют итерируемый, поэтому почему бы не закрыть его самостоятельно, когда закончите? Управление ресурсами - это не то, что вы можете просто наложить на внешнюю библиотеку, которая не знает ничего лучше.
Ответ 4
Создайте собственный итератор, который реализует интерфейс AutoCloseable
public interface CloseableIterator<T> extends Iterator<T>, AutoCloseable {
}
И затем используйте этот итератор в попробуйте с помощью оператора ресурсов.
try(CloseableIterator iterator = dao.findAll()) {
while(iterator.hasNext()){
process(iterator.next());
}
}
Этот шаблон закроет базовый ресурс:
- после завершения заявления
- и даже если выбрано исключение
Наконец, четко документируйте, как этот итератор должен использоваться.
Если вы не хотите делегировать закрытые вызовы, используйте стратегию push. например. с java 8 lambda:
dao.findAll(r -> process(r));
Ответ 5
Просто определите свой собственный под-интерфейс Iterator, который включает метод close, и убедитесь, что вы используете его вместо обычного класса Iterator. Например, создайте этот интерфейс:
import java.io.Closeable;
import java.util.Iterator;
public interface CloseableIterator<T> extends Iterator<T>, Closeable {}
И тогда реализация может выглядеть так:
List<String> someList = Arrays.asList( "what","ever" );
final Iterator<String> delegate = someList.iterator();
return new CloseableIterator<String>() {
public void close() throws IOException {
//Do something special here, where you have easy
//access to the vars that created the iterator
}
public boolean hasNext() {
return delegate.hasNext();
}
public String next() {
return delegate.next();
}
public void remove() {
delegate.remove();
}
};
Ответ 6
У меня была аналогичная проблема в одном из моих проектов с использованием Iterator, как поток Object. Чтобы покрыть время, в течение которого Итератор полностью не потребляется, мне также нужен был близкий метод.
Первоначально я просто расширил интерфейсы Iterator и Closable, но, копаясь немного глубже, оператор try-with-resources, введенный в java 1.7, я думал, что он обеспечивает опрятный способ его реализации.
Вы расширяете интерфейсы Iterator и AutoCloseable, реализуете метод Close и используете Iterator в try-with-resources.
Время выполнения вызовет вас близко, как только Iterator будет недоступен.
https://docs.oracle.com/javase/7/docs/api/java/lang/AutoCloseable.html
https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html
Например
Интерфейс:
public interface MyIterator<E> extends Iterator<E>, AutoCloseable {
}
Реализация этого: `
Открытый класс MyIteratorImpl реализует MyIterator {
private E nextItem = null;
@Override
public boolean hasNext() {
if (null == nextItem)
nextItem = getNextItem();
return null != nextItem;
}
@Override
public E next() {
E next = hasNext() ? nextItem : null;
nextItem = null;
return next;
}
@Override
public void close() throws Exception {
// Close off all your resources here, Runtime will call this once
Iterator out of scope.
}
private E getNextItem() {
// parse / read the next item from the under laying source.
return null;
}
}
`
И пример использования его с try -with-resource:
`
открытый класс MyIteratorConsumer {
/**
* Simple example of searching the Iterator until a string starting
* with the given string is found.
* Once found, the loop stops, leaving the Iterator partially consumed.
* As 'stringIterator' falls out of scope, the runtime will call the
* close method on the Iterator.
* @param search the beginning of a string
* @return The first string found that begins with the search
* @throws Exception
*/
public String getTestString(String search) throws Exception {
String foundString = null;
try (MyIterator<String> stringIterator = new MyIteratorImpl<>()) {
while (stringIterator.hasNext()) {
String item = stringIterator.next();
if (item.startsWith(search)) {
foundString = item;
break;
}
}
}
return foundString;
}
}
`