我知道 Java 本身没有直接的等价物,但也许是第三方?
真的很方便。目前我想实现一个迭代器,它产生树中的所有节点,大约有五行代码。
我知道的两个选项是2007 年的 Aviad Ben Dov 的 infomancers-collections 库和2008 年的 Jim Blackler 的 YieldAdapter 库(在另一个答案中也提到了)。
两者都将允许您yield return
在 Java 中使用 -like 构造编写代码,因此两者都将满足您的要求。两者之间的显着差异是:
Aviad 的库使用字节码操作,而 Jim 的库使用多线程。根据您的需要,每个可能都有自己的优点和缺点。很可能 Aviad 的解决方案更快,而 Jim 的解决方案更便携(例如,我认为 Aviad 的库不能在 Android 上运行)。
Aviad 的库具有更简洁的界面 - 这是一个示例:
Iterable<Integer> it = new Yielder<Integer>() {
@Override protected void yieldNextCore() {
for (int i = 0; i < 10; i++) {
yieldReturn(i);
if (i == 5) yieldBreak();
}
}
};
虽然吉姆的方法要复杂得多,但要求您使用具有方法adept
的泛型……呃。但是,您可以在 Zoom Information 的 Jim 代码周围使用类似这样的包装器,这大大简化了这一点:Collector
collect(ResultHandler)
Iterable<Integer> it = new Generator<Integer>() {
@Override protected void run() {
for (int i = 0; i < 10; i++) {
yield(i);
if (i == 5) return;
}
}
};
Aviad 的解决方案是 BSD。
Jim 的解决方案是公共领域,上面提到的它的包装器也是如此。
现在 Java 有了 Lambda,这两种方法都可以变得更加简洁。你可以做类似的事情
public Yielderable<Integer> oneToFive() {
return yield -> {
for (int i = 1; i < 10; i++) {
if (i == 6) yield.breaking();
yield.returning(i);
}
};
}
我知道这是一个非常古老的问题,上面描述了两种方法:
yield
,显然有资源成本。然而,还有另一种,第三种,可能是最自然的yield
Java 中实现生成器的方法,它是最接近 C# 2.0+ 编译器为yield return/break
生成所做的实现:lombok-pg。它完全基于状态机,需要密切配合javac
才能操作源代码 AST。不幸的是,对 lombok-pg 的支持似乎已经停止(超过一两年没有存储库活动),而原始的Lombok 项目不幸地缺少该yield
功能(尽管它具有更好的 IDE,如 Eclipse、IntelliJ IDEA 支持)。
Stream.iterate(seed, seedOperator).limit(n).foreach(action)与 yield 运算符不同,但以这种方式编写自己的生成器可能会很有用:
import java.util.stream.Stream;
public class Test01 {
private static void myFoo(int someVar){
//do some work
System.out.println(someVar);
}
private static void myFoo2(){
//do some work
System.out.println("some work");
}
public static void main(String[] args) {
Stream.iterate(1, x -> x + 1).limit(15).forEach(Test01::myFoo); //var1
Stream.iterate(1, x -> x + 1).limit(10).forEach(item -> myFoo2()); //var2
}
}
如果您已经在项目中使用RXJava ,我还建议您使用 Observable 作为“yielder”。如果您制作自己的 Observable,它可以以类似的方式使用。
public class Example extends Observable<String> {
public static void main(String[] args) {
new Example().blockingSubscribe(System.out::println); // "a", "b", "c", "d"
}
@Override
protected void subscribeActual(Observer<? super String> observer) {
observer.onNext("a"); // yield
observer.onNext("b"); // yield
observer.onNext("c"); // yield
observer.onNext("d"); // yield
observer.onComplete(); // finish
}
}
Observables 可以转换为迭代器,因此您甚至可以在更传统的 for 循环中使用它们。RXJava 还为您提供了非常强大的工具,但如果您只需要一些简单的东西,那么这可能是一种矫枉过正。
我刚刚在这里发布了另一个(MIT 许可)解决方案,它在一个单独的线程中启动生产者,并在生产者和消费者之间建立了一个有界队列,允许在生产者和消费者之间进行缓冲、流控制和并行流水线处理(所以消费者可以在生产者正在生产下一个项目时消费前一个项目)。
您可以使用这种匿名内部类形式:
Iterable<T> iterable = new Producer<T>(queueSize) {
@Override
public void producer() {
produce(someT);
}
};
例如:
for (Integer item : new Producer<Integer>(/* queueSize = */ 5) {
@Override
public void producer() {
for (int i = 0; i < 20; i++) {
System.out.println("Producing " + i);
produce(i);
}
System.out.println("Producer exiting");
}
}) {
System.out.println(" Consuming " + item);
Thread.sleep(200);
}
或者您可以使用 lambda 表示法来减少样板:
for (Integer item : new Producer<Integer>(/* queueSize = */ 5, producer -> {
for (int i = 0; i < 20; i++) {
System.out.println("Producing " + i);
producer.produce(i);
}
System.out.println("Producer exiting");
})) {
System.out.println(" Consuming " + item);
Thread.sleep(200);
}