Benji's Blog

Yield Return in Java

A feature often missed in Java by c# developers is yield return

It can be used to create Iterators/Generators easily. For example, we can print the infinite series of positive numbers like so:

public static void Main()
{
    foreach (int i in positiveIntegers()) 
    {
        Console.WriteLine(i);    
    }
}
    
public static IEnumerable positiveIntegers()
{
    int i = 0;
    while (true) yield return ++i;
}

Annoyingly, I don’t think there’s a good way to implement this in Java, because it relies on compiler transformations.

If we want to use it in Java there are three main approaches I am aware of, which have various drawbacks.

The compile-time approach means your code can’t be compiled with javac alone, which is a significant disadvantage.

The bytecode transformation approach means magic going on that you can’t easily understand by following the code. I’ve been burnt by obscure problems with aspect-oriented-programming frameworks using bytecode manipulation enough times to avoid it.

The threads approach has a runtime performance cost of extra threads. We also need to dispose of the created threads or we will leak memory.

I don’t personally want the feature enough to put up with the drawbacks of any of these approaches.

That being said, if you were willing to put up with one of these approaches, can we make them look cleaner in our code.

I’m going to ignore the lombok/compile-time transformation approach as it allows pretty much anything.

Both the other approaches above require writing valid Java. The threads approach is particularly verbose, but there is a wrapper which simplify it down to returning an anonymous implementation of an abstract class that provides yield / yieldBreak methods. e.g.

public Iterable oneToFive() {
    return new Yielder() {
        protected void yieldNextCore() {
            for (int i = 1; i < 10; i++) {
                if (i == 6) yieldBreak();
                yieldReturn(i);
            }
        }
    };	
}

This is quite ugly compared to the c# equivalent. We can make it cleaner now we have lambdas, but we can't use the same approach as above.

I'm going to use the threading approach for this example as it's easier to see what's going on.

Let's say we have an interface Foo which extends Runnable, and provides an additional default method.

interface Foo extends Runnable {
    default void bar() {}
}

If we create an instance of this as an anonymous inner class we can call the bar() method from our implementation of run();

Foo foo = new Foo() {
    public void run() {
        bar();
    }
};

However, if we create our implementation with a lambda this no longer compiles

Foo foo = () -> {
    bar(); // java: cannot find symbol. symbol: method bar()
};

This means we'll have to take a different approach. Here's something we can do, that is significantly cleaner thanks to lambdas.

public Yielderable oneToFive() {
    return yield -> {
        for (int i = 1; i < 10; i++) {
            if (i == 6) yield.breaking();
            yield.returning(i);
        }
    };
}

How can this work? Note the change of the method return type from Iterable to a new interface Yielderable. This means we can return any structurally equivalent lambda. Since we want to create an Iterable we'll need to extend Iterable to have it behave as an Iterable.

public interface Yielderable extends Iterable { /* ... */ }

The Iterable interface already has an abstract method iterator(), which means we'll need to implement that if we want to add a new method to build our yield statement, while still remaining a lambda-compatible single method interface.

We can add a default implementation of iterator() that executes a yield definition defined by a new abstract method.

public interface Yielderable extends Iterable {
    void execute(YieldDefinition builder);

    default Iterator iterator() {  /* ... */  }
}

We now have a single abstract method, still compatible with a lambda. It accepts one parameter - a YieldDefinition. This means we can call methods defined on YieldDefinition in our lambda implementation. The default iterator() method can create an instance of a YieldDefinition and invoke the execute method for us.

public interface Yielderable extends Iterable {
    void execute(YieldDefinition builder);

    default Iterator iterator() {
	    YieldDefinition definition = new YieldDefinition<>()
	    execute(definition);
	    //... more implementation needed.
    }
}

Our YieldDefinition class provides the returning(value) and breaking() methods to use in our yield definition.

class YieldDefinition {
    public void returning(T value) { /* ... */ }

    public void breaking() { /* ... */ }
}

Now Java can infer the type of the lambda parameter, allowing us to call them in the lambda body. You should even get code completion in your IDE of choice.

I have made a full implementation of the threaded approach using with above lambda definition style if you are interested.

There's some more examples of what is possible and the full implementation for reference.