close()
メソッドを使用して最後に解放する必要があるリソースを使用して、カスタムJava.util.Iteratorを実装しました。そのリソースは、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)をあげます。 hasNext()がfalseを返す場合、私はすでにイテレータを閉じています。私の問題は、私の例に示されているように、ループの反復が最後の反復の前の前で中断する場合です。
問題は条件最後にです。多くの場合、完全なコレクションまたはデータセットを反復処理するため、最後に現時点では、読み取るデータが残っていません。
ただし、End Of Dataに到達する前にループから抜け出すように設定した場合、イテレーターは終了せず、閉じられません。
解決策は、構築中にデータソースのコンテンツをイテレータにキャッシュし、リソースを閉じることです。したがって、イテレータは開かれたリソースではなく、キャッシュされたデータで機能します。
AutoCloseableインターフェイス を実装するカスタムイテレータを作成します
public interface CloseableIterator<T> extends Iterator<T>, AutoCloseable {
}
そして、このイテレータを リソースステートメントで試してください で使用します。
try(CloseableIterator iterator = dao.findAll()) {
while(iterator.hasNext()){
process(iterator.next());
}
}
このパターンは、次のような場合に、基になるリソースを閉じます。-ステートメントの完了後-例外がスローされた場合でも
最後に、このイテレータの使用方法を明確に文書化します。
クローズコールを委任したくない場合は、プッシュ戦略を使用します。例えば。 Java 8ラムダ:
dao.findAll(r -> process(r));
実装では、イテレーターが使い果たされたときに、自分で閉じることができます。
public boolean hasNext() {
....
if( !hasNext ) {
this.close();
}
return hasNext;
}
そして明確に文書化する:
このイテレータは、hasNext()がfalseを返すときにclose()を呼び出します。イテレータを破棄する必要がある場合は、必ずcloseを呼び出す必要があります
例:
void testIt() {
Iterator i = DbIterator.connect("db.config.info.here");
try {
while( i.hasNext() {
process( i.next() );
}
} finally {
if( i != null ) {
i.close();
}
}
}
ところで、そこにいる間にIterableを実装し、拡張されたforループを使用することができます。
finalizer で閉じることもできますが、希望する動作は得られません。ファイナライザーは、ガベージコレクターがオブジェクトをクリーンアップする場合にのみ呼び出されるため、リソースが開いたままになる可能性があります。さらに悪いことに、誰かがあなたのイテレータを握った場合、それは決して閉じません。
Falseを返すhasNext()への最初の呼び出しでストリームを閉じる可能性があります。誰かが最初の要素だけを繰り返し、二度とそれを気にしないかもしれないので、それはまだそれをすることが保証されていません。
本当に、外部ライブラリを扱うときは自分で管理する必要があると思います。 iterableを使用するメソッドを呼び出すので、完了したら自分で閉じてみませんか?リソース管理は、それ以上のことを知らない外部ライブラリに課すことができるものではありません。
Closeメソッドを含むIteratorの独自のサブインターフェイスを定義し、通常の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();
}
};
可能であれば、イテレータをStreamでラップします。これにより、ストリームのonClose
メソッドにアクセスできるようになります。次に、クローズロジックをそのメソッドに移動し、そこでクリーンアップを実行する必要があります。
例:
StreamSupport.stream(Spliterators.spliteratorUnknownSize(
new MyCustomIterator<T>(), 0), false).onClose(() -> {
// close logic here
});
オブジェクトストリームのようなイテレータを使用しているプロジェクトの1つで同様の問題が発生しました。 Iteratorが完全に消費されていない時間をカバーするために、closeメソッドも必要でした。最初は単純にIteratorインターフェイスとClosableインターフェイスを拡張しましたが、もう少し深く掘り下げて、Java 1.7で導入されたtry-with-resourcesステートメントは、それを実装するためのきちんとした方法を提供すると思いました。
IteratorおよびAutoCloseableインターフェースを拡張し、Closeメソッドを実装して、try-with-resourcesでIteratorを使用します。イテレータがスコープ外になると、ランタイムはcloseを呼び出します。
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;
}
} `