[英]Simplify writing custom iterators in Java
在Java中為自定義集合編寫迭代器非常復雜,因為您不必編寫提供一個元素的直接代碼,而是必須編寫狀態機:
public class CustomCollection<T> implements Iterable<T>
{
private T[] data;
private int size;
@Override
public Iterator<T> iterator()
{
return new Iterator<T>()
{
private int cursor = 0;
@Override
public boolean hasNext()
{
return cursor < size;
}
@Override
public T next()
{
return data[cursor++];
}
@Override
public void remove()
{
throw new UnsupportedOperationException();
}
};
}
// ...
}
對於比數組列表或鏈表更復雜的集合,正確獲取這些狀態機是一項艱巨的任務。 事實上,C#設計團隊認為編寫自定義迭代器足夠復雜,可以引入特殊的語言支持( yield return
),讓編譯器構建狀態機。
在下一版本的Java中會出現類似於yield return
嗎? 或者是否有任何庫解決方案使我在Java中編寫自己的迭代器時生活更輕松?
不,Java沒有像yield
這樣的東西。 就庫而言, Guava有許多有用的類可以使某些類型的迭代器易於編寫:
AbstractIterator
只需要您實現T computeNext()
方法。 AbstractLinkedIterator
要求您實現T computeNext(T previous)
。 AbstractIterator
可用於此,如下所示:
return new AbstractIterator<T>() {
private int index = 0;
protected T computeNext() {
return index == size ? endOfData() : data[index++];
}
};
您也可以像Amir建議的那樣使用Arrays.asList
,甚至可以這樣做:
private final List<T> listView = new AbstractList<T>() {
public int size() {
return data.length;
}
public T get(int index) {
return data[index];
}
};
public Iterator<T> iterator() {
return listView.iterator();
}
也許我只是不理解你的問題。 你能不能return Arrays.asList(data).iterator()
Java總是提供一種機制來維護狀態並在以后的某個時間繼續執行:線程。 我的庫解決方案的基本思想是讓ConcurrentIterable
在一個線程中生成元素,讓ConcurrentIterator
在另一個線程中使用它們,通過有界隊列進行通信。 (這通常被稱為生產者/消費者模式。)
首先,這是簡化用法的演示:
public class CustomCollection<T> extends ConcurrentIterable<T>
{
private T[] data;
private int size;
@Override
protected void provideElements()
{
for (int i = 0; i < size; ++i)
{
provideElement(data[i]);
}
}
// ...
}
請注意完全沒有狀態機。 您所要做的就是從ConcurrentIterable
派生並實現方法provideElements
。 在此方法中,您可以編寫直接代碼,該代碼為集合中的每個元素調用provideElement
。
有時,客戶端不會遍歷整個集合,例如在線性搜索中。 一旦檢測到墮胎,您可以通過檢查iterationAborted()
來停止提供元素:
@Override
protected void provideElements()
{
for (int i = 0; i < size && !iterationAborted(); ++i)
{
provideElement(data[i]);
}
}
只要您不關心生成的其他元素,檢查iterationAborted()
就完全沒問題。 對於無限序列,檢查iterationAborted()
是必需的。
生產者如何檢測消費者已停止迭代? 這是通過對消費者中的令牌的強引用以及對生產者中的相同令牌的弱引用來實現的。 當消費者停止迭代時,該令牌變得有資格進行垃圾收集,並且它最終將對生產者不可見。 從那時起,所有新元素都將被丟棄。
(如果沒有這種預防措施,在某些情況下,有界隊列最終可能會填滿,生產者將進入無限循環,並且所包含的元素永遠不會被垃圾收集。)
現在為實施細節:
import java.util.Iterator;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
public abstract class ConcurrentIterable<T> implements Iterable<T>
{
private static final int CAP = 1000;
private final ThreadLocal<CommunicationChannel<T>> channels
= new ThreadLocal<CommunicationChannel<T>>();
@Override
public Iterator<T> iterator()
{
BlockingQueue<Option<T>> queue = new ArrayBlockingQueue<Option<T>>(CAP);
Object token = new Object();
final CommunicationChannel<T> channel
= new CommunicationChannel<T>(queue, token);
new Thread(new Runnable()
{
@Override
public void run()
{
channels.set(channel);
provideElements();
enqueueSentinel();
}
}).start();
return new ConcurrentIterator<T>(queue, token);
}
protected abstract void provideElements();
protected final boolean iterationAborted()
{
return channels.get().iterationAborted();
}
protected final void provideElement(T element)
{
enqueue(Option.some(element));
}
private void enqueueSentinel()
{
enqueue(Option.<T> none());
}
private void enqueue(Option<T> element)
{
try
{
while (!offer(element))
{
System.gc();
}
}
catch (InterruptedException ignore)
{
ignore.printStackTrace();
}
}
private boolean offer(Option<T> element) throws InterruptedException
{
CommunicationChannel<T> channel = channels.get();
return channel.iterationAborted()
|| channel.queue.offer(element, 1, TimeUnit.SECONDS);
}
}
import java.lang.ref.WeakReference;
import java.util.concurrent.BlockingQueue;
public class CommunicationChannel<T>
{
public final BlockingQueue<Option<T>> queue;
private final WeakReference<Object> token;
public CommunicationChannel(BlockingQueue<Option<T>> queue, Object token)
{
this.queue = queue;
this.token = new WeakReference<Object>(token);
}
public boolean iterationAborted()
{
return token.get() == null;
}
}
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.concurrent.BlockingQueue;
public class ConcurrentIterator<T> implements Iterator<T>
{
private final BlockingQueue<Option<T>> queue;
@SuppressWarnings("unused")
private final Object token;
private Option<T> next;
public ConcurrentIterator(BlockingQueue<Option<T>> queue, Object token)
{
this.queue = queue;
this.token = token;
}
@Override
public boolean hasNext()
{
if (next == null)
{
try
{
next = queue.take();
}
catch (InterruptedException ignore)
{
ignore.printStackTrace();
}
}
return next.present;
}
@Override
public T next()
{
if (!hasNext()) throw new NoSuchElementException();
T result = next.value;
next = null;
return result;
}
@Override
public void remove()
{
throw new UnsupportedOperationException();
}
}
public class Option<T>
{
public final T value;
public final boolean present;
private Option(T value, boolean present)
{
this.value = value;
this.present = present;
}
public static <T> Option<T> some(T value)
{
return new Option<T>(value, true);
}
@SuppressWarnings("unchecked")
public static <T> Option<T> none()
{
return none;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private static final Option none = new Option(null, false);
}
讓我知道你的想法!
如果您不介意啟動新線程,可以使用SynchronousQueue
public class InternalIterator<T> implements Iterator<T>{
private SynchronousQueue<T> queue = new SynchronousQueue<T>();
private volatile boolean empty = false;
private T current =null;
private Object lock = new Object();
private Runner implements Runnable{//run in deamon
public void run(){
//iterate and call
synchronized()(lock){
try{
queue.offer(t);
lock.wait();
}catch(InteruptedException e){
empty=true;
throw new RuntimeException(e);
}
}
//for each element to insert this will be the yield return
emtpy=true;
}
}
public boolean hasNext(){
if(current!=null)return true;
while(!empty){
if( (current=queue.poll(100,TimeUnit.MILLISECONDS))!=null){//avoid deadlock when last element is already returned but empty wasn't written yet
return true;
}
}
return false;
}
public boolean next(){
if(!hasNext())throw new NoSuchElementException();
T tmp = current;
current=null;
return tmp;
}
public void remove(){
throw new UnsupportedOperationException();
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.