On Sun, 7 Nov 2021 07:51:06 GMT, Michael Bien <d...@openjdk.java.net> wrote:

>>> wouldn't this make streams no longer lazy if the collection is empty?
>>> 
>>> ```java
>>>         List<String> list = new ArrayList<>();
>>>         Stream<String> stream = list.stream();
>>> 
>>>         list.addAll(List.of("one", "two", "three"));
>>> 
>>>         stream.forEach(System.out::println); // prints one two three
>>> ```
>> 
>> I did not consider this case, thank you for bringing it up. I have always 
>> found this behaviour a bit strange and have never used it "in the real 
>> world". It is also not consistent between collections. Here is an example 
>> with four collections: ArrayList, CopyOnWriteArrayList, 
>> ConcurrentSkipListSet and ArrayBlockingQueue:
>> 
>> 
>> import java.util.ArrayList;
>> import java.util.Arrays;
>> import java.util.Collection;
>> import java.util.List;
>> import java.util.Objects;
>> import java.util.concurrent.ArrayBlockingQueue;
>> import java.util.concurrent.ConcurrentSkipListSet;
>> import java.util.concurrent.CopyOnWriteArrayList;
>> import java.util.function.Supplier;
>> import java.util.stream.IntStream;
>> 
>> public class LazyStreamDemo {
>>     public static void main(String... args) {
>>         List<Supplier<Collection<String>>> suppliers =
>>                 List.of(ArrayList::new, // fast-fail
>>                         CopyOnWriteArrayList::new, // snapshot
>>                         ConcurrentSkipListSet::new, // weakly-consistent
>>                         () -> new ArrayBlockingQueue<>(10) // 
>> weakly-consistent
>>                 );
>>         for (Supplier<Collection<String>> supplier : suppliers) {
>>             Collection<String> c = supplier.get();
>>             System.out.println(c.getClass());
>>             IntStream stream = c.stream()
>>                     .sorted()
>>                     .filter(Objects::nonNull)
>>                     .mapToInt(String::length)
>>                     .sorted();
>> 
>>             c.addAll(List.of("one", "two", "three", "four", "five"));
>>             System.out.println("stream = " + 
>> Arrays.toString(stream.toArray()));
>>         }
>>     }
>> }
>> 
>> 
>> The output is:
>> 
>> 
>> class java.util.ArrayList
>> stream = [3, 3, 4, 4, 5]
>> class java.util.concurrent.CopyOnWriteArrayList
>> stream = []
>> class java.util.concurrent.ConcurrentSkipListSet
>> stream = []
>> class java.util.concurrent.ArrayBlockingQueue
>> stream = [3, 3, 4, 4, 5]
>> 
>> 
>> At least with the EmptyStream we would have consistent output of always []
>
> @kabutz I agree that i wouldn't consider it clean code to use a stream like i 
> put into the example. I only brought it up because it might break existing 
> code, since i think streams are expected to be lazy. Interesting to see that 
> they are in fact not lazy in all situations - i put that into my notes.

Edit: actually I think the implementation of `Collection::stream` could simply 
be changed to:


default Stream<E> stream() {
    var spliterator = spliterator();
    if(spliterator.hasCharacteristics(Spliterator.IMMUTABLE | 
Spliterator.CONCURRENT) && isEmpty()) {
        return Stream.empty();
    }
    return StreamSupport.stream(spliterator, false);
}


Note that the spliterators of methods such as `Collections::emptyList`, 
`List::of`, `List::copyOf`, `Set::of`, ... currently don't have the `IMMUTABLE` 
characteristic though, so they should be updated.

---

The Javadoc for 
[java.util.stream](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/stream/package-summary.html)
 is clear though:

> Traversal of the pipeline source does not begin until the terminal operation 
> of the pipeline is executed.

and further on says:

> Spliterators for mutable data sources have an additional challenge; timing of 
> binding to the data, since the data could change between the time the 
> spliterator is created and the time the stream pipeline is executed. Ideally, 
> a spliterator for a stream would report a characteristic of IMMUTABLE or 
> CONCURRENT; if not it should be late-binding.

which explains why the collections in `java.util.concurrent` (whose 
spliterators have one of those characteristics) need not be late-binding.

-------------

PR: https://git.openjdk.java.net/jdk/pull/6275

Reply via email to