Update
Ok, I think I know how to work with streams now. The code in the old post is a mess and I'm not proud of it. So, thank you for you help for directing me in the right direction. I wrote a supplier class, which provides me with items and used static filters and mapper functions:
final TradeSupplier tradeSupplier = new TradeSupplier();
Stream.generate(tradeSupplier).map(TradeSupplier::getPrice)
.map(TradeSupplier::getTradePartner)
.map(TradeSupplier::getTradeInfo)
.filter(TradeSupplier::validateInfo)
.map(TradeSupplier::getPartnerAssetId)
.filter(TradeSupplier::validatePartnerAssetId).forEach(t -> {
if (trade.sendTrade(t)) {
tradeSupplier.finishedItem();
TradeCache.save(t);
}
});
With this design, I don't need flatMap, because it's just an one by one mapping. Additional information is filed into the item, which is just in the stream
I hope, this code is better than the code below... What do you think?
I'm appreciative for any help to improve my understanding of streams :)
Old post
I'm looking for help for the "new" stream api of java 8: first I get a list of items, for every item I collect a list of strings and after that, i want to combine the string with their corresponding item:
input:
item1
item2
wanted output:
item1; string1
item1; string2
item2; string1
item2; string2
item2; string3
Is the following code the right way to use this api?
Code (with stream api)
// input is a list of items
analyst.getInRange(wantMinValue, wantMaxValue)
.stream()
.filter(i -> !haveItem.getName().contains(i.getName())
|| (!haveItem.getModel().contains(i.getModel()) && haveItem
.getQuality() > i.getQuality()))
// get extra information and add it to a list (key: item; value: string)
.collect(Collectors.toMap(s -> s, s -> lounge.getItemId(s)))
.entrySet()
.stream()
// delete all null and empty strings
.filter(e -> e.getValue() != null && !e.getValue().isEmpty())
// for every entry in list, create an list and add to result
.forEach(
e -> {
lounge.getListOfValue(e.getValue(), 1)
.stream()
.filter(s -> s != null && !s.isEmpty())
.map(s -> lounge.getStringFromOldString(s))
.filter(s -> s != null && !s.isEmpty())
.collect(
Collectors
.toCollection(HashSet::new))
// add list to resulting list
.forEach(
s -> {
result.add(new TradeLink(s,
haveItem, e.getKey()));
});
});
First thing: .filter(s -> s != null && !s.isEmpty())
Don't include these things unless these are actually things that can happen. Are empty strings or null strings actually going to come up in your application? (If so, that probably reflects a design flaw in the first place. It may be better to let your application crash, because nulls generally shouldn't be in your program in ways like this.)
Second: don't do the mutable thing you're doing here:
.forEach(
e -> {
lounge.getListOfValue(e.getValue(), 1)
.stream()
.filter(s -> s != null && !s.isEmpty())
.map(s -> lounge.getStringFromOldString(s))
.filter(s -> s != null && !s.isEmpty())
.collect(
Collectors
.toCollection(HashSet::new))
// add list to resulting list
.forEach(
s -> {
result.add(new TradeLink(s,
haveItem, e.getKey()));
});
});
Instead, do something like:
.flatMap(e ->
lounge.getListOfValue(e.getValue(), 1)
.stream()
.map(lounge::getStringFromOldString)
.distinct()
.map(s -> new TradeLink(s, haveItem, e.getKey()))
.collect(toList())
Related
I have a list of objects say car. I want to filter this list based on some parameter using Java 8. But if the parameter is null, it throws NullPointerException. How to filter out null values?
Current code is as follows
requiredCars = cars.stream().filter(c -> c.getName().startsWith("M"));
This throws NullPointerException if getName() returns null.
In this particular example, I think #Tagir is 100% correct get it into one filter and do the two checks. I wouldn't use Optional.ofNullable the Optional stuff is really for return types not to be doing logic... but really neither here nor there.
I wanted to point out that java.util.Objects has a nice method for this in a broad case, so you can do this:
cars.stream()
.filter(Objects::nonNull)
Which will clear out your null objects. For anyone not familiar, that's the short-hand for the following:
cars.stream()
.filter(car -> Objects.nonNull(car))
To partially answer the question at hand to return the list of car names that starts with "M":
cars.stream()
.filter(car -> Objects.nonNull(car))
.map(car -> car.getName())
.filter(carName -> Objects.nonNull(carName))
.filter(carName -> carName.startsWith("M"))
.collect(Collectors.toList());
Once you get used to the shorthand lambdas you could also do this:
cars.stream()
.filter(Objects::nonNull)
.map(Car::getName) // Assume the class name for car is Car
.filter(Objects::nonNull)
.filter(carName -> carName.startsWith("M"))
.collect(Collectors.toList());
Unfortunately once you .map(Car::getName) you'll only be returning the list of names, not the cars. So less beautiful but fully answers the question:
cars.stream()
.filter(car -> Objects.nonNull(car))
.filter(car -> Objects.nonNull(car.getName()))
.filter(car -> car.getName().startsWith("M"))
.collect(Collectors.toList());
You just need to filter the cars that have a null name:
requiredCars = cars.stream()
.filter(c -> c.getName() != null)
.filter(c -> c.getName().startsWith("M"));
The proposed answers are great. Just would like to suggest an improvement to handle the case of null list using Optional.ofNullable, new feature in Java 8:
List<String> carsFiltered = Optional.ofNullable(cars)
.orElseGet(Collections::emptyList)
.stream()
.filter(Objects::nonNull)
.collect(Collectors.toList());
So, the full answer will be:
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
You can do this in single filter step:
requiredCars = cars.stream().filter(c -> c.getName() != null && c.getName().startsWith("M"));
If you don't want to call getName() several times (for example, it's expensive call), you can do this:
requiredCars = cars.stream().filter(c -> {
String name = c.getName();
return name != null && name.startsWith("M");
});
Or in more sophisticated way:
requiredCars = cars.stream().filter(c ->
Optional.ofNullable(c.getName()).filter(name -> name.startsWith("M")).isPresent());
Leveraging the power of java.util.Optional#map():
List<Car> requiredCars = cars.stream()
.filter (car ->
Optional.ofNullable(car)
.map(Car::getName)
.map(name -> name.startsWith("M"))
.orElse(false) // what to do if either car or getName() yields null? false will filter out the element
)
.collect(Collectors.toList())
;
you can use this
List<Car> requiredCars = cars.stream()
.filter (t-> t!= null && StringUtils.startsWith(t.getName(),"M"))
.collect(Collectors.toList());
I'm trying to reach a lambda expression avoiding doing this:
for (OrderEntity o: onEntryL) {
for(GeoFenceEventEntity g: o.getGeoFenceEvent()){
if(null != g.getEndAt() && g.getDynamoGeofenceType().equalsIgnoreCase("WAREHOUSE")){
//all of them, get data
}
}
}
And on Lambda trying something like this (with errors):
List<OrderEntity> chargingL = onEntryL.stream()
.map(o->o.getGeoFenceEvent().stream()
.map(g->null != g.getEndAt() && g.getDynamoGeofenceType().equalsIgnoreCase("WAREHOUSE"))
.collect(Collectors.toList()));
Appreciate any help, regards.
OK, update for comment. Assuming you take the OrderEntry if any GeoFenceEventEntity meets your conditions then you can use
List<OrderEntity> chargingL = onEntryL
.stream()
.filter(o -> o.getGeoFenceEvent().stream().anyMatch(g -> null != g.getEndAt() && g.getDynamoGeofenceType().equalsIgnoreCase("WAREHOUSE")))
.collect(Collectors.toList());
I think you want flatMap with filter.
onEntryL.stream()
.map(OrderEntity::getGeoFenceEvent)
.flatMap(e -> e.stream().filter(g -> null != g.getEndAt() && g.getDynamoGeofenceType().equalsIgnoreCase("WAREHOUSE")))
.flatMap(g -> g.getData().stream())
.collect(Collectors.toList());
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());
I have the following usecase. I have a nested map with following structure:
Map<String, Map<WorkType, List<CostLineItem>>>
I have to iterate over the map and get the list of CLObject. If the single entry in the list has identifier as null. I have to generate the unique identifier per EnumType. I am not sure how to do it with streams? Following iteration logic will make clear what i want to accomplish
for(Map.Entry<String, Map<WorkType, List<CostLineItem>>> cliByWorkTypeIterator: clisByWorkType.entrySet()) {
Map<WorkType, List<CostLineItem>> entryValue = cliByWorkTypeIterator.getValue();
for(Map.Entry<WorkType, List<CostLineItem>>cliListIterator : entryValue.entrySet()) {
List<CostLineItem> clis = cliListIterator.getValue();
//if any CLI settlementNumber is zero this means we are in standard upload
//TODO: Should we use documentType here? Revisit this check while doing dispute file upload
if(clis.get(0).getSettlementNumber() == null) {
clis.forEach(f -> f.toBuilder().settlementNumber(UUID.randomUUID().toString()).build());
}
}
}
Nested loop makes the code bit boiler plate and dirty. Can someone help me with streams here?
You can use flatMap to iterate over all the List<CostLineItem> values of all the inner Maps.
clisByWorkType.values() // returns Collection<Map<WorkType, List<CostLineItem>>>
.stream() // returns Stream<Map<WorkType, List<CostLineItem>>>
.flatMap(v->v.values().stream()) // returns Stream<List<CostLineItem>>
.filter(clis -> clis.get(0).getSettlementNumber() == null) // filters that Stream
.forEach(clis -> {do whatever logic you need to perform on the List<CostLineItem>});
The following is equivalent to your for-loop:
clisByWorkType.entrySet()
.map(Map.Entry::getValue) // cliByWorkTypeIterator.getValue();
.flatMap(m -> m.entrySet().stream())
.map(Map.Entry::getValue)
.map(CostLineItem::getValue)
.filter(clis.get(0).getSettlementNumber() == null) //filter before flattening
.flatMap(List::stream)
.forEach(f -> f.toBuilder().settlementNumber(UUID.randomUUID().toString()).build());
clisByWorkType.values()
.stream()
.flatMap(e -> e.values().stream())
.filter(clis -> clis.get(0).getSettlementNumber() == null)
.flatMap(Collection::stream)
.forEach(f -> f.toBuilder().settlementNumber(UUID.randomUUID().toString()).build());
i have a list of DTOs, these dtos contains a list of tags. I'm searching to find dtos that contain 2 tags each with its own key and value. this code will work - but it would only find first on the inner filters, i would like to collect instead of finding the first, in case there is more than one object with that criteria
List<myDTO> responsesList = getAllData(parameters);
List<myDTO> result = responsesList.stream()
.filter(d ->
d.getData().getTags().stream()
.filter(t -> t.getKey().equals(key1) && t.getValue().equals(value1))
.findFirst().isPresent())
.filter(d ->
d.getData().getTags().stream()
.filter(t -> t.getKey().equals(key2) && t.getValue().equals(value2))
.findFirst().isPresent())
.collect(Collectors.toList());
what am I missing to collect a collection instead of the findFirst().isPresent()? if I do Collect(collectors.toList) I get an error message like "inference variable T has incompatible bounds"?
It's not actually clear what do you want. If you need to collect all the myDTO objects which have both key1/value1 tag and key2/value2 tag, then your code already works. You can just shorten it as filter(predicate).findFirst().isPresent() could be replaced with anyMatch(predicate):
List<myDTO> result = responsesList.stream()
.filter(d ->
d.getData().getTags().stream()
.anyMatch(t -> t.getKey().equals(key1) && t.getValue().equals(value1)))
.filter(d ->
d.getData().getTags().stream()
.anyMatch(t -> t.getKey().equals(key2) && t.getValue().equals(value2)))
.collect(Collectors.toList());
You can also join both filters into single predicate, though this is a matter of taste:
List<myDTO> result = responsesList.stream()
.filter(d ->
d.getData().getTags().stream()
.anyMatch(t -> t.getKey().equals(key1) && t.getValue().equals(value1))
&&
d.getData().getTags().stream()
.anyMatch(t -> t.getKey().equals(key2) && t.getValue().equals(value2)))
.collect(Collectors.toList());
If you actually want to collect matching tags, you may need a flatMap:
List<myTag> result = responsesList.stream()
.flatMap(d -> d.getData().getTags().stream())
.filter(t -> t.getKey().equals(key1) && t.getValue().equals(value1) ||
t.getKey().equals(key2) && t.getValue().equals(value2))
.collect(Collectors.toList());