I'm looking for the most pain free way to filter a collection. I'm thinking something like
Collection<?> foo = existingCollection.stream().filter( ... ). ...
But I'm not sure how is best to go from the filter, to returning or populating another collection. Most examples seem to be like "and here you can print". Possible there's a constructor, or output method that I'm missing.
There’s a reason why most examples avoid storing the result into a Collection. It’s not the recommended way of programming. You already have a Collection, the one providing the source data and collections are of no use on its own. You want to perform certain operations on it so the ideal case is to perform the operation using the stream and skip storing the data in an intermediate Collection. This is what most examples try to suggest.
Of course, there are a lot of existing APIs working with Collections and there always will be. So the Stream API offers different ways to handle the demand for a Collection.
Get an unmodifiable List implementation containing all elements (JDK 16):
List<T> results = l.stream().filter(…).toList();
Get an arbitrary List implementation holding the result:
List<T> results = l.stream().filter(…).collect(Collectors.toList());
Get an unmodifiable List forbidding null like List.of(…) (JDK 10):
List<T> results = l.stream().filter(…).collect(Collectors.toUnmodifiableList());
Get an arbitrary Set implementation holding the result:
Set<T> results = l.stream().filter(…).collect(Collectors.toSet());
Get a specific Collection:
ArrayList<T> results =
l.stream().filter(…).collect(Collectors.toCollection(ArrayList::new));
Add to an existing Collection:
l.stream().filter(…).forEach(existing::add);
Create an array:
String[] array=l.stream().filter(…).toArray(String[]::new);
Use the array to create a list with a specific specific behavior (mutable, fixed size):
List<String> al=Arrays.asList(l.stream().filter(…).toArray(String[]::new));
Allow a parallel capable stream to add to temporary local lists and join them afterward:
List<T> results
= l.stream().filter(…).collect(ArrayList::new, List::add, List::addAll);
(Note: this is closely related to how Collectors.toList() is currently implemented, but that’s an implementation detail, i.e. there is no guarantee that future implementations of the toList() collectors will still return an ArrayList)
An example from java.util.stream's documentation:
List<String>results =
stream.filter(s -> pattern.matcher(s).matches())
.collect(Collectors.toList());
Collectors has a toCollection() method, I'd suggest looking this way.
As an example that is more in line with Java 8 style of functional programming:
Collection<String> a = Collections.emptyList();
List<String> result = a.stream().
filter(s -> s.length() > 0).
collect(Collectors.toList());
You would possibly want to use toList or toSet or toMap methods from Collectors class.
However to get more control the toCollection method can be used. Here is a simple example:
Collection<String> c1 = new ArrayList<>();
c1.add("aa");
c1.add("ab");
c1.add("ca");
Collection<String> c2 = c1.stream().filter(s -> s.startsWith("a")).collect(Collectors.toCollection(ArrayList::new));
Collection<String> c3 = c1.stream().filter(s -> s.startsWith("a")).collect(Collectors.toList());
c2.forEach(System.out::println); // prints-> aa ab
c3.forEach(System.out::println); // prints-> aa ab
Related
I am trying to convert below for-loop to forEach method with help Stream function using Java1.8, but I was messed up and also confused to do that.
List<A> valueList = new ArrayList<>();
List<B> responseList = getResponses();
List<A> value = new ArrayList<>();
for (B getResponse: responseList) {
valueList = getValues(getResponse);
value.addAll(valueList);
}
With streams you generally want to avoid creating empty lists and then adding items. Streams should use functional idioms and avoid side effects as much as possible. It's better to work with the stream as a whole and then "collect" the results into a list at the end.
List<C> value = getResponses().stream()
.flatMap(r -> getValues(r).stream())
.collect(Collectors.toList());
I am trying to convert below for-loop to forEach method with help
Stream function using Java 1.8.
You shouldn't use a stream along with forEach simply to accumulate into a predefined list as there will be side effects (which should be avoided when dealing with streams), rather go with the stream approach suggested by John Kugelman if you want to perform it with streams or using the forEach method it can also be done as:
List<A> value = new ArrayList<>();
responseList.forEach(response -> value.addAll(getValues(response))));
It appears you are trying to add all the values in responseList to valueList and I think you could just do valueList.addAll(responseList); and not have to use a for loop at all.
You could have a problem though if type B doesn't inherit from A because you can't have a list of two unrelated types.
Just iterating below list & adding into another shared mutable list via java 8 streams.
List<String> list1 = Arrays.asList("A1","A2","A3","A4","A5","A6","A7","A8","B1","B2","B3");
List<String> list2 = new ArrayList<>();
Consumer<String> c = t -> list2.add(t.startsWith("A") ? t : "EMPTY");
list1.stream().forEach(c);
list1.parallelStream().forEach(c);
list1.forEach(c);
What is the difference between above three iteration & which one we need to use. Are there any considerations?
Regardless of whether you use parallel or sequential Stream, you shouldn't use forEach when your goal is to generate a List. Use map with collect:
List<String> list2 =
list2.stream()
.map(item -> item.startsWith("A") ? item : "EMPTY")
.collect(Collectors.toList());
Functionally speaking,for the simple cases they are almost the same, but generally speaking, there are some hidden differences:
Lets start by quoting from Javadoc of forEach for iterable use-cases stating that:
performs the given action for each element of the Iterable until all
elements have been processed or the action throws an exception.
and also we can iterate over a collection and perform a given action on each element – by just passing a class that implements the Consumer interface
void forEach(Consumer<? super T> action)
https://docs.oracle.com/javase/8/docs/api/java/lang/Iterable.html#forEach-java.util.function.Consumer-
The order of Stream.forEach is random while Iterable.forEach is always executed in the iteration order of the Iterable.
If Iterable.forEach is iterating over a synchronized collection, Iterable.forEach takes the collection's lock once and holds it across all the calls to the action method. The Stream.forEach call uses the collection's spliterator, which does not lock
The action specified in Stream.forEach is required to be non-interfering while Iterable.forEach is allowed to set values in the underlying ArrayList without problems.
In Java, Iterators returned by Collection classes, e.g. ArrayList, HashSet, Vector, etc., are fail fast. This means that if you try to add() or remove() from the underlying data structure while iterating it, you get a ConcurrentModificationException.
https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html#fail-fast
More Info:
What is the difference between .foreach and .stream().foreach?
What is difference between Collection.stream().forEach() and Collection.forEach()?
When working with streams, you should write your code in a way that if you switch to parallel streams, it does not produce the wrong results.
Imagine if in your code you were doing reading and writing on the same shared memory (list2) and you distribute your process into several threads (using parallel streams). Then you are DOOMED. Therefore you have several options.
make your shared memory (list2) thread-safe. for example by using AtomicReferences
List<String> list2 = new ArrayList<>();
AtomicReference<List<String>> listSafe = new AtomicReference<>();
listSafe.getAndUpdate(strings -> {strings.add("newvalue"); return strings;});
or you can go with the purely functional approach (code with no side effects)
like the #Eran solution.
Please consider the below code snippet.
List<String> list = new ArrayList<String>();
list.add("A");
list.add("B");
list.add("C");
List<String> copyList = new ArrayList<String>();
Consumer<String> consumer = s->copyList.add(s);
list.stream().forEach(consumer);
Since we are using lambda expression, as per functional programming (pure functions) it should only compute the input & provide corresponding output.
But here in the example it is trying to add elements to the list which is neither input nor declared inside the lambda scope.
Is this a good practice, I mean, leading to any side effects?
forEach would be useless if it didn't produce side-effects, since it has no return value. Hence, whenever you use forEach you should be expecting side-effects to take place. Therefore there's nothing wrong with your example.
A Consumer<String> can print the String, or insert it into some database, or write it into some output file, or store it in some Collection (as in your example), etc...
From the Stream Javadoc:
A stream pipeline consists of a source (which might be an array, a collection, a generator function, an I/O channel, etc), zero or more intermediate operations (which transform a stream into another stream, such as Stream.filter(Predicate)), and a terminal operation (which produces a result or side-effect, such as Stream.count() or Stream.forEach(Consumer)).
Besides, if you look at the Javadoc of Consumer, you'll see that it's expected to have side-effects:
java.util.function.Consumer
Represents an operation that accepts a single input argument and returns no result. Unlike most other functional interfaces, Consumer is expected to operate via side-effects.
I guess this means Java Streams and functional interfaces were not designed to be used only for "purely" functional programming.
For a forEach or even a stream().forEach it is pretty straightforward. Your example works fine.
Be aware though that if you would do this with other streaming methods, then you could get some surprises: e.g. The following code prints absolutely nothing.
List<String> lst = Arrays.asList("a", "b", "c");
lst.stream().map(
s -> {
System.out.println(s);
return "-";
});
In this case, the stream acts more like a builder which prepares a process but does not execute it yet. It's only when a collect, count or find... method is called that the lambda is executed.
An easy way to spot this, is by looking at the return type of the map method, which in turn is again a Stream.
Having said that, I think for your specific example, there are easier alternatives.
List<String> list = Arrays.asList("A", "B", "C");
// this is the base pattern for transforming one list to another.
// the map applies a transformation.
List<String> copyList1 = list.stream().map(e -> e).collect(Collectors.toList());
// if it's a 1-to-1 mapping, then you don't really need the map.
List<String> copyList2 = list.stream().collect(Collectors.toList());
// in essence, you could of course just clone the list without streaming.
List<String> copyList3 = new ArrayList<>(list);
Is there a short and elegant way of reversing mapping function?
The problem goes like this: I have a bunch of objects of type A, which I need to map to type B, perform filtering on the B objects, and then return back to corresponding A objects.
To provide some specifics and an example:
I have a bulk filtering function
Collection<CarVO> myFilter(Collection<CarVO> originalCollection);
and a Collection<Car> cars, and finally a mapping method Car#toVO().
I came up with this code:
Collection<Car> filtered = cars.stream()
.filter(car -> !myFilter(Collections.singleton(car)).isEmpty())
.collect(toList());
but I suspect this is not efficient because I filter objects one by one.
Other way to do the same thing:
Map<Car, CarVO> map = new HashMap<>();
for (Car car : cars) map.put(car, car.toVO());
Collection<CarVO> filteredVOs = myFilter(map.values());
cars = cars.stream.filter(car -> filteredVOs.contains(map.get(car))).collect(toList());
But I don't fancy building map explicitly.
UPD: Thank you all,
Didn't know that Map.values() returned a live view. I'll stick to using retainAll() then.
Since your method myFilter(Collection<CarVO>) must make a decision about whether each element should be removed or not, and you assume this to work even for a single-element collection, it must be possible to extract the logic into a single-item Predicate. Using that extracted predicate would be the most efficient solution.
But if you can’t do that and are for whatever reason bound to using this existing method, you may use it as follows:
Map<Car, CarVO> map = new HashMap<>();
for(Car car: cars) map.put(car, car.toVO());
map.values().retainAll(myFilter(map.values()));
Set<Car> filtered=map.keySet();
If you really need a list, you may replace the last line with
List<Car> filtered=new ArrayList<>(map.keySet());
Note that this assumes that myFilter returns a new collection. If it applies the filter directly to its collection parameter by removing elements from it, you may replace map.values().retainAll(myFilter(map.values())); with myFilter(map.values());
Use a Predicate for a single Car?
In the Predicate you could transform a car in a CarVO (if necessary) and then apply the logic for the single element.
And for the the sake of the DRY principle you should use this predicate inside the original myFilter method.
I currently get returned an ImmutableSet from a function call (getFeatures()) and due to the structure of the rest of my code to be executed later on- it would be much easier to change this to a List. I have tried to cast it which produces a runtime exception. I have also looked around for a function call to convert it to a list to no avail. Is there a way to do this? My most recent [failed] attempt is shown below:
ImmutableSet<FeatureWrapper> wrappersSet = getFeatures();
List<FeatureWrapper> wrappers = (List<FeatureWrapper>) wrappersSet;
I have found wrapperSet.asList() which will give me an ImmutableList however i would much rather prefer a mutable list
You can't cast a Set<T> into a List<T>. They are entirely-different objects. Just use this copy constructor which creates a new list out of a collection:
List<FeatureWrapper> wrappers = new ArrayList<>(wrappersSet);
ImmutableCollection has the "asList" function...
ImmutableList<FeatureWrapper> wrappersSet = getFeatures().asList();
Bonus points that the returned type an ImmutableList.
If you really wanted a mutable List though, then Vivin's answer is what you want.
Since Guava-21 supports java-8 you can use stream and collector to convert an ImmutableSet to a List:
ImmutableSet<Integer> intSet = ImmutableSet.of(1,2,3,4,5);
// using java-8 Collectors.toList()
List<Integer> integerList = intSet.stream().collect(Collectors.toList());
System.out.println(integerList); // [1,2,3,4,5]
integerList.removeIf(x -> x % 2 == 0);
System.out.println(integerList); // [1,3,5] It is a list, we can add
// and remove elements
We can use ImmutableList#toImmutableList with collectors to convert an ImmutableList to a ImmutableList :
// using ImmutableList#toImmutableList()
ImmutableList<Integer> ints = intSet.stream().collect(
ImmutableList.toImmutableList()
);
System.out.println(ints); // [1,2,3,4,5]
And the easiest way is to call ImmutableSet#asList
// using ImmutableSet#asList
ImmutableList<Integer> ints = intSet.asList();