JAVA-STREAM : re-use double a DoubleStream - java

I need a trick to solve this problem i'm using Java 1.8 And I retrieved a an object from a method that returns a DoubleStream object. So, the problem is I could not reuse the stream after it has been consumed.
Here is the first version of the code :
DoubleStream stream = object.getvalueStream(a,b);
if(condtion)
stream.map(v -> v * 2);
stream.forEach(value -> {
// do something
}
The problem is that once the condition is true, the stream is consumed And I can not reuse it. So I tried to use a supplier to return a doubleStream from supplier and iterate overt it.
But still the same problem as I try to recover the stream from my stream object which is already used.
Here is my updated code :
DoubleStream stream = object.getvalueStream(a,b);
if(condtion)
stream.map(v -> v * 2);
Supplier>DoubleStream> streamSupplier = () -> DoubleStream.of(stream.toArray());
streamSupplier.get().forEach(value -> {
//Do something
But I still have the same problem since I create my supplier from my stream already used if the condition is true.
Thanks for your help.

once the condition is true, the stream is consumed And I can not reuse it
Intermediate operations (e.g. map) return a new stream, so you need to reassign the stream after the intermediate operation (map).
I.e.
DoubleStream stream = object.getvalueStream(a,b);
if (condition) {
stream = stream.map(v -> v * 2);
}
stream.forEach(value -> {
// do something
}
Note terminal operations (e.g. foreach) do not return a stream. So if you want many terminal operations, you should collect the stream so it can be reused.
Note also, there is also an intermediate version of foreach called peek if you wish to chain foreach (peek) calls.

Streams in Java are not up to be reused. You should collect the result and stream twice like
List<Double> doubles = object.getvalueStream(a,b).boxed().collect(toList());
if(condition) {
doubles = doubles.stream().map(v -> v * 2).boxed().collect(toList);
}
// and further processing here
doubles.stream().forEach(v ->
...
);

You can use map() if it is important for you to keep the stream without collecting it. The only drawback in this approach is that you have to check the condition each time
DoubleStream stream = object.getvalueStream(a,b).map(v-> condition ? v*2 : v).forEach(...);
Or just assign the right Stream to the variable
DoubleStream stream = condition ? object.getvalueStream(a,b).map(v->v*2) : object.getvalueStream(a,b).map(v->v*2).forEach(...);

Related

Java Streams - Are you able to have multiple terminal operations (e.g. forEach)

I'm fairly new to Java and trying to learn how to use streams for easier code writing. If I can code like this:
Map<String, SomeConfig> temp = new HashMap<>();
resultStorage.forEach((key, value) -> key.getUsers().forEach(user -> {
if (!temp.containsKey(user.getMeta())) {
SomeConfig emailConfiguration = key
.withCheck1(masterAccountId)
.withCheck2(getClientTimezone())
.withCheck3(user.getMeta());
temp.put(user.getMeta(), emailConfiguration);
}
temp.get(user. getMeta()).getStreams().add(value);
}));
return new ArrayList<>(temp.values());
resultStorage declaration:
private Map< SomeConfig, byte[]> resultStorage = new ConcurrentHashMap<>();
getStreams is a getter on SomeConfig that returns a List<byte[]> as here:
private List<byte[]> attachmentStreams = new ArrayList<>();
public List<byte[]> getAttachmentStreams() {
return attachmentStreams;
}
My first attempt was something similar to this:
resultStorage.entrySet().stream()
.forEach(entry -> entry.getKey().getUsers().forEach(user -> {
}));
Are we able to use a forEach within one of the streams terminating operation, forEach? How would a stream benefit in this case as I saw documentation that it can significantly improve readability and performance of older pre-Java8 code?
Edit:
resultStorage holds a ConcurrentHashMap. It will contain Map<SomeConfig, byte[]> for email and attachments. Using another HashMap temp that is initially empty - we analyze resultStorage , see if temp contains a specific email key, and then put or add based on the existence of a user's email
The terminal operation of entrySet().stream().forEach(…) is entirely unrelated to the getUsers().forEach(…) call within the Consumer. So there’s no problem of “multiple terminal operations” here.
However, replacing the Map operation forEach((key, value) -> … with an entrySet() .stream() .forEach(entry -> …) rarely adds a benefit. So far, you’re not only made the code longer, you introduced the necessity to deal with a Map.Entry instead of just using key and value.
But you can simplify your operation by using a single computeIfAbsent instead of containsKey, put, and get:
resultStorage.forEach((key, value) -> key.getUsers().forEach(user ->
temp.computeIfAbsent(user.getMeta(), meta ->
key.withCheck1(masterAccountId).withCheck2(getClientTimezone()).withCheck3(meta))
.getStreams().add(value)));
Notes after the code.
Map<String, SomeConfig> temp = resultStorage.keySet()
.stream()
.flatMap(key -> key.getUsers()
.stream()
.map(user -> new AbstractMap.SimpleEntry(user, key)))
.collect(Collectors.toMap(e -> e.getKey().getMeta(),
e -> e.getValue()
.withCheck1(masterAccountId)
.withCheck2(getClientTimezone())
.withCheck3(e.getKey().getMeta())
resultStorage.keySet()
This returns Set<SomeConfig>.
stream()
This returns a stream where every element in the stream is an instance of SomeConfig.
.flatMap(key -> key.getUsers()
.stream()
.map(user -> new AbstractMap.SimpleEntry(user, key)))
Method flatMap() must return a Stream. The above code returns a Stream where every element is an instance of AbstractMap.SimpleEntry. The "entry" key is the user and the entry value is the key from resultStorage.
Finally I create a Map<String, SomeConfig> via [static] method toMap of class Collectors.
The first argument to method toMap is the key mapper, i.e. a method that extracts the [map] key from the AbstractMap.SimpleEntry. In your case this is the value returned by method getMeta() of the user – which is the key from AbstractMap.SimpleEntry, i.e. e.getKey() returns a user object.
The second argument to toMap is the value mapper. e.getValue() returns a SomeConfig object and the rest is your code, i.e. the withChecks.
There is no way I can test the above code because not only did you not post a minimal, reproducible example, you also did not post any sample data. Hence the above may be way off what you actually require.
Also note that the above code simply creates your Map<String, SomeConfig> temp. I could not understand the code in your question that processes that Map so I did not try to implement that part at all.

Initializing a String on basis on java streams results in an empty String [duplicate]

The JavaDoc for Stream.collect() says that it returns "the result of the reduction". That doesn't tell me if code like this can return null for filteredList:
List<String> filteredList = inputList.stream()
.filter(c -> c.isActive())
.collect(Collectors.toList());
I would expect that if it could return null then it would return an Optional, but it doesn't say that either.
Is it documented anywhere whether Stream.collect() can return null?
Collector.toList() will return an empty List for you.
Here is the implementation:
public static <T>
Collector<T, ?, List<T>> toList() {
return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
(left, right) -> { left.addAll(right); return left; },
CH_ID);
}
As you can see ArrayList::new is being used as a container for your items.
From JavaDoc of Collector:
A mutable reduction operation that
accumulates input elements into a mutable result container, optionally
transforming the accumulated result into a final representation after
all input elements have been processed. Reduction operations can be
performed either sequentially or in parallel.
A Collector is specified by four functions that work together to
accumulate entries into a mutable result container, and optionally
perform a final transform on the result. They are:
creation of a new result container (supplier())
incorporating a new data element into a result container (accumulator())
combining two result containers into one (combiner())
performing an optional final transform on the container (finisher())
And
A sequential implementation of a reduction using a collector would
create a single result container using the supplier function, and
invoke the accumulator function once for each input element. A
parallel implementation would partition the input, create a result
container for each partition, accumulate the contents of each
partition into a subresult for that partition, and then use the
combiner function to merge the subresults into a combined result.
So as long as you don't do weird things like combine function return null, the Collector always return at least a mutable container using your provided supplier function.
And I think it's very counter-intuitive if an implementation would ever return null container.
This is not dependent on Stream.collect, but on the individual Collector. Collectors.toList() will return an empty ArrayList.
That said, there's no reason someone couldn't use a weird Collector to return null in certain circumstances:
.collect(
Collector.of(
ArrayList::new,
ArrayList::add,
(a, b) -> {
a.addAll(b);
return a;
},
a -> a.isEmpty() ? null : a // finisher replaces empty list with null
)
);
So the Collector is the thing you need to remember to check. I believe all of the Collectors available out-of-the-box will return empty collections, as you'd expect.
You could use Collectors::collectingAndThen to pass collect() result to a Function<T,R>. Return value of the Function<T,R> will be return value of collect().
List<String> filteredList = inputList.stream()
.filter(c -> c.isActive())
.collect(Collectors.collectingAndThen(Collectors.toList(), c -> !c.isEmpty()?c:null));
I think this part of the documentation says that it cannot be null:
Returns a Collector that accumulates the input elements into a new
List.
Highlights added by me. I think this new List means that something that isn't null.
I started to check ReferencePipeline.collect() to check whether it's true for the actual implementation. Unfortunately, it was a futile attempt. There are so many cases here, like is it parallel? is it after a forEach? etc.
This is collector-dependant. The one You're using (Collectors.toList()) returns an empty list.
No collect will never return null, in order to check use isEmpty() instead of null
I believe the following code is a good implementation if you really need to return null when the list is empty (assuming the type of the variable c is MyObj):
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toList;
...
List<String> filteredList = inputList.stream()
.filter(MyObj::isActive)
.collect(collectingAndThen(toList(), Stream::of)
.filter(List::isEmpty)
.findAny()
.orElse(null);

Can we Merge these three nested for loops by using streams in java 8?

While trying to convert the following code by using Streams I ended up in some error. I couldn't convert it by using the streams. I couldn't merge the for loops by using Streams
The below code is the one which has to be changed by using streams.
for (Admin ah : subProducers) {
List<String> wns = ah.getAdminSitCodes().stream()
.map(new SitCodeMap()).map(Pair::getFirst)
.distinct().collect(Collectors.toList());
for (String wn : wns) {
for (String value : values) {
if (wn.equals(value)) {
admin.add(ah);
}
}
}
}
I have tried as found below
admin = subProducers.stream().map(sp-> sp.getAdminSitCodes().stream()
.map(new SitCodeMap())
.map(Pair::getFirst).distinct()).collect(Collectors.toList())
.stream()
.filter(wns->values.stream().anyMatch(v-
>wns.equals(v)))
.collect(Collectors.toList());
Have to get this converted to a list using streams
You can make use of the Set collection for values and then complete the code as :
List<Admin> admin = subProducers.stream()
.filter(a -> a.getAdminSitCodes().stream()
.map(new SitCodeMap())
.map(Pair::getFirst) // assuming this is Stream<String>
.distinct()
.anyMatch(values::contains))
.collect(Collectors.toList());
You're probably looking for stream's flatMap():
https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#flatMap-java.util.function.Function-
After flatMap you get a "flattened" stream of all the elements of the sub-streams that are created within the flatmap.
So
subProducers.stream().flatMap(sp-> sp.getAdminSitCodes().stream())
produces a stream of all adminSitCodes whereas
subProducers.stream().map(sp-> sp.getAdminSitCodes().stream())
produces a stream of streams.

Is there any way to reuse a Stream? [duplicate]

This question already has answers here:
Copy a stream to avoid "stream has already been operated upon or closed"
(10 answers)
Closed 3 years ago.
I'm learning the new Java 8 features, and while experimenting with streams (java.util.stream.Stream) and collectors, I realized that a stream can't be used twice.
Is there any way to reuse it?
If you want to have the effect of reusing a stream, you might wrap the stream expression in a Supplier and call myStreamSupplier.get() whenever you want a fresh one. For example:
Supplier<Stream<String>> sup = () -> someList.stream();
List<String> nonEmptyStrings = sup.get().filter(s -> !s.isEmpty()).collect(Collectors.toList());
Set<String> uniqueStrings = sup.get().collect(Collectors.toSet());
From the documentation:
A stream should be operated on (invoking an intermediate or terminal stream operation) only once.
A stream implementation may throw IllegalStateException if it detects that the stream is being reused.
So the answer is no, streams are not meant to be reused.
As others have said, "no you can't".
But it's useful to remember the handy summaryStatistics() for many basic operations:
So instead of:
List<Person> personList = getPersons();
personList.stream().mapToInt(p -> p.getAge()).average().getAsDouble();
personList.stream().mapToInt(p -> p.getAge()).min().getAsInt();
personList.stream().mapToInt(p -> p.getAge()).max().getAsInt();
You can:
// Can also be DoubleSummaryStatistics from mapToDouble()
IntSummaryStatistics stats = personList.stream()
.mapToInt(p-> p.getAge())
.summaryStatistics();
stats.getAverage();
stats.getMin();
stats.getMax();
The whole idea of the Stream is that it's once-off. This allows you to create non-reenterable sources (for example, reading the lines from the network connection) without intermediate storage. If you, however, want to reuse the Stream content, you may dump it into the intermediate collection to get the "hard copy":
Stream<MyType> stream = // get the stream from somewhere
List<MyType> list = stream.collect(Collectors.toList()); // materialize the stream contents
list.stream().doSomething // create a new stream from the list
list.stream().doSomethingElse // create one more stream from the list
If you don't want to materialize the stream, in some cases there are ways to do several things with the same stream at once. For example, you may refer to this or this question for details.
As others have noted the stream object itself cannot be reused.
But one way to get the effect of reusing a stream is to extract the stream creation code to a function.
You can do this by creating a method or a function object which contains the stream creation code. You can then use it multiple times.
Example:
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
// The normal way to use a stream:
List<String> result1 = list.stream()
.filter(i -> i % 2 == 1)
.map(i -> i * i)
.limit(10)
.map(i -> "i :" + i)
.collect(toList());
// The stream operation can be extracted to a local function to
// be reused on multiple sources:
Function<List<Integer>, List<String>> listOperation = l -> l.stream()
.filter(i -> i % 2 == 1)
.map(i -> i * i)
.limit(10)
.map(i -> "i :" + i)
.collect(toList());
List<String> result2 = listOperation.apply(list);
List<String> result3 = listOperation.apply(Arrays.asList(1, 2, 3));
// Or the stream operation can be extracted to a static method,
// if it doesn't refer to any local variables:
List<String> result4 = streamMethod(list);
// The stream operation can also have Stream as argument and return value,
// so that it can be used as a component of a longer stream pipeline:
Function<Stream<Integer>, Stream<String>> streamOperation = s -> s
.filter(i -> i % 2 == 1)
.map(i -> i * i)
.limit(10)
.map(i -> "i :" + i);
List<String> result5 = streamOperation.apply(list.stream().map(i -> i * 2))
.filter(s -> s.length() < 7)
.sorted()
.collect(toCollection(LinkedList::new));
}
public static List<String> streamMethod(List<Integer> l) {
return l.stream()
.filter(i -> i % 2 == 1)
.map(i -> i * i)
.limit(10)
.map(i -> "i :" + i)
.collect(toList());
}
If, on the other hand, you already have a stream object which you want to iterate over multiple times, then you must save the content of the stream in some collection object.
You can then get multiple streams with the same content from than collection.
Example:
public void test(Stream<Integer> stream) {
// Create a copy of the stream elements
List<Integer> streamCopy = stream.collect(toList());
// Use the copy to get multiple streams
List<Integer> result1 = streamCopy.stream() ...
List<Integer> result2 = streamCopy.stream() ...
}
Come to think of it, this will of "reusing" a stream is just the will of carry out the desired result with a nice inline operation. So, basically, what we're talking about here, is what can we do to keep on processing after we wrote a terminal operation?
1) if your terminal operation returns a collection, the problem is solved right away, since every collection can be turned back into a stream (JDK 8).
List<Integer> l=Arrays.asList(5,10,14);
l.stream()
.filter(nth-> nth>5)
.collect(Collectors.toList())
.stream()
.filter(nth-> nth%2==0).forEach(nth-> System.out.println(nth));
2) if your terminal operations returns an optional, with JDK 9 enhancements to Optional class, you can turn the Optional result into a stream, and obtain the desired nice inline operation:
List<Integer> l=Arrays.asList(5,10,14);
l.stream()
.filter(nth-> nth>5)
.findAny()
.stream()
.filter(nth-> nth%2==0).forEach(nth-> System.out.println(nth));
3) if your terminal operation returns something else, i really doubt that you should consider a stream to process such result:
List<Integer> l=Arrays.asList(5,10,14);
boolean allEven=l.stream()
.filter(nth-> nth>5)
.allMatch(nth-> nth%2==0);
if(allEven){
...
}
The Functional Java library provides its own streams that do what you are asking for, i.e. they're memoized and lazy. You can use its conversion methods to convert between Java SDK objects and FJ objects, e.g. Java8.JavaStream_Stream(stream) will return a reusable FJ stream given a JDK 8 stream.

why java stream "reduce()" accumulates the same object

ComparisonResults comparisonResults = requestsList
.parallelStream()
.map(item -> getResponse(item))
.map(item -> compareToBl(item))
.reduce(new ComparisonResults(), (result1, result2) ->
{
result1.addSingleResult(result2);
return result1;
});
when
private ComparisonResults compareToBl(CompleteRoutingResponseShort completeRoutingResponseShortFresh) {
...
ComparisonResults comparisonResults = new ComparisonResults();
...
return comparisonResults;
}
however when I debug:
.reduce(new ComparisonResults(), (result1, result2) ->
{
result1.addSingleResult(result2);
return result1;
});
I see result1 and result2 are always the same object (object id in the
IDEA)
result1 equals result2
addSingleResult should return a new object as modified a copy of this so you should change your code to:
.reduce(new ComparisonResults(), (result1, result2) ->
{
return result1.addSingleResult(result2);
});
Otherwise, you are always returning the same instance (without modifications).
From Java documentation:
The reduce operation always returns a new value. However, the
accumulator function also returns a new value every time it processes
an element of a stream. Suppose that you want to reduce the elements
of a stream to a more complex object, such as a collection. This might
hinder the performance of your application. If your reduce operation
involves adding elements to a collection, then every time your
accumulator function processes an element, it creates a new collection
that includes the element, which is inefficient. It would be more
efficient for you to update an existing collection instead. You can do
this with the Stream.collect method, which the next section describes.

Categories

Resources