Today, a colleague asked me how they can pass a java.util.Stream
to a function that accept an java.lang.Iterable
.
Let’s suppose we have a following function that accepts an Iterable
.
public static void doSth(Iterable<String> iterable){
iterable.forEach(System.out::println);
}
The calling function has a Stream that it want to pass to the doSth
function.
public static void main(String[] args) throws IOException {
Stream<String> lines = Files.lines(Paths.get("src", "main", "resources", "names.txt"));
}
One way we could easily achieve this is by collecting the Stream into a Collection like List. As List is an Iterable so we can pass it. This is should below
Stream<String> lines = Files.lines(Paths.get("src", "main", "resources", "names.txt"));
doSth(lines.collect(Collectors.toList()));
This works but what if Stream is big and collecting into an in-memory data structure like List leads to java.lang.OutOfMemoryError: Java heap space
.
I googled around and found a nice Lambda hack.
Stream<String> lines = Files.lines(Paths.get("src", "main", "resources", "names.txt"));
doSth(lines::iterator);
I have worked a lot on Java 8 but first time I looked at it I couldn’t figure out how it works. If you know Java 8, give it a shot.
The magic here is that Iterable interface has a single abstract method iterator
.
public interface Iterable<T> {
Iterator<T> iterator();
}
This means we can write it as following Lambda function.
Iterable<T> iterable = () -> iterator();
In our case, Stream has an iterator method. So, we can convert Stream to Iterable by defining a lambda function as shown below.
Stream<String> lines = Files.lines(Paths.get("src", "main", "resources", "names.txt"));
doSth(() -> lines.iterator());
You can refactor the Lambda to a method reference.
Stream<String> lines = Files.lines(Paths.get("src", "main", "resources", "names.txt"));
doSth(lines::iterator);
Can we use () -> iterator() in the for-each loop
I wondered if we can use the lambda expression in the for-each loop
for (String str : () -> lines.iterator()) {
System.out.println(str);
}
This looks like a valid use case. As for-each loop works with types that implement Iterable
interface. But, it turns out the code does not compile. It gives Lambda expression not implemented here
error.
The answer for this is mentioned in the JSR335
Deciding what contexts are allowed to support poly expressions
is driven in large part by the practical
need for such features:
The expression in an enhanced for loop is not in a
poly context because, as the construct is currently defined,
it is as if the expression were a receiver:
exp.iterator() (or, in the array case, exp[i]).
It is plausible that an Iterator could be wrapped
as an Iterable in a for loop via a
lambda expression (for (String s : () -> stringIterator)),
but this doesn't mesh very well with the semantics of Iterable.
Another Interesting thing to note is that Iterable is not marked as @FunctionalInterface
. I don’t know the exact reason why Iterable is not marked as @FunctionalInterface
. My guess is that because Iterable has special semantics in Java so they didn’t explicitly marked it @FunctionalInterface
.