How can I accomplish using Optional and Streams? - java

public class Product {
private String name;
private List<Image> images;
//getters and setters
public class Images {
private String url;
private List<Thumbnail> thumbnails;
// getters and setters
public class Thumbnail{
//getters and setters
private String url;
}
}
}
}
I have this class. I need to fetch product name, url of the first image and url of the first thumbnail. I also need to make sure product, images and thumbnails are non-empty.
This is how I am trying to do it:
Optional.ofNullable(product).ifPresent(productData -> {
response.setProductName(productData.getName());
Optional.ofNullable(productData.getImages()).ifPresent(images -> {
images.stream().findFirst().ifPresent(image -> {
response.setImageUrl(image.getUrl());
Optional.ofNullable(image.getThumbnails()).ifPresent(thumbnails -> {
thumbnails.stream().findFirst().ifPresent(thumbnail -> {
response.getThumbnailUrl();
});
});
});
});
});
Is there a better way?

You shouldn't ever have null lists. An empty list represents the same thing without having to introduce a null check. That lets you get rid of two ifPresent checks.
Optional.ofNullable(product).ifPresent(productData -> {
response.setProductName(productData.getName());
productData.getImages().stream().findFirst().ifPresent(image -> {
response.setImageUrl(image.getUrl());
image.getThumbnails().stream().findFirst().ifPresent(thumbnail -> {
response.getThumbnailUrl();
});
});
});
Optionals and streams aren't really doing you any favors. Don't use them just because they exist. Consider just using regular if statements.
if (product == null) {
return;
}
if (!product.getImages().isEmpty()) {
Image image = product.getImages().get(0);
response.setImageUrl(image.getUrl());
if (!image.getThumbnails().isEmpty()) {
response.getThumbnailUrl();
}
}
Alternatively, you could simulate stream().findFirst().ifPresent() with for loops that break after the first iteration.
if (product == null) {
return;
}
for (Image image: product.getImages()) {
response.setImageUrl(image.getUrl());
for (Thumbnail thumbnail: image.getThumbnails()) {
response.getThumbnailUrl();
break;
}
break;
}

#John's answer is the correct one. If you can, return empty lists.
It might also be that you want to distinguish between not having any items and not having a result (in your case it doesn't make much sense, but we're talking hypothetically). Then return Optional<List<T>> instead of returning null then converting it. Then #Johannes' answer is the correct one.
Another way of thinking about the problem is, if you have no control over the return values, to convert it to a stream to chain the calls:
Optional.ofNullable(possiblyNullProduct).stream()
.peek(product -> response.setProductName(product.getName()))
.map(Product::getImages)
.filter(Objects::nonNull)
.map(images -> images.stream().findFirst())
.filter(Optional::isPresent).map(Optional::get)
.peek(image -> response.setImageUrl(image.getUrl())
.map(Image::getThumbnails)
.filter(Objects::nonNull)
.map(thumbnails -> thumbnails.stream().findFirst())
.filter(Optional::isPresent).map(Optional::get)
.forEach(thumbnail -> response.getThumbnailUrl());
Optional::stream was added in Java 9.
This is just another solution, by no means a better solution. I would welcome any comments on the performance.
Another option to get the first item of each list is to convert to an Optional and back into a Stream:
.map(Product::getImages)
.filter(Objects::nonNull)
.flatMap(List::stream).findFirst().stream() // <- changed here
.peek(image -> ...)
And you can also change out the last three lines in a similar way:
.map(Image::getThumbnails)
.filter(Objects::nonNull)
.flatMap(List::stream).findFirst() // <- from here
.ifPresent(thumbnail -> response.getThumbnailUrl());

Yes, flatMap will help here:
Optional<Product> optProduct = Optional.ofNullable(product);
optProduct.ifPresent(p -> response.setProductName(p.getName()));
Optional<Image> optImage = optProduct.flatMap(p -> Optional.ofNullable(p.getImages()))
.stream().flatMap(il -> il.stream()).findFirst();
optImage.ifPresent(i -> response.setImageUrl(i.getUrl()));
optImage.flatMap(i -> Optional.ofNullable(i.getThumbnails()))
.stream().flatMap(tl -> tl.stream()).findFirst()
.ifPresent(t -> response.getThumbnailUrl()); // WTF?
Node that Optional.stream() was added in Java 9.

I don't think the Optionals are helping much here. I would clean up the code by abstracting the iteration logic, like this:
private <T> void findFirst(List<T> list, Consumer<T> action) {
if (list != null) {
list.stream()
.findFirst()
.ifPresent(action);
}
}
if (product != null) {
response.setProductName(productData.getName());
findFirst(productData.getImages(), image -> {
response.setImageUrl(image.getUrl());
findFirst(image.getThumbnails(), thumbnail -> response.setThumbnailUrl(thumbnail.getUrl()));
});
}

Related

How to get value from an optional object in another optional?

Basically,I need to get a size of optional list in an optional object. Something like:
private int getCount(#NonNull Optional<myObject> aaa) {
if(aaa.isPresent() && aaa.get().getMyList().isPresent()) {
return aaa.get().getMyList().get().size();
}
return 0;
}
The code doesn't look nice. What's the elegant way to get it? With ifPresent().orElse()?
Thanks in advance!
Consecutive map (or flatMap, in case something returns an Optional) operations, and a final orElse:
private int getCount(#NonNull Optional<myObject> cvm) {
return cvm
.flatMap(x -> x.getMyList())
.map(list -> list.size()) // or List::size
.orElse(0);
}

Convert one Optional<List<Object>> to another Optional<List<Object>> in Java

How can I convert Optional List object from one type to another, for an example
Optional<List<ProductMultipleOptionViewModel>> productOptionType1 // One type
Optional<List<ProductMultipleOption>> productOptionType2 // Other type
ProductMultipleOptionViewModel
Type 1
#Introspected
public record ProductMultipleOptionViewModel(
ProductOptionViewModel productOption,
String optionName) {
}
Type 2
#Introspected
public record ProductMultipleOption(
ProductOptionViewModel productOption,
String optionName) {
}
I want to convert from Optional<List<ProductMultipleOption>>to other Optional<List<ProductMultipleOptionViewModel>>. I tried the below code
Optional<List<ProductMultipleOptionViewModel>> conveertItem = Optional.ofNullable(product.getProductMultipleOption())
.orElseGet(null)
.stream()
.map(option -> {
return new ProductMultipleOptionViewModel(
ProductOptionViewModel.valueOf(//Access the option value//), //access the option value//
);
})
.collect(Collectors.toList());
With the above code, I am not able to access the option value inside map method
If product.getProductMultipleOption() is null return null or empty list.
You should rarely use Optional and Collections (like List or Set) together. Instead you should work with empty Collections. Also keep in mind that Optionals should not really be used for conditional logic, but rather as return values.
Either using a normal if statement:
List<ProductMultipleOptionViewModel> conveertItem = new ArrayList<>();
if (product.getProductMultipleOption() != null) {
for(ProductMultipleOption option : product.getProductMultipleOption()) {
conveertItem.add(new ProductMultipleOptionViewModel(
ProductOptionViewModel.valueOf(option)
));
}
}
Another variant:
List<ProductMultipleOption> list = product.getProductMultipleOption();
if (list == null) {
list = Collections.emptyList();
}
List<ProductMultipleOptionViewModel> conveertItem = new ArrayList<>();
for(ProductMultipleOption option : list) {
conveertItem.add(new ProductMultipleOptionViewModel(
ProductOptionViewModel.valueOf(option)
));
}
Or if you really want to use Streams and Optionals (matter of taste):
List<ProductMultipleOptionViewModel> conveertItem = Optional.ofNullable(product.getProductMultipleOption())
.map(List::stream)
.orElseGet(Stream::empty)
.map(option -> new ProductMultipleOptionViewModel(
ProductOptionViewModel.valueOf(option)
))
.collect(Collectors.toList());
Now that's a lot of code for simply converting a nullable List. So why not return an empty List in the first place? Change product.getProductMultipleOption() to something like this:
public List<ProductMultipleOption> getProductMultipleOption() {
List<ProductMultipleOption> list = ...; // your current logic for getting the list
return list == null ? Collections.emptyList() : list;
}
That way you never have to worry about null checks. Because you're simply working with an empty collection wherever you're calling getProductMultipleOption().
It helps to think about dealing with nulls/empty optionals separately from dealing with the list. The code below deals with nulls using the Optional.map() method, which returns an empty optional (of the appropriate return type) if the given argument is empty; otherwise, it applies the mapping function on the list.
Optional<List<ProductMultipleOptionViewModel>> convertedItem =
Optional.ofNullable(product.getProductMultipleOption())
.map(list -> list.stream()
.map(option -> new ProductMultipleOptionViewModel(
option.productOption(),
option.optionName()))
.collect(Collectors.toList()));
Might not be the best way to do whatever you're doing... but to answer your question if you're trying to work with what you've got and keep it minimal:
private List<ProductMultipleOption> getProductOptionViewModelList() {
/* simulating return of a list that could be null. */
return null;
}
private Optional<List<ProductMultipleOption>> getProductMultipleOptionNull() {
/* simulating return of an optional list. */
return Optional.empty();
}
private static class ProductOptionViewModel { }
public record ProductMultipleOptionViewModel(
ProductOptionViewModel productOption,
String optionName) {
}
public record ProductMultipleOption(
ProductOptionViewModel productOption,
String optionName) {
}
/*
Create your own methods to convert the models.
Replace the variables with whichever method is available to get the name:
(inputOption.productOption, inputOption.optionName)
(inputOption.productOption(), inputOption.optionName())
. (inputOption.getProductOption(), inputOption.getOptionName())
*/
private ProductMultipleOptionViewModel convertToMultipleOptionViewModel(
ProductMultipleOption inputOption) {
return new ProductMultipleOptionViewModel(
inputOption.productOption,
inputOption.optionName);
}
private ProductMultipleOption convertToMultipleOption(
ProductMultipleOptionViewModel inputOption) {
return new ProductMultipleOption(
inputOption.productOption,
inputOption.optionName);
}
/*
If the list you're getting is Optional<List<ProductOptionViewModel>>
and you want List<ProductMultipleOptionViewModel>
*/
List<ProductMultipleOptionViewModel> convertedFromOptionalList =
getProductMultipleOptionNull()
.stream()
.flatMap(Collection::stream)
.map(this::convertToMultipleOptionViewModel)
.toList();
/*
If the list you're getting is List<ProductOptionViewModel>
and you want List<ProductMultipleOptionViewModel>
*/
List<ProductMultipleOptionViewModel> convertedFromNullableList = Optional
.ofNullable(getProductOptionViewModelList())
.stream()
.flatMap(Collection::stream)
.map(this::convertToMultipleOptionViewModel)
.toList();
/*
If for some reason you're trying to get the list as
Optional<List<ProductOptionViewModel>> you can wrap
them with Optional.of() :
*/
Optional<List<ProductMultipleOptionViewModel>> convertedFromOptionalList = Optional
.of(Optional.ofNullable(getProductOptionViewModelList())
.stream()
.flatMap(Collection::stream)
.map(this::convertToMultipleOptionViewModel)
.toList());
Optional<List<ProductMultipleOptionViewModel>> convertedFromNullableList = Optional
.of(getProductMultipleOptionNull()
.stream()
.flatMap(Collection::stream)
.map(this::convertToMultipleOptionViewModel)
.toList());

Sort list by multiple fields(not then compare) in java

Now I have an object:
public class Room{
private long roomId;
private long roomGroupId;
private String roomName;
... getter
... setter
}
I want sort list of rooms by 'roomId', but in the meantime while room objects has 'roomGroupId' greator than zero and has same value then make them close to each other.
Let me give you some example:
input:
[{"roomId":3,"roomGroupId":0},
{"roomId":6,"roomGroupId":0},
{"roomId":1,"roomGroupId":1},
{"roomId":2,"roomGroupId":0},
{"roomId":4,"roomGroupId":1}]
output:
[{"roomId":6,"roomGroupId":0},
{"roomId":4,"roomGroupId":1},
{"roomId":1,"roomGroupId":1},
{"roomId":3,"roomGroupId":0},
{"roomId":2,"roomGroupId":0}]
As shown above, the list sort by 'roomId', but 'roomId 4' and 'roomId 1' are close together, because they has the same roomGroupId.
This does not have easy nice solution (maybe I am wrong).
You can do this like this
TreeMap<Long, List<Room>> roomMap = new TreeMap<>();
rooms.stream()
.collect(Collectors.groupingBy(Room::getRoomGroupId))
.forEach((key, value) -> {
if (key.equals(0L)) {
value.forEach(room -> roomMap.put(room.getRoomId(), Arrays.asList(room)));
} else {
roomMap.put(
Collections.max(value, Comparator.comparing(Room::getRoomId))
.getRoomId(),
value
.stream()
.sorted(Comparator.comparing(Room::getRoomId)
.reversed())
.collect(Collectors.toList())
);
}
});
List<Room> result = roomMap.descendingMap()
.entrySet()
.stream()
.flatMap(entry -> entry.getValue()
.stream())
.collect(Collectors.toList());
If you're in Java 8, you can use code like this
Collections.sort(roomList, Comparator.comparing(Room::getRoomGroupId)
.thenComparing(Room::getRoomId));
If not, you should use a comparator
class SortRoom implements Comparator<Room>
{
public int compare(Room a, Room b)
{
if (a.getRoomGroupId().compareTo(b.getRoomGroupId()) == 0) {
return a.getRoomId().compareTo(b.getRoomId());
}
return a.getRoomGroupId().compareTo(b.getRoomGroupId();
}
}
and then use it like this
Collections.sort(roomList, new SortRoom());

Handling Null pointer in the given Java stream based code

I am having the below given data structure.
In the given code block, Im trying to get Object Dc from the given HashMap dealCommits.
The piece of code will work if the map has got the object associated with the given key (cNumber) is available.
My problem is how to handle the null pointer in line (//5) when the given key is not found in the map.
It will be a great help if somebody can throw some light to amend this stream based code to handle the exception.
public class Dc {
private String cNumber;
private Optional<List<pTerms>> pTerms;
}
public class Dd {
private String cParty;
//Dc:cNumber is the Key
private Optional<Map<String,Dc>> dealCommits;
}
public class PTerms {
private String pType;
}
public String check(String tMonth,String cNumber,Dd dDetails)
{
Optional<DealPricingTerms> dealPricingTerms = dDetails
.getDealCommits().get()
.get(cNumber)
.getPTerms().get().stream() //5
.filter(dealPricingTerm ->
tMonth.equals(dealPricingTerm.getDeliveryPeriod()))
.findFirst();
return dealPricingTerms.isPresent()? "Success" : "failed";
}
You shouldn't call get() on your Optionals - and when you use Optional.map you have a nice way to wrap a null result from a Map get into another Optional:
Optional<PTerms> dealPricingTerms = dDetails
.getDealCommits().map(c -> c.get(cNumber))
.flatMap(dc -> dc.getPTerms())
.map(l -> l.stream())
.flatMap(s ->
s.filter(dealPricingTerm ->
tMonth.equals(dealPricingTerm.getDeliveryPeriod()))
.findFirst());
You have to add filter to check null before your condition filter.
.filter(Objects::nonNull)
for example,
List<String> carsFiltered = Optional.ofNullable(cars)
.orElseGet(Collections::emptyList)
.stream()
.filter(Objects::nonNull) //filtering car object that are null
.map(Car::getName) //now it's a stream of Strings
.filter(Objects::nonNull) //filtering null in Strings
.filter(name -> name.startsWith("M"))
.collect(Collectors.toList()); //back to List of Strings

Converting to streams

I'd like to convert the following code, which breaks from the outer loop, into Java 8 Streams.
private CPBTuple getTuple(Collection<ConsignmentAlert> alertsOnCpdDay)
{
CPBTuple cpbTuple=null;
OUTER:
for (ConsignmentAlert consignmentAlert : alertsOnCpdDay) {
List<AlertAction> alertActions = consignmentAlert.getAlertActions();
for (AlertAction alertAction : alertActions) {
cpbTuple = handleAlertAction(reportDTO, consignmentId, alertAction);
if (cpbTuple.isPresent()) {
break OUTER;
}
}
}
return cpbTuple;
}
Every answer here uses flatMap, which until java-10 is not lazy. In your case that would mean that alertActions is traversed entirely, while in the for loop example - not. Here is a simplified example:
static class User {
private final List<String> nickNames;
public User(List<String> nickNames) {
this.nickNames = nickNames;
}
public List<String> getNickNames() {
return nickNames;
}
}
And some usage:
public static void main(String[] args) {
Arrays.asList(new User(Arrays.asList("one", "uno")))
.stream()
.flatMap(x -> x.getNickNames().stream())
.peek(System.out::println)
.filter(x -> x.equalsIgnoreCase("one"))
.findFirst()
.get();
}
In java-8 this will print both one and uno, since flatMap is not lazy.
On the other hand in java-10 this will print one - and this is what you care about if you want to have your example translated to stream-based 1 to 1.
Something along the lines of this should suffice:
return alertsOnCpdDay.stream()
.flatMap(s-> s.getAlertActions().stream())
.map(s-> handleAlertAction(reportDTO, consignmentId, s))
.filter(s-> s.isPresent())
.findFirst().orElse(null);
That said, a better option would be to change the method return type to Optional<CPBTuple> and then simply return the result of findFirst(). e.g.
private Optional<CPBTuple> getTuple(Collection<ConsignmentAlert> alertsOnCpdDay) {
return alertsOnCpdDay.stream()
.flatMap(s-> s.getAlertActions().stream())
.map(s-> handleAlertAction(reportDTO, consignmentId, s))
.filter(s-> s.isPresent())
.findFirst();
}
This is better because it better documents the method and helps prevent the issues that arise when dealing with nullity.
Since you break out of the loops upon the first match, you can eliminate the loops with a Stream with flatMap, which returns the first available match:
private CPBTuple getTuple(Collection<ConsignmentAlert> alertsOnCpdDay) {
return alertsOnCpdDay.stream()
.flatMap(ca -> ca.getAlertActions().stream())
.map(aa -> handleAlertAction(reportDTO, consignmentId, aa))
.filter(CPBTuple::isPresent)
.findFirst()
.orElse(null);
}
Try this out,
alertsOnCpdDay.stream()
.map(ConsignmentAlert::getAlertActions)
.flatMap(List::stream)
.map(alertAction -> handleAlertAction(reportDTO, consignmentId, alertAction))
.filter(CPBTuple::isPresent)
.findFirst().orElse(null);

Categories