mmuzahid
mmuzahid

Reputation: 2270

Considering list elements that are added after filtered stream creation

Given the following code:

List<String> strList = new ArrayList<>(Arrays.asList("Java","Python","Php"));

Stream<String> jFilter = strList.stream().filter(str -> str.startsWith("J"));

strList.add("JavaScript"); // element added after filter creation
strList.add("JQuery"); // element added after filter creation

System.out.println(Arrays.toString(jFilter.toArray())); 

which outputs:

[Java, JavaScript, JQuery]

Why do JavaScript and JQuery appear in the filtered result even though they were added after creating the filtered stream?

Upvotes: 20

Views: 630

Answers (6)

fastcodejava
fastcodejava

Reputation: 41117

The toArray method is the terminal operation and it works on that full content of your list. To get predictable result do not save the stream to a temporary variable as it will lead to misleading results. A better code is:

String[] arr = strList.stream().filter(str -> str.startsWith("J")).toArray();

Upvotes: 0

Ousmane D.
Ousmane D.

Reputation: 56453

Short Answer

You're assuming after this point:

Stream<String> jFilter = strStream.filter(str -> str.startsWith("J"));

That a new stream of the elements starting with "J" are returned i.e. only Java. However this is not the case;

streams are lazy i.e. they don't perform any logic unless told otherwise by a terminal operation.

The actual execution of the stream pipeline starts on the toArray() call and since the list was modified before the terminal toArray() operation commenced the result will be [Java, JavaScript, JQuery].

Longer Answer

here's part of the documentation which mentions this:

For well-behaved stream sources, the source can be modified before the terminal operation commences and those modifications will be reflected in the covered elements. For example, consider the following code:

 List<String> l = new ArrayList(Arrays.asList("one", "two"));
 Stream<String> sl = l.stream();
 l.add("three");
 String s = sl.collect(joining(" "));  

First a list is created consisting of two strings: "one"; and "two". Then a stream is created from that list. Next the list is modified by adding a third string: "three". Finally the elements of the stream are collected and joined together. Since the list was modified before the terminal collect operation commenced the result will be a string of "one two three". All the streams returned from JDK collections, and most other JDK classes, are well-behaved in this manner;

Upvotes: 13

ernest_k
ernest_k

Reputation: 45329

Until the statement

System.out.println(Arrays.toString(jFilter.toArray()));

runs, the stream doesn't do anything. A terminal operation (toArray in the example) is required for the stream to be traversed and your intermediate operations (filter in this case) to be executed.

In this case, what you can do is, for example, capture the size of the list before adding other elements:

int maxSize = strList.size();
Stream<String> jFilter = strStream.limit(maxSize)
                                  .filter(str -> str.startsWith("J"));

where limit(maxSize) will not allow more than the initial elements to go through the pipeline.

Upvotes: 8

user1773603
user1773603

Reputation:

@Hadi J's comment but it should be answer according to the rules.

Because streams are lazy and when you call terminal operation it executed.

Upvotes: 1

Kishore Bandi
Kishore Bandi

Reputation: 5721

Its because the stream never got evaluated. you never called a "Terminal operation" on that stream for it to get executed as they're lazy.

Look at a modification of your code and the output. The filtering actually takes place when you call the Terminal Operator.

 public static void main(String []args){
         List<String> strList = new ArrayList<>();
    strList.add("Java");
    strList.add("Python");
    strList.add("Php");

    Stream<String> strStream = strList.stream();

    Stream<String> jFilter = strStream.filter(str -> {
        System.out.println("Filtering" + str);
        return str.startsWith("J");
        });

 System.out.println("After Stream creation");
    strList.add("JavaScript"); // element added after filter creation
    strList.add("JQuery"); // element added after filter creation

    System.out.println(Arrays.toString(jFilter.toArray()));

     }

Output:

After Stream creation
FilteringJava
FilteringPython
FilteringPhp
FilteringJavaScript
FilteringJQuery
[Java, JavaScript, JQuery]

Upvotes: 3

GreyBeardedGeek
GreyBeardedGeek

Reputation: 30088

As explained in the official documentation at ,https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html, streams have no storage, and so are more like iterators than collections, and are evaluated lazily.

So, nothing really happens with respect to the stream until you invoke the terminal operation toArray()

Upvotes: 1

Related Questions