Yield Return In Java

67

我使用泛型在Java中创建了一个链表,现在我想能够遍历列表中的所有元素。在C#中,当遍历包含在列表中的元素列表时,我会在链表内使用yield return

我该如何创建上述Java版本,以便可以遍历链表中包含的所有项目?

我希望能够编写类似以下的代码:

LinkedList<something> authors = new LinkedList<something>();
for (Iterator<something> i = authors.Values ; i.HasNext())
      doSomethingWith(i.Value);

我认为该属性/方法的值应该包含类似于以下代码:

LinkedListObject<something> current = first;
While (current != null){
 yield return current.getValue();
 current = current.getNext()
}

编辑: 注意我不想使用任何第三方API。仅使用内置的Java功能。


3
我不知道C#。好奇的是,yield return是什么? - bragboy
请查看此链接:http://msdn.microsoft.com/zh-cn/library/9k7k7cf0(VS.80).aspx - Asad
9
编译器过于以编译为中心吗?如果我想象一下,如果没有编译器为我编写程序,我将不得不自己编写所有编译器为我编写的东西... - flq
1
@MerlynMorgan-Graham 或者当计算(生成器函数)耗费大量时间且需要惰性求值时。 - rwong
@rwong:这只是我说的话的改述。如果列表已经完全在内存中构建,那么它就不是“惰性评估”。如果函数像我所说的“将它们推到容器中”,并且如OP在问题中描述的那样,结果是急切地计算的。 - Merlyn Morgan-Graham
显示剩余4条评论
10个回答

45

您可以返回一个匿名的Iterable实现。效果非常相似,只是这种方式更加冗长。

public Iterable<String> getStuff() {
    return new Iterable<String>() {

        @Override
        public Iterator<String> iterator() {
            return new Iterator<String>() {

                @Override
                public boolean hasNext() {
                    // TODO code to check next
                }

                @Override
                public String next() {
                    // TODO code to go to next
                }

                @Override
                public void remove() {
                    // TODO code to remove item or throw exception
                }

            };
        }
    };
}

37
"yield return" 是一种非常复杂的编译器技巧。它基本上让您以声明方式实现 IEnumerable,而无需 "找出" 如何构建迭代器的烦人细节。不幸的是,它在其他语言中并不容易被转换,因为很少有编译器具备这样的能力。在某些方面,"yield return" 就像是一个可怕的,并且带有革命性质的东西。
基本上,在 C# 中,编译器将生成两个 IEnumerable 和 IEnumerator (of T) 的实现。它通过将你的 "方法" 的局部变量实现为生成的实现类中的实例字段,以及检查包含 "yield return" 产物的帧来完成这个过程。一旦你知道了这一点,一个经验丰富的开发者就可以显式地完成同样的事情...尽管不像这么简洁。为了演示,我将 CONCAT!
public static <T> Iterable<T> concat(Iterable<T> x, Iterable<T> y)
{
    for(T e: x)
    {
        yield return e;
    }

    for(T e: y)
    {
        yield return e;
    }
}

// becomes ....

public static <E> Iterator<E> concat_(Iterable<E> x, Iterator<E> y)
{
    T e1, e2;
    Iterator<E> i1, i2;

    Iterator<E> s;
    Iterator<E> s4 = new Iterator<E>()
    {
        public bool hasNext()
        {
            return false;
        }

        public E next()
        {
            throw ... ;
        }

        public void remove()
        {
            throw ... ;
        }
    }

    Iterator<E> s3 = new Iterator<E>()
    {
        Iterator<E> act()
        {
            if(i2.hasNext())
            {
                return i2;
            }

            i2 = y.iterator();
            return (s = s4);
        }

        public bool hasNext()
        {
            return act().hasNext();
        }

        public E next()
        {
            return act().next();
        }

        public void remove()
        {
            return i2.remove();
        }
    }

    Iterator<E> s2 = new Iterator<E>()
    {
        Iterator<E> act()
        {
            if(i1.hasNext())
            {
                return i1;
            }

            i2 = y.iterator();
            return (s = s3);
        }

        public bool hasNext()
        {
            return act().hasNext();
        }

        public E next()
        {
            return act().next();
        }

        public void remove()
        {
            return i1.remove();
        }
    };

    Iterator<E> s1 = new Iterator<E>()
    {
        Iterator<E> act()
        {
            i1 = x.iterator();
            return s = s2;
        }

        public bool hasNext()
        {
            return act().hasNext();
        }

        public E next()
        {
            return act().next();
        }

        public void remove()
        {
            return act().remove();
        }
    };

    s = s1;
    return new Iterator<T>()
    {
        public bool hasNext()
        {
            return s.hasNext();
        }

        public E next()
        {
            return s.next();
        }

        public void remove()
        {
            return s.remove();
        }
    };
}

public static <T> Iterable<T> concat(Iterable<T> x, Iterable<T> y)
{
    return new Iterable<T>()
    {
        public Iterator<T> iterator()
        {
            return concat_(x, y)
        }
    };
}

// tada!

如果您能原谅我在凌晨3点写的伪Java代码......


14

答案中的第二个链接已经失效了,显示为“文件未找到(404错误)”。 - Pang

7

我不明白为什么人们在谈论线程...难道yield return中有我不知道的东西吗?

据我所知,yield return只是保存方法堆栈并在以后恢复它。要实现yield return,您只需手动保存状态。有关详细信息,请参阅Java迭代器类,但对于链表,您只需保存当前项即可。对于数组,您只需要索引。


2
这是正确的。在C#中,yield和yield return不使用线程。它们在编译时进行转换并创建状态机,但该状态机不使用任何额外的线程(尽管可能是线程安全的)。 - Sean Reilly

1

只是为了帮助读者理解细节。

如果您创建一个包含所有结果元素并返回列表的新列表,则这是一个很好的实现,足够简单易于编码。您可以拥有任何您需要的有趣数据结构,并在扫描它以获取正确条目时,只需返回所有匹配项的列表,您的客户端将对列表进行迭代。

如果您想保存状态,可能会更加复杂。每次调用函数时,您都需要回到之前的位置。更不用说可重入问题等。

使用线程的解决方案不会创建新列表。而且与第一个解决方案一样简单。唯一的问题是涉及线程同步,这可能更难编码,并且具有性能惩罚。

因此,是的,yield return很棒,但Java中缺少它。然而,有解决方法。


1

yield return操作可以被视为

  1. 在某处放置一些检查点
  2. 在某处写入一个值
  3. 当恢复时,跳转到其旁边的指令。

因此,我将其实现为类似状态机的协程。 在这个机制中,每个指令都有它的指令指针、索引和标签,因此我们可以使用jmp(label)跳转到标签。

  1. 添加一些机制以实现goto语法:addInstruction(..)和jmp()
  2. 并将状态/变量存储在某处:setVariable(name,value),yield(value)
  3. 一种暂时挂起/恢复的方法:exec()

例如:

    public class FibbonaciCoroutine implements Iterator<BigInteger> {
        BigInteger[] bucket = { new BigInteger("1"), new BigInteger("1"), new BigInteger("0") };
        int idx = 2;
        Coroutine coroutine = new Coroutine((pthis) -> {
    
            pthis.addInstruction("_label1", (me) -> {
                int p1 = idx - 2;
                int p2 = idx - 1;
                if (p1 < 0)
                    p1 += 3;
                if (p2 < 0)
                    p2 += 3;
                bucket[idx] = bucket[p1].add(bucket[p2]);
                idx = (idx + 1) % bucket.length;
    
                me.yield(bucket[idx]);
    
            });
            // goto
            pthis.addInstruction((me) -> {
                me.jmp("_label1");
            });
            pthis.start();
        });
    
        @Override
        public boolean hasNext() {
            return !coroutine.isStopped();
        }
    
        @Override
        public BigInteger next() {
            while (coroutine.exec())
                ;
            return coroutine.getYieldValue();
        }
    
        public static void main(String[] argv) {
            FibbonaciCoroutine cor = new FibbonaciCoroutine();
            for (int i = 0; i < 100 && cor.hasNext(); ++i) {
                System.out.printf("%d ", cor.next());
            }
        }
    
    }

请查看FibonacciCoroutine.java以了解更多信息。

回到您的问题...

    LinkedListObject<something> current = first;
    While (current != null){
       yield return current.getValue();
       current = current.getNext()
    }

能够转换为以下代码。
   //some where in class, or use Var<> to wrap it.
   Var<LinkedListObject<something> > current = new Var<>(first);
   Coroutine cor = new Coroutine();
   cor.While((ins)->current.get() != null).run((ins)->{
       ins.addInstruction((c)->c.yield(current.get().getValue()) );
       // wrap it with lambda for being a checkpoint
       ins.addInstruction( (c)->current.set(current.get().getNext()) );
   });

因此,我们可以使用getYieldValue()来检索结果,或者只需调用coroutine.iterator将协程转换为迭代器。


我还实现了一个递归示例BinaryTreeCoroutine - sunneo

0

这个问题已经发布很久了,我不确定是否应该回答这个老问题,但是我想提供另一种方法来实现它,并在这里介绍它,以防有人搜索到这个问题,考虑到这个SO线程是谷歌搜索的最早结果之一。

下面显示的代码已经在我的脑海中编译过了。绝对没有保证它是正确的,但它背后的思想是正确的。

使用回调函数

是的,我知道,这不同于yield return。但我认为OP并不是特别想要一个可以(通过适当的语法糖)放入for (var x : <some_iterator>)中的替代品。相反,我的方法更像C#的linq(或Java的stream()),而不是yield返回。

@FunctionalInterface
public interface Looper<T> {
    void each(T item);
}



public interface Loopable<T> {
    void forEach(Looper<? super T> looper);
}

然后您将在代码中实现Loopable<T>,创建这个伪迭代器。它实际上并不是迭代器,只是利用了@FunctionalInterface,这是Java进行回调的方式(有点像)

public class WhatEvs implements Loopable<WhatEvs> {
    // ...
    @Override
    public void forEach(Looper<? super T> looper) {
        while(your_condition) {
            WhatEvs nextItem = getNextItem();
            looper.each(nextItem);
        }
    }
}

0

我认为Java提供的最佳方式是Stream.generate()方法。您的情况将如下所示。

static <something> Stream<something> someMethod() {
        LinkedListObject<something> someObject = ...;
        var current = new AtomicReference<>(someObject);
        Supplier<something> getElement = () -> {
            if (current.get() != null) {
                var currentObject = current.get();
                /// ... some code ...
                current.set(current.get().getNext());
                return currentObject.getValue();
            }
            
            return null;
        };

        return Stream.generate(getElement).takeWhile(Objects::nonNull);
}

请注意,由于 lambda 内部的代码无法更改在该 lambda 外部声明的变量的值或引用,因此使用 AtomicReference 是必要的。
考虑到您希望在遇到 null 时停止产出,我们可以以更简单的方式编写。
static <something> Stream<something> someMethod() {
        LinkedListObject<something> someObject = ...;
        return Stream.iterate(someObject, something::getNext)
                     .takeWhile(Objects::nonNull)
                     .peek(current -> {/* some code */})
                     .map(something::getValue);
}

-3
如果您想要完整的 yield return 功能,可能需要在两个线程中设置--一个用于第一个方法,另一个用于第二个方法。然后第一个线程应该 wait 直到第二个线程将其值放在某个可访问的位置并 notify 它已准备好。然后第一个线程将处理该值,wait 等待下一个值,以此类推。

-4

1
该页面是德语的,而你的回答是英语的。请考虑添加一个代码示例来展示你的库是如何工作的。 - Camilo Sanchez

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接