Java Lambda for break inside two for loops - java

I am trying to convert an iterative block of code in Java 8 to functional. The functional approach is unable to find the matching message in the set shared.
List<Optional<Message>> allMessages = new ArrayList<>();
Set<Status> allStatuses = getAllStatuses();
//Iterative : Working
Set<StatusMessage> set = new HashSet<>(STATUS_MESSAGE.values());
for (StatusMessage statusMessage : set) {
for (Status status : statusMessage.getStatusAndInfo().keySet()) {
Optional<Message> message = MessageBuilder.createMessage(allStatuses, status, this::createMessage);
if (message.isPresent()) {
allMessages.add(message);
break;
}
}
}
//Functional : Not working - Never adds anything to the
//map even when matching status is present
STATUS_MESSAGE.values().stream()
.distinct()
.map(statusMessage -> statusMessage.getStatusAndInfo().keySet())
.flatMap(Collection::stream)
.map(key -> MessageBuilder.createMessage(allStatuses, key, this::createMessage))
.anyMatch(allMessages::add);
The MessageBuilder.createMessage looks like this:
Optional<Status> matchingStatus = statuses.stream()
.filter(matchingStatus::equals)
.findFirst();
System.out.println("Found : " + matchingStatus.toString());
return matchingStatus.flatMap(creator);
Also, for debugging purposes, how can I see what is happening at each step of the stream? The stack in the debugger in intellij wasn't showing anything in the stream.

This should do it:
STATUS_MESSAGE.values().stream()
.distinct()
.forEach(statusMessage ->
statusMessage.getStatusAndInfo().keySet().stream()
.map(status -> MessageBuilder.createMessage(allStatuses, status, this::createMessage))
.filter(Optional::isPresent)
.findFirst()
.ifPresent(allMessages::add)
);
UPDATE
To build the result list using toList instead of adding to a list:
List<Optional<Message>> allMessages = STATUS_MESSAGE.values().stream()
.distinct()
.flatMap(statusMessage ->
statusMessage.getStatusAndInfo().keySet().stream()
.map(status -> MessageBuilder.createMessage(allStatuses, status, this::createMessage))
.filter(Optional::isPresent)
.limit(1)
)
.collect(Collectors.toList());

This should be a comment, but it's too long...
Seems like your MessageBuilder.createMessage method is overcomplicated.
Check below a simplified and more readable version of the same logic:
if (allStatuses.contains(status)) {
System.out.println("Found : " + status.toString());
return creator.apply(status);
}
return Optional.empty();

You should not use forEach for accumulating operations, so this should be more idiomatic:
Function<StatusInfo, Optional<Message>> messageForStatus = statusInfo ->
statusInfo().keySet().stream()
.map(status -> MessageBuilder.createMessage(allStatuses, status, this::createMessage))
.filter(Optional::isPresent)
.findFirst()
.orElse(Optional.empty());
allMessages = STATUS_MESSAGE.values().stream()
.distinct()
.map(StatusMessage::getStatusAndInfo)
.map(messageForStatus)
.filter(Optional::isPresent)
.collect(toList());
As a side note, you have too many optionals, you may want to consider unwrapping some earlier, as a list of optionals may just as well be the list of only the present values.

Related

Using Java Optional within stream mapping

I have code like this:
public void processList(List<String> list) {
for (String item : list) {
Object obj = getObjectForString(item);
if (obj != null) {
doSomethingWithObject(obj);
} else {
System.err.println("Object was null for " + item);
}
}
}
Ideally I would like to streamline this and avoid the null check using list.stream().map( *blah, blah, blah* ), and doSomethingWithObject if the object is not null, but log the error otherwise (by using the orElse method on an optional). I'm not super savvy with this Java 8 functionality and not sure if there is a nice, slick way to do what I want here or not. Suggestions?
Edit to add a failed attempt at this:
list.stream()
.map(p -> getObjectForString(p))
.map(Optional::ofNullable)
.forEach(
p -> p.ifPresentOrElse(
r -> doSomethingWithObject(r),
() -> System.err.println("Object was null")
));
Even if that code behaved the way I want, it still doesn't append the String from the original list to the error message as I would like it to. But maybe that's too much complexity to try to accomplish with streams like this.
we should propagate the item even after conversion. The slick way is using tuple or pair.
I used Tuple from vavr functional library to do the same. And below is the code for your reference
list.stream()
.map(p -> Tuple.of(p, getObjectForString(p)).map2(Optional::ofNullable))
.forEach(p -> p._2.ifPresentOrElse(
r -> doSomethingWithObject(r),
() -> System.err.println("Object was null" + p._1))
);
Another approach would be to collect the items in to separate 2 buckets/partitions based on if the item had an associated object or not. After that, process the 2 buckets as required:
final Boolean HAS_OBJECT = Boolean.FALSE;
Map<Boolean, List<String>> partitionedMap = list.stream()
.collect(Collectors.partitioningBy(item -> !Objects.isNull(getObjectForString(item))));
partitionedMap.get(HAS_OBJECT).stream()
.map(item -> getObjectForString(item))
.forEach(obj -> doSomethingWithObject(obj));
partitionedMap.get(!HAS_OBJECT)
.forEach(item -> System.err.println("Object was null for " + item));
Even though the below method does not avoid a null check as you wanted in your question, this is just another way to achieve the same result. (Only benefit is that it saves 1-2 lines of code!).
The below code uses Runnable (takes no arguments and returns nothing as well) along with Java 8's Function.
NOTE : I would still recommend the normal for loop :-), as I believe that the below might look fancy, but the for loop is more easy to understand in this particular case.
Function<String, Runnable> func = item -> {
Object obj = getObjectForString(item);
return (obj != null) ? ( () -> doSomethingWithObject(obj))
: ( () -> System.err.println("Object was null for " + item));
};
list.stream().map(func).forEach(Runnable::run);

Logging warning message while processing streams in java

I want to log error messages if the map rdsDesksByName.get(e.getKey()) does not have any value for the key.
How can I handle this within streams?
Currently I return createDeskWithScope(rdsDesksByName.get(e.getKey()),ds.outOfScope()), but how can I enhance this function to log an error message if rdsDesksByName does not have any value for the key, otherwise it should process as usual
List<Desk> desks = deskScopesByName.entrySet()
.stream()
.flatMap(e -> {
return deskScopesByName.get(e.getKey())
.stream()
.map(ds -> {
return createDeskWithScope(rdsDesksByName.get(e.getKey()), ds.outOfScope());
});
})
.collect(Collectors.toList());
In the flatmapping operation, find an object by the key and use the if-else condition:
If the object is null (no value for such key), log a message and return Stream.empty().
Otherwise, continue processing using createDeskWithScope method.
Here is how the code might look like (untested, but should give you an idea):
List<Desk> desks = deskScopesByName.entrySet()
.stream()
.flatMap(entry -> {
String key = entry.getKey();
Desk rdsDesk = rdsDesksByName.get(key);
if (rdsDesk == null) {
log.warn("There is no Desk present for {}", key);
return Stream.empty();
} else {
return entry.getValue()
.stream()
.map(ds -> createDeskWithScope(rdsDesk, ds.outOfScope()));
}
})
.collect(Collectors.toList());
I recommend wrapping the flatmapping operation inside a method.

Java 8 stream with two Lists

I have a method takes 2 lists as parameters and as you can see in the method body I want to do some filtering and returning the result to the caller. I wanted to convert this code to the Java 8 stream with lambda expressions but I couldn't figure that out. I ended up creating more than one stream for this and it beats the purpose of this refactoring (IMHO). What I wanted to know is that how I do, in a simple way, refactor this into just one stream?
public Set<CustomerTrack> getCustomerTracks(List<CusomerTrack> tracks, List<Customer> customers) {
Set<CustomerTrack> tracksToSave = new HashSet<>();
for (Customer customer : customers) {
if (customer.getTrack() == null) {
continue;
}
Long allowedTrackId = customer.getTrack().getId();
for (CustomerTrack track : tracks) {
if (Long.valueOf(track.getId()).equals(allowedTrackId)) {
tracksToSave.add(track);
}
}
}
return tracksToSave;
}
Seems that this is what you are after:
customers.stream()
.filter(c -> c.getTrack() != null)
.map(c -> c.getTrack().getId())
.flatMap(id -> tracks.stream().filter(track -> Long.valueOf(track.getId()).equals(id)))
.collect(Collectors.toSet());
Just note that for each id you are iterating the entire list of tracks; this has O(n*m) complexity. This is generally see as bad and you can improve it.
To make it better you would first create a HashSet of ids from Customer; having that HashSet you can now call contains on it with the ids you are interested in, since contains has a time complexity of O(1) (it's really called amortized complexity of O(1)). So now your complexity becomes O(n) + O(1), but since O(1) is a constant, it's really O(n) - much better that what you had before. In code:
Set<Long> set = customers.stream()
.filter(c -> c.getTrack() != null)
.map(c -> c.getTrack().getId())
.collect(Collectors.toSet());
Set<CusomerTrack> tracksToSave = tracks.stream()
.filter(track -> set.contains(track.getId())
.collect(Collectors.toSet()));
An additional way favoring method reference usage :
Set<Track> tracks =
customers.stream()
.map(Customer::getTrack) // customer to track
.filter(Objects::nonNull) // keep non null track
.map(Track::getId) // track to trackId
.flatMap(trackId -> tracks.stream() // collect tracks matching with trackId
.filter(t-> Long.valueOf(t.getId()).equals(trackId))
)
.collect(toSet());
Firstly you can create a Set of allowed Ids:
Set<Long> collect = customers.stream()
.filter(customer -> customer.getTrack() != null)
.map(customer -> customer.getTrack().getId())
.collect(Collectors.toSet());
Then you can filler your track collection
Set<CusomerTrack> tracksToSave = tracks.stream()
.filter(track -> collect.contains(Long.valueOf(track.getId())))
.collect(Collectors.toSet());
Try this one
customers.stream()
.filter(customer -> customer.getTrack() != null)
.map(c -> c.getTrack().getId())
.forEach(allowedTrackId -> {
tracks.stream()
.filter(track -> Long.valueOf(track.getId()).equals(allowedTrackId))
.forEach(tracksToSave::add);
});
The important Operator here is flatMap
Set<CustomerTrack> tracksToSave = customers.stream()
.map(Customer::getTrack)
.filter(track -> track != null)
.flatMap(track -> {
tracks.stream.filter(it -> Long.valueOf(it.getId()).equals(track.getId())))
.collect(Collectors.toSet());
You need to filter the null values first and then filter it with the list of customerTrack.
Hope this answer helps you.
return customers.stream().map(cust-> cust.track).filter(track -> track != null).
collect(Collectors.toList())
.stream().filter(track-> customerTracks.stream()
.anyMatch(ele -> ele.getId() ==
track.getId())).collect(Collectors.toSet());
You could try something like this
customers
.stream()
.map(Customer::getTrack)
.filter(Objects::nonNull)
.map(CustomerTrack::getId)
.flatMap(trackId -> tracks
.stream()
.filter(track -> Long.valueOf(track.getId()).equals(trackId)))
.collect(Collectors.toSet());

Java8 filter and return if only element

How can I filter a list using java8 streams and return the found element if it is the only element in the filtered list, otherwise(if there are more which meet the condition, or there is no result that meets the condition) return for example an Optional.empty()
I would need something like this:
Suppose I have a:
List<String> list = Arrays.asList("Apple","Banana","Peach");
then I want:
Optional<String> string = list.stream()
.filter(item -> item.startsWith("A"))
.findOne();
I know I can do it by:
boolean singleElement = list.stream()
.filter(item -> item.startsWith("A"))
.count() == 1;
String string = null;
if(singleElement){
string = list.stream().filter(item -> item.startsWith("A")).iterator().next();
}
but I was wondering if I can do it in a single stream?
Is there any single stream solution?
Not very pretty, but you could limit the stream to 2 elements, collect those in a List, and see if that list has exactly one element. This still has more than one line, and has some overhead for creating the list, but that overhead is limited (to a list of size 2) and it does not have to iterate the stream twice, either.
List<String> tmp = list.stream()
.filter(item -> item.startsWith("A"))
.limit(2)
.collect(Collectors.toList());
Optional<String> res = tmp.size() == 1 ? Optional.of(tmp.get(0)) : Optional.empty();
(My other idea was to use reduce((s1, s2) -> null) after limit(2) and reduce any two matches to null, but instead of returning an Optional.empty this will just raise an Exception, i.e. it does not work, but maybe this triggers some better (working) ideas.)
Update: It seems like while reduce raises an Exceptions, Collectors.reducing does not, and instead returns an Optional.empty as desired, so this also works, as shown in this answer to a very similar question. Still, I'd add limit(2) to make it stop early:
Optional<String> res = list.stream()
.filter(item -> item.startsWith("A"))
.limit(2)
.collect(Collectors.reducing((s1, s2) -> null));
(If you like this last part, please upvote the original answer.)
You could use google Guava library's MoreCollectors.onlyElement as below:
List<String> list = Arrays.asList("Apple", "Banana", "Peach");
String string = null;
try {
string = list.stream()
.filter(item -> item.startsWith("A"))
.collect(MoreCollectors.onlyElement());
} catch (NoSuchElementException | IllegalArgumentException iae) {
System.out.println("zero or more than one elements found.");
}
Optional<String> res = string == null ? Optional.empty() : Optional.of(string);
Notice it throws NoSuchElementException if there is no element and it throws IllegalArgumentException if there are more than one elements.
I don't know if this counts as a single operation to you, but you can do :
Arrays.asList("Apple", "Banana", "Peach")
.stream()
.collect(Collectors.collectingAndThen(
Collectors.partitioningBy(
x -> x.startsWith("A")),
map -> {
List<String> list = map.get(Boolean.TRUE);
return list.size() == 1 ? Optional.of(list.get(0)) : Optional.empty();
}));

CompletableFutures and filtering based on values that are inside

I'm in a bit of confusion right now, so I have a method that should return CompletableFuture<List<A>>
inside the method is:
CompletableFuture<List<String>> toReturn = asyncCall().thenApply(....)
.thenCompose(listOfStuff -> convertToList(listOfStuff.stream().map(
key -> asyncCall2(key)
.thenApply(optionalValue -> optionalValue.orElse(null))
).collect(Collectors.toList()));
and convertToList() simply joins futures to convert CompletableFuture<List<ComputableFuture<A>>> into CompletableFuture<List<A>>
Basically my intention is to filter null values that emerge from optionalValue.orElse(null) And it would be easy to do filter before collecting it all to list in the last line, but if I use it just before .collect it is working over CompletableFutures
I suspect there's a lot of restructuring I can do in my code.
EDIT:
private<T> CompletableFuture<List<T>> convertToList(List<CompletableFuture<T>> toConvert) {
return CompletableFuture.allOf(toConvert.toArray(new CompletableFuture[toConvert.size()]))
.thenApply(v -> toConvert.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList())
);
}
The best way would probably be to change convertToList() so that it does not return a future of list, but of stream instead:
private <T> CompletableFuture<Stream<T>> convertToFutureOfStream(List<CompletableFuture<T>> toConvert) {
return CompletableFuture.allOf(toConvert.stream().toArray(CompletableFuture[]::new))
.thenApply(
v -> toConvert.stream()
.map(CompletableFuture::join)
);
}
This will be more reusable as the method will allow better chaining and will not force the caller to work with a list, while still allowing to easily get a list with a simple collect.
You can then simply filter that stream to remove empty optionals:
CompletableFuture<List<String>> toReturn = asyncCall()
.thenCompose(listOfStuff -> convertToFutureOfStream(
listOfStuff.stream()
.map(this::asyncCall2)
.collect(Collectors.toList())
)
.thenApply(stream ->
stream.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList())
)
);
You can even improve this a little further by changing convertToFutureOfStream() to take a stream as argument as well:
private <T> CompletableFuture<Stream<T>> convertToFutureOfStream(Stream<CompletableFuture<T>> stream) {
CompletableFuture<T>[] futures = stream.toArray(CompletableFuture[]::new);
return CompletableFuture.allOf(futures)
.thenApply(v -> Arrays.stream(futures).map(CompletableFuture::join));
}
(unfortunately this raises an unchecked assignment warning because of the array of generic types)
Which then gives
CompletableFuture<List<String>> toReturn = asyncCall()
.thenCompose(listOfStuff -> convertToFutureOfStream(
listOfStuff.stream().map(this::asyncCall2)
)
.thenApply(stream ->
stream.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList())
)
);

Categories