1

I have a question about scope, streams, and reassignment.

I'm wondering if there's a way to do this using the streaming api without reassigning the list. the problem with reassigning the parameter is it breaks the scope, and so the test doesn't see the new value.

I know there are other ways (iterator, backwards iteration, etc.) to solve the problem. This question is just out of curiosity.

Test code:

@Test
void testAllNegativesByIterator() {
    List<Integer> values = new ArrayList<>(Arrays.asList(-1, -2, -3, -4, -5, -6, -7, -8, -9));
    p.removeNegativesByIterator(values);
    checkNonNegativeSize(0, values);
}

Code under test:

public void removeNegativesByStream(List<Integer> values) {
    values = values.stream().filter(v -> v > 0).collect(Collectors.toList());
}

Edit:

I also understand I could return a value. As I said in the question, I'm asking specifically a way to do this using streams out of curiosity.

Lino
  • 19,604
  • 6
  • 47
  • 65
xandermonkey
  • 4,054
  • 2
  • 31
  • 53

3 Answers3

6

Firstly, reassigning the result of the stream query back to values will not persist after the method call as java doesn't support pass by reference.

As for the question:

you can use the Collection::removeIf method which removes all of the elements of the source collection that satisfy the given predicate:

values.removeIf(v -> v <= 0);

otherwise, you have no choice but to create a new list because a stream does not modify the source.

Ousmane D.
  • 54,915
  • 8
  • 91
  • 126
  • Thanks. So, no way to do this using streams? Only option is the `removeIf`? – xandermonkey Jul 26 '18 at 19:29
  • 1
    @xandermonkey a stream does not modify the source. so the answer is yes "there's no way to do this using streams". – Ousmane D. Jul 26 '18 at 19:30
  • @xandermonkey what exactly do you hope to get from “do this using streams”? Isn’t `values.removeIf(v -> v < 0);` simple enough? – Holger Jul 27 '18 at 15:41
  • 1
    Yup, it sure is. That's why I accepted the answer. What I was looking for was to satisfy my curiosity as I posted originally – xandermonkey Jul 27 '18 at 15:43
0

You want the streams API to mutate the underlying collection which runs counter to the functional nature of Streams. Pure functions do not have side effects. You can mutate the list within the method and thus avoid reassignment, but not using streams.

  public void removeNegativesByStream(List<Integer> values) {
    List<Integer> original = new ArrayList<>(values);
    values.clear();
    values.addAll(original.stream().filter(v -> v > 0).collect(Collectors.toList()));
  }
neildo
  • 2,206
  • 15
  • 12
  • +1 for solving the problem using streams as well as the explanation of why it goes against their nature. – xandermonkey Jul 26 '18 at 19:50
  • 4
    This is not going to work because at the time of calling `values.stream()` `values` is already empty because of `values.clear()` before. – Tomasz Linkowski Jul 27 '18 at 06:50
  • While what you say is correct, you proposed solution does not work. You'd have to copy the List before clearing it and stream the copy - that would technically avoid reassigning. – Hulk Jul 27 '18 at 07:50
  • Yep, thanks. I definitely had a bug which I've fixed. – neildo Jul 30 '18 at 21:35
0

First of all, what you are doing is wrong besides the use of the Streams API or not.

In java, you can not reassign a new reference to method parameters as java does not support pass by reference.

Your code snippet without the introduction of the stream pipeline is exactly equivalent to for example the following :

public void removeNegativesByStream(List<Integer> values) {
    values = null;
}

The reassignment of values to a null is only visible inside the scope of the method, it will never leak out and that holds true either using the Streams API or not.

When you are confronted to mutating a source collection using the new Streams API, you simply can not, as a stream pipeline is simply a run-once pass over a data source (in your example values).

What you can do is collect the result of the pipeline into a new List<Integer> containing only positive numbers, but that would not affect your values variable and the next test (checkNonNegativeSize(0, values);) will simply fail.

Now, you are left with the other solutions, amongst them, removing manually the negative numbers through the list's iterator or use a declarative wrapper Collection#removeIf describing what elements to remove from the collection.

The most efficient and simple to read solution that comes to my mind is Aomine's solution.

marsouf
  • 1,107
  • 8
  • 15