Help us understand the problem. What is going on with this article?

Java で yield return ぽいことを実現しよう!

More than 5 years have passed since last update.

Java には yield return のような構文は存在しないのですが、 Is there a Java equivalent to C#'s 'yield' keyword? の回答にあるとおり、頑張ればそれっぽい機能を実現することができます。例えば同 stackoverflow の質問の回答では

の二つのライブラリが具体例として紹介されています。

この二つのライブラリ、前者は Java の文法に yield のキーワードを追加していたりバイトコードいじいじするなどして頑張り過ぎていてちょっとカジュアル利用が難しい一方で、後者はマルチスレッドで yield ライクな機能をクラスとして提供していてカジュアルに利用できるかと思いきや、Maven が提供されていなかったり単一のクラスに収まっているわけでもなく、どちらにせよカジュアルな利用は難しい状況にあります。

そういうわけで、ないものは作ってしまえ精神を発揮して yield return ぽい機能を作ってみました。

Yielder.java
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * Java で yield return っぽいことを実現する機能を提供します。
 *
 * 以下は使い方の一例です。
 * <pre>
 * class YielderDemo {
 *   public static void main(String[] args) {
 *     for (Integer integer : Yielder.newIterable(new Yielder<Integer>() {
 *       public void run() {
 *         for (int i = 0; i < 10; i++) {
 *           yield(i);
 *         }
 *         for (int i = 30; i < 50; i++) {
 *           yield(i);
 *         }
 *       }
 *     })) {
 *       System.out.println(integer);
 *     }
 *   }
 * }
 * </pre>
 *
 * @author KOMIYA Atsushi
 */
public abstract class Yielder<T> {
    private static class Item<T> {
        T object;
        boolean ended = false;

        private Item<T> set(T object) {
            this.object = object;
            return this;
        }

        private Item<T> end() {
            this.ended = true;
            return this;
        }
    }

    private static class Context<T> {
        private final BlockingQueue<Item<T>> queue;
        private final Item<T>[] items;
        private int index;
        private boolean endReceived;
        private boolean ended;
        private Exception thrownException;

        Context(int queueSize) {
            queue = new ArrayBlockingQueue<>(queueSize);
            items = new Item[queueSize + 2];
            for (int i = 0; i < items.length; i++) {
                items[i] = new Item<>();
            }
        }

        synchronized boolean yield(T returnValue) {
            if (endReceived) {
                throw new IllegalStateException("#yieldEnd() has been called.");
            }

            try {
                Item<T> item = items[index++].set(returnValue);
                while (!queue.offer(item, 1000, TimeUnit.SECONDS)) {
                    if (ended) {
                        throw new IllegalStateException("iteration has been ended.");
                    }
                }

                if (index == items.length) {
                    index = 0;
                }

                return true;

            } catch (Exception e) {
                thrownException = e;
                return false;
            }
        }

        synchronized boolean yieldEnd() {
            if (endReceived) {
                throw new IllegalStateException("#yieldEnd() has been called.");
            }

            endReceived = true;

            try {
                Item<T> item = items[index++].end();
                while (!queue.offer(item, 1000, TimeUnit.SECONDS)) {
                    if (ended) {
                        throw new IllegalStateException("iteration has been ended.");
                    }
                }

                if (index == items.length) {
                    index = 0;
                }

                return true;

            } catch (InterruptedException e) {
                thrownException = e;
                return false;
            }
        }

        synchronized Exception thrownException() {
            return thrownException;
        }

        Item<T> pop() throws InterruptedException {
            Item<T> result = queue.take();
            if (result.ended) {
                this.ended = true;
            }

            return result;
        }
    }

    public static class YielderIterable<T> implements Iterable<T>, AutoCloseable {
        private final Context<T> context;
        private final Thread thread;

        private boolean iteratorCalled;
        private boolean closed;

        private YielderIterable(int queueSize, Yielder<T> yielder) {
            this.context = new Context<>(queueSize);
            this.thread = new Thread() {
                @Override
                public void run() {
                    try {
                        yielder.context.set(YielderIterable.this.context);
                        yielder.run();
                        context.yieldEnd();

                    } catch (Exception e) {
                        e.printStackTrace();

                    } finally {
                        yielder.context.remove();
                    }
                }
            };

            this.thread.start();
        }

        @Override
        public Iterator<T> iterator() {
            if (iteratorCalled) {
                throw new IllegalStateException("#iterator() has been called");
            }

            iteratorCalled = true;

            return new Iterator<T>() {
                Item<T> nextItem;

                @Override
                public boolean hasNext() {
                    if (nextItem != null) {
                        return !nextItem.ended;
                    }

                    try {
                        nextItem = context.pop();

                    } catch (InterruptedException e) {
                        throw new IllegalStateException(e);
                    }

                    return !nextItem.ended;
                }

                @Override
                public T next() {
                    if (!hasNext()) {
                        throw new NoSuchElementException();
                    }

                    Item<T> result = nextItem;
                    nextItem = null;

                    return result.object;
                }
            };
        }

        @Override
        public void close() throws Exception {
            if (closed) {
                return;
            }

            closed = true;
            context.endReceived = true;
            context.ended = true;

            thread.interrupt();
        }
    }

    private static final int DEFAULT_QUEUE_SIZE = 1;
    private ThreadLocal<Context<T>> context = new ThreadLocal<>();

    public static <T> Iterable<T> newIterable(final Yielder<T> yielder) {
        return new YielderIterable<>(DEFAULT_QUEUE_SIZE, yielder);
    }

    protected boolean yield(T returnValue) {
        return context.get().yield(returnValue);
    }

    protected Exception thrownException() {
        return context.get().thrownException();
    }

    public abstract void run();
}

class YielderDemo {
    public static void main(String[] args) {
        for (Integer integer : Yielder.newIterable(new Yielder<Integer>() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    yield(i);
                }

                for (int i = 30; i < 50; i++) {
                    yield(i);
                }
            }
        })) {
            System.out.println(integer);
        }
    }
}

あんまりテストできていないので正しく動かないこともあるかもしれないけど、そこは自己責任で!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした