Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Explain how this lambda can be assigned to an Iterable

I came across some clever code to convert an Iterator to a Stream from Karol on this post. I have to admit that I don't completely understand how the lambda is allowed to be assigned to the Iterable type in the following code...

static <T> Stream<T> iteratorToFiniteStream(final Iterator<T> iterator) {
    final Iterable<T> iterable = () -> iterator;
    return StreamSupport.stream(iterable.spliterator(), false);
}

I decided to write my own small test to ensure that it compiles and executes, and it does.

public void printsStream_givenIterator()
{
    Iterator<String> iterator = Arrays.asList("a", "b", "c").iterator();
    final Iterable<String> iterable = () -> iterator;
    StreamSupport.stream(iterable.spliterator(), false).forEach(s -> System.out.println(s));
}

// prints: abc

My understanding is that the lambda () -> iterator is acting as a Supplier function.

Iterable isn't a FunctionalInterface so how can it be assigned this lambda?

like image 874
Brad Avatar asked Jan 25 '17 00:01

Brad


People also ask

Can a lambda expression be assigned to an object variable?

Yes, any lambda expression is an object in Java. It is an instance of a functional interface. We have assigned a lambda expression to any variable and pass it like any other object.

What are the two conditions required for using a lambda function in a stream?

In order to match a lambda to a single method interface, also called a "functional interface", several conditions need to be met: The functional interface has to have exactly one unimplemented method, and that method (naturally) has to be abstract.

How do you convert this method into a lambda expression?

To convert an anonymous method to a lambda expressionMove to the anonymous method you want to convert. From the Refactor menu of the VisualAid choose To Lambda. Telerik® JustCode™ will replace the anonymous method with a lambda expression.


2 Answers

() -> iterator is not “acting as a Supplier function”. Lambda expressions can be assigned to any congruent functional interface type. And there is no requirement for a functional interface to be annotated with @FunctionalInterface. You can create an Iterable with a lambda expression, which will implement the Iterable.iterator() method, which is the only abstract method of that interface. However, implementing it by returning the same Iterator instance each time may violate the expectation of being able to iterate this object multiple times (which is the reason why Stream doesn’t implement Iterable despite having an iterator() method).

This solution will work in this narrow context, but isn’t clever.

The sequence

final Iterable<T> iterable = () -> iterator; … iterable.spliterator()

just introduces an additional step before Spliterators.spliteratorUnknownSize(iterator, 0), which is how the default method of Iterable.spliterator() is implemented.

So

static <T> Stream<T> iteratorToFiniteStream(final Iterator<T> iterator) {
    final Iterable<T> iterable = () -> iterator;
    return StreamSupport.stream(iterable.spliterator(), false);
}

is just a less efficient variant of

static <T> Stream<T> iteratorToFiniteStream(final Iterator<T> iterator) {
    return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);
}

If you compare this to the accepted answer of the Q&A you’ve linked, you’ll see that it is exactly that, but that answer takes the opportunity of passing specific characteristics instead of 0.

like image 150
Holger Avatar answered Sep 20 '22 10:09

Holger


As you've pointed out, assignment from a lambda expression is only valid if the target is a functional interface. This is described in section 15.27.3 of the JLS: Type of a Lambda Expression.

A lambda expression is compatible in an assignment context, invocation context, or casting context with a target type T if T is a functional interface type (§9.8) and the expression is congruent with the function type of the ground target type derived from T.

Jumping to section 9.8: Functional Interfaces, we can see the definition of a functional interface.

A functional interface is an interface that has just one abstract method (aside from the methods of Object), and thus represents a single function contract.

Iterable does satisfy the criteria for a functional interface, because it has only one abstract method: iterator(). (The 2 additional default methods do not violate the criteria, because they are not abstract.) Therefore, the assignment is valid.

like image 29
Chris Nauroth Avatar answered Sep 21 '22 10:09

Chris Nauroth