Our objects have "properties"; and their current state is represented as Map<String, Object> where the key resembles the name of the property. The values can have different types, my current task is only dealing with Boolean properties though.
Beyond the current status, also "updates" to objects are organized via such maps.
Now I have to prevent that a property that is currently true gets disabled (turned to false).
Using streams, this here works:
Set<String> currentlyEnabled = currentObjectPropertiesMap.
.entrySet()
.stream()
.filter(e -> Boolean.TRUE.equals(e.getValue()))
.map(Entry::getKey)
.collect(Collectors.toSet());
Set<String> goingDisabled = updatedObjectPropertiesMap
.entrySet()
.stream()
.filter(e -> Boolean.FALSE.equals(e.getValue()))
.map(Entry::getKey)
.collect(Collectors.toSet());
currentlyEnabled.retainAll(goingDisabled);
if (currentlyEnabled.isEmpty()) {
return;
} else {
throw new SomeExceptionThatKnowsAllBadProperties(currentlyEnabled);
}
The above code first fetches a set of all properties that are true, then it separately collects all properties that will turn false. And if the intersection of these two sets is empty, I am fine, otherwise error.
The above works, but I find it clumsy, and I dislike the fact that the currentlyEnabled set is misused to compute the intersection.
Any suggestion how this can be done in a more idiomatic, but readable "stream-ish" way?
You can just select all key-value-pairs whose value is true, and then via the key, check if the value from the "update"-map is false.
Set<String> matches = currentObjectPropertiesMap
.entrySet()
.stream()
.filter(e -> Boolean.TRUE.equals(e.getValue()))
.map(Map.Entry::getKey)
.filter(k -> Boolean.FALSE.equals(
updatedObjectPropertiesMap.get(k)
))
.collect(Collectors.toSet());
if(!matches.isEmpty()) throw ...
One solution that does not include explicit set intersection could be:
Set<String> violatingProperties = new HashSet<String>();
for (Entry<String, Object> entry : currentObjectPropertiesMap.entrySet()) {
if (! (Boolean) entry.getValue()) {
continue;
}
if (! updatedObjectPropertiesMap.hasKey(entry.getKey())) {
continue;
}
if (! (Boolean) updatedObjectPropertiesMap.get(entry.getKey())) {
violatingProperties.add(entry.getKey());
}
}
if (violatingProperties.size() > 0) {
throw ...
}
Try anyMatch
boolean anyMatch = currentXXXMap.entrySet()
.stream()
.anyMatch(e -> e.getValue() && !updatedXXXMap.getOrDefault(e.getKey(), true));
Related
I'm trying to use Java stream to filter some values based on certain conditions. I am able to achieve the same using traditional for loops and a little bit of streams, but I want to rewrite the same logic fully in streams.
Original code:
public List <String> getProductNames(Hub hub, String requestedGroup) {
List <SupportedProduct> configuredProducts = repo.getSupportedProducts(hub);
List <String> productNames = new ArrayList <> ();
for (SupportedProduct supportedProduct: configuredProducts) {
List < String > categoryNameList = new ArrayList <> ();
String activeCategoryName = supportedProduct.getCategoryDetails().getActiveCategoryName();
if (activeCategoryName == null) {
Optional.ofNullable(supportedProduct.getCategoryDetails().getCategories())
.orElse(Collections.emptyList())
.forEach(category - > categoryNameList.add(category.getName()));
} else {
categoryNameList.add(activeCategoryName);
}
for (String catName: categoryNameList) {
Division division = divisionRepo.getDivisionByCatName(catName);
if (division != null && division.getGroup() == requestedGroup) {
productNames.add(supportedProduct.getProductName());
}
}
}
return productNames;
}
My try:
return Optional.ofNullable(configuredProducts).orElse(Collections.emptyList()).stream()
.map(supportedProduct -> {
List<String> categoryNameList = new ArrayList<>();
String activeCategoryName = supportedProduct.getCategoryDetails().getActiveCategoryName();
if (activeCategoryName == null) {
Optional.ofNullable(supportedProduct.getCategoryDetails().getCategories())
.orElse(Collections.emptyList())
.forEach(category -> categoryNameList.add(category.getName()));
} else {
categoryNameList.add(activeCategoryName);
}
return categoryNameList;
})
.filter(catName ->{
Division division = divisionRepo.getDivisionByCatName(catName);
return division != null && division.getGroup() == requestedGroup;
})........
But I'm lost beyond this.
Please help me to write the same using streams.
EDIT: Added IDEOne for testing - Link
The logic inside is quite complicated, however, try this out:
public List <String> getProductNames(Hub hub, String requestedGroup) {
List<SupportedProduct> configuredProducts = repo.getSupportedProducts(hub);
// extract pairs:
// key=SupportedProduct::getProductName
// values=List with one activeCategoryName OR names of all the categories
Map<String, List<String>> namedActiveCategoryNamesMap = configuredProducts.stream()
.collect(Collectors.toMap(
SupportedProduct::getProductName,
p -> Optional.ofNullable(p.getCategoryDetails().getActiveCategoryName())
.map(Collections::singletonList)
.orElse(Optional.ofNullable(p.getCategoryDetails().getCategories())
.stream()
.flatMap(Collection::stream)
.map(Category::getName)
.collect(Collectors.toList()))));
// look-up based on the categories' names, group equality comparison and returning a List
return namedActiveCategoryNamesMap.entrySet().stream()
.filter(entry -> entry.getValue().stream()
.map(catName -> divisionRepo.getDivisionByCatName(catName))
.filter(Objects::nonNull)
.map(Division::getGroup)
.anyMatch(requestedGroup::equals))
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
I recommend splitting into separate methods for sake of readability (the best way to go).
The verbose logics of Optional chains including two orElse calls can be surely simplified, however, it gives you the idea.
You can perform within one Stream using Collectors.collectingAndThen. In that case, I'd extract the Function finisher elsewhere, example:
public List<String> getProductNames(Hub hub, String requestedGroup) {
return repo.getSupportedProducts(hub).stream()
.collect(Collectors.collectingAndThen(
Collectors.toMap(
SupportedProduct::getProductName,
categoryNamesFunction()),
productNamesFunction(requestedGroup)));
}
private Function<Map<String, List<String>>, List<String>> productNamesFunction(String requestedGroup) {
return map -> map.entrySet().stream()
.filter(entry -> entry.getValue().stream()
.map(divisionRepo::getDivisionByCatName)
.filter(Objects::nonNull)
.map(Division::getGroup)
.anyMatch(requestedGroup::equals))
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
private Function<SupportedProduct, List<String>> categoryNamesFunction() {
return p -> Optional.ofNullable(p.getCategoryDetails().getActiveCategoryName())
.map(Collections::singletonList)
.orElse(Optional.ofNullable(p.getCategoryDetails().getCategories())
.stream()
.flatMap(Collection::stream)
.map(Category::getName)
.collect(Collectors.toList()));
}
Function should return optional of most frequent last name (if it encountered at least two times) or optional empty if number of last names is the same or list of users is empty
This is what i came up with, but it doesnt return Optional.empty
#Override
public Optional<String> getMostFrequentLastName(final List<User> users) {
return users.stream()
.map(User::getLastName)
.distinct()
.collect
(Collectors.groupingBy(
Function.identity(),
Collectors.summingInt(w -> 1)
))
.entrySet()
.stream()
.filter(stringIntegerEntry -> stringIntegerEntry.getValue() >= 2)
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.map(Map.Entry::getKey)
.findFirst();
}
This is my test class
public static void main(String[] args) {
Optional<String> optionalS = Stream.of(new User("name1"),
new User("name1"), new User("name2"), new User("name2"))
.map(User::getLastName)
.collect
(Collectors.groupingBy(
Function.identity(),
Collectors.counting()
))
.entrySet()
.stream()
.filter(stringIntegerEntry -> stringIntegerEntry.getValue() >= 2)
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.map(Map.Entry::getKey)
.findFirst();
System.out.println(optionalS.toString());
}
Here is the awnser
Optional[name2]
But should be
Optional[empty]
You may use
Optional<String> optionalS =
Stream.of(new User("name1"), new User("name1"), new User("name2"), new User("name2"))
.collect(Collectors.groupingBy(User::getLastName, Collectors.counting()))
.entrySet()
.stream()
.filter(entry -> entry.getValue() >= 2)
.reduce((e1, e2) -> e1.getValue() < e2.getValue()? e2:
e1.getValue() > e2.getValue()? e1:
new AbstractMap.SimpleImmutableEntry<>(null, e1.getValue()))
.map(Map.Entry::getKey);
System.out.println(optionalS.toString());
Getting the maximum value is a form of Reduction. Since you want to get an empty optional in case of a tie, the simplest solution is to write the reduction function explicitly, use the Map.Entry with the bigger value if there is one, otherwise construct a new Map.Entry with a null key.
The result of the reduction is already an Optional, which will be empty if there were no elements (with a count >=2). So the last map step is applied on an Optional. If already empty, the map function won’t be evaluated and the resulting Optional stays empty. If the optional is not empty, but Map.Entry::getKey evaluates to null, the resulting optional will be empty.
It seems to me that if you have the same number of maximum of some different lastNames you want to return an Optional::empty, as such:
Map<String, Long> map =
Stream.of(new User("name1"),
new User("name1"),
new User("name2"),
new User("name2"))
.collect(Collectors.groupingBy(User::getLastName, Collectors.counting()));
map.entrySet()
.stream()
.max(Entry.comparingByValue())
.flatMap(en -> {
boolean b = map.entrySet()
.stream()
.filter(x -> !x.getKey().equals(en.getKey()))
.mapToLong(Entry::getValue)
.noneMatch(x -> x == en.getValue());
return b ? Optional.of(en.getKey()) : Optional.empty();
})
.ifPresent(System.out::println);
}
Here my monster for you:
Optional<String> optionalS = Stream.of(
new User("name1"),
new User("name1"),
new User("name2"),
new User("name2"))
.map(User::getLastName)
.collect(
Collectors.groupingBy(
Function.identity(),
Collectors.counting()
))
.entrySet()
.stream()
.filter(stringIntegerEntry -> stringIntegerEntry.getValue() >= 2)
.collect(
Collectors.groupingBy(
Map.Entry::getValue,
Collectors.toList()
))
.entrySet()
.stream()
.sorted(Comparator.comparing(
Map.Entry::getKey,
Comparator.reverseOrder()))
.map(Map.Entry::getValue)
.findFirst()
.filter(x -> x.size() == 1)
.map(x -> x.get(0).getKey());
System.out.println(optionalS);
As far as I undestand your solution in stream you code creates
Map<String(lastname),Integer(number of occurence)>
and then filter that map where number of occurence >=2 and in your test case you have map with entries:
<"name1",2>
<"name2",2>
So ordering by value will still return two values.
You should try create
Map<Integer,List<String>>
which will store number of occurence -> names, then filter map keys, sort them descending and (in map value) you will get most frequently lastname (or lastnames if there were more than once in input).
//edit
Below short snippet with my solution:
Map<Integer, List<String>> map = new HashMap<>();
map.put(2,Arrays.asList("name1","name2"));
Optional<String> optionalS = map
.entrySet()
.stream()
.sorted(Map.Entry.comparingByKey(Comparator.reverseOrder()))
.findFirst() //get max{map's keys}
.filter(x->x.getValue().size() == 1) //get lastname that occured only once
.map(x->x.getValue().get(0)); //get that lastname (above filter check that list has only one element) or Optional.empty if stream didn't find any
System.out.println(optionalS.toString());
I skipped the part of creating map.
P.S. You can replace HashMap with TreeMap with custom comparator to avoid sorting in stream.
I'm making a service call and trying to handle response.
Response might have a list of something. That list might be null.
Moreover, if list not null or not empty, then
it needs to be filtered.
In the code "entry" reference might be null if filtering gives nothing or response list is empty or null.
Currently i'm getting NPE when i try to use stream() on a null response list.
How can i handle this situation?
#Getter
public class ServiceResponse {
List<ResponseEntry> entryList;
}
#Getter
public class ResponseEntry {
String value;
}
ServiceResponse serviceResponse = service.getServiceResponse();
ResponseEntry entry = serviceResponse.getEntryList()
.stream()
.filter(e -> "expectedValue".equals(e.getValue()))
.findFirst()
.orElse(null);
if (entry == null) { ... }
if list not null or not empty, then it needs to be filtered.
No need for Optional here, as it's not intended to replace simple if checks.
ResponseEntry entry = null;
List<ResponseEntry> responseEntries = serviceResponse.getEntryList();
if(responseEntries != null && !responseEntries.isEmpty()){
entry = responseEntries.stream()
.filter(e -> "expectedValue".equals(e.getValue()))
.findFirst()
.orElse(null);
}
reads "if responseEntries is not null and responseEntries is not empty then apply the filter operation and find the first item or else null". Very readable.
On the other hand, the optional approach:
ResponseEntry entry = Optional.ofNullable(serviceResponse.getEntryList())
.orElseGet(() -> Collections.emptyList())
.stream()
.filter(e -> "expectedValue".equals(e.getValue()))
.findFirst();
if(!entry.isPresent()){ ... } // or entry.ifPresent(e -> ...) depending on the logic you're performing inside the block
unnecessarily creates objects that could be avoided and not really the intention of optional to be used as a substitute for simple "if" checks.
Stream.ofNullable (Java-9)
Returns a sequential Stream containing a single element, if non-null,
otherwise returns an empty Stream.
Current Code
ResponseEntry entry = serviceResponse.getEntryList() // List<ResponseEntry>
.stream() // NPE here // Stream<ResponseEntry>
.filter(e -> "expectedValue".equals(e.getValue())) // filter
.findFirst() // Optional<ResponseEntry>
.orElse(null); // or else null
Updated Code
ResponseEntry entry = Stream.ofNullable(serviceResponse.getEntryList()) // Stream<List<ResponseEntry>>
.flatMap(List::stream) // Stream<ResponseEntry>
.filter(e -> "expectedValue".equals(e.getValue())) // filter here
.findFirst() // Optional<ResponseEntry>
.orElse(null); // or else null
Optional.stream (Java-9)
returns a sequential Stream containing only that value, otherwise
returns an empty Stream.
ResponseEntry entry = Optional.ofNullable(serviceResponse.getEntryList())
.stream() // Stream<List<ResponseEntry>>
.flatMap(List::stream) // Stream<ResponseEntry>
.filter(e -> "expectedValue".equals(e.getValue())) // filter here
.findFirst() // Optional<ResponseEntry>
.orElse(null); // or else null
Optional.isEmpty(Java-11)
If a value is not present, returns true, otherwise false
Optional<ResponseEntry> entry = Optional.ofNullable(serviceResponse.getEntryList()) // Optional<List<ResponseEntry>>
.orElseGet(Collections::emptyList) // or else empty List
.stream() // Stream<ResponseEntry>
.filter(e -> "expectedValue".equals(e.getValue())) // filter
.findFirst(); // Optional<ResponseEntry>
if (entry.isEmpty()) { // !entry.isPresent in java-8
// Do your work here
}
In Java 9, you could use the new method Objects.requireNonNullElse(T,T):
Objects.requireNonNullElse(serviceResponse.getEntryList(),
Collections.emptyList())
Apache Commons Collections actually has a method ListUtils.emptyIfNull(List<T>) which returns an empty list if the argument list is null. That's even better, but Objects.requireNonNullElse is the closest thing to it in Java SE.
If you're restricted to just Java 8, then I agree with Aomine's answer that trying to do something like go through Optional is worse than an if statement.
You could simply use the ternary operator:
ServiceResponse serviceResponse = service.getServiceResponse();
List<ResponseEntry> list = serviceResponse.getEntryList();
ResponseEntry entry = (list == null ? Collections.emptyList() : list)
.stream()
.filter(e -> "expectedValue".equals(e.getValue()))
.findFirst()
.orElse(null);
if (entry == null) { ... }
Sometimes, traditional is better IMO.
Another option would be to use the Optional monad:
Optional<ResponseEntry> entry = Optional.ofNullable(serviceResponse.getEntryList()).flatMap(list ->
list.stream().filter(e -> "expectedValue".equals(e.getValue())).findFirst()
);
if (!entry.isPresent()) {
…
}
You might even use orElseGet instead of that if statement if your objective is to build (and return) a value, instead of executing a side effect.
I am new to Optional and I may be wrong. Logic can be written like below if you want to have logic including only optional.
ServiceResponse serviceResponse = service.getServiceResponse();
ResponseEntry entry =
Optional.of(CollectionUtils.isNotEmpty(serviceResponse.getEntryList()))
.filter(BooleanUtils::isTrue)
.stream()
.filter(e -> "expectedValue".equals(e.getValue()))
.findFirst()
.orElse(null);
I'm searching a solution for this problem(it is for an exam):
I have a Map < String, SortedSet < String > > operators populated by a function
public void addOperator(String operatorName, String... destinationNames) throws ProposalException {
if(operators.containsKey((operatorName))){
throw new ProposalException("Operator " + operatorName + "already into system!");
}
else{
SortedSet<String> destinationstemp=new TreeSet<>();
for(String s: destinationNames){
if(s!=null){
destinationstemp.add(s);
}
}
operators.put(operatorName, destinationstemp);
}
Now, i want to create a new Map < String, SortedSet < String > > destinations that has as key the destinationName and as values the operatorNames related.
How can i make this out?
P.S: this one up there is the usage of the methods and the not-in-code part is the output wanted. Sorry for the bad formattation of the code. ph is the instance of the façade pattern class
public SortedSet<String> getDestOperators(String destinationName) {...}//method that returns the **destinations** values related to destinationName}
ph.addOperator("op3","london","rome");
ph.addOperator("op2","london","berlin");
ph.addOperator("op5","berlin","rome","madrid");
ph.addOperator("op1","london","madrid","berlin");
ph.addOperator("op10","rome");
ph.addOperator("op4","madrid","berlin");
System.out.println(ph.getDestOperators("madrid"));
Output: [op1, op4, op5]
you need to go through each entry in your map and check if inner set contains the value you are checking against,
public SortedSet<String> getDestOperators(String destinationName) {
Set<String> result = new HashSet<String>();
for(Map.Entry<String,Set<String>> entry : operators.getValues()){
if(entry.getValue().contains(destinationName)){
results.add(entry.getKey());
}
}
return result;
}
To get your example output a simple one-liner with streams:
List<String> result = operators.entrySet().stream().filter(entry -> entry.getValue().contains(destinationName)).map(Entry::getKey).sorted().collect(Collectors.toList());
or here for better readability spread over multiple lines:
List<String> result = operators
.entrySet()
.stream()
.filter(entry -> entry.getValue().contains(destinationName))
.map(Entry::getKey)
.sorted()
.collect(Collectors.toList());
A more complex one-liner if you want to "reverse" the mapping as described in your text:
Map<String, List<String>> result = operators.entrySet().stream().flatMap(entry -> entry.getValue().stream().collect(Collectors.toMap(Function.identity(), o -> Arrays.asList(entry.getKey()))).entrySet().stream()).collect(Collectors.toMap(Entry::getKey, Entry::getValue, (a, b) -> Stream.of(a, b).flatMap(List::stream).sorted().collect(Collectors.toList())));
or here for better readability spread over multiple lines:
Map<String, List<String>> result2 = operators
.entrySet()
.stream()
.flatMap(entry -> entry
.getValue()
.stream()
.collect(Collectors.toMap(Function.identity(),
o -> Arrays.asList(entry.getKey())))
.entrySet()
.stream())
.collect(Collectors.toMap(Entry::getKey,
Entry::getValue,
(a, b) -> Stream.of(a, b)
.flatMap(List::stream)
.sorted()
.collect(Collectors.toList())));
What you need to do, is loop over each operator, and then loop over all entries in the list, if value from the list is not yet present in your output map, you add it, else you modify its colection of operators.
Here is some code for you:
origin.forEach((key, list) -> {list.forEach(city -> {
if(result.containsKey(city))
result.get(city).add(key);
else{
SortedSet<String> set = new TreeSet<>();
set.add(key);
result.put(city, set);
});
});
Imagine that I have the following working lambda expression:
Map<Field, String> fields = Arrays.stream(resultClass.getDeclaredFields())
.filter(f -> f.isAnnotationPresent(Column.class))
.collect(toMap(f -> {
f.setAccessible(true);
return f;
}, f -> f.getAnnotation(Column.class).name()));
I would like to create a stream with 2 values before the filter statement. So I want to do a mapping but still keep the original value aside from it. I want to achieve something like this:
this.fields = Arrays.stream(resultClass.getDeclaredFields())
//map to <Field, Annotation> stream
.filter((f, a) -> a != null)
.collect(toMap(f -> {
f.setAccessible(true);
return f;
}, f -> a.name()));
Is this possible with Java 8 streams? I have looked at collect(groupingBy()) but still without succes.
You need something like a Pair that holds two values. You can write your own, but here is some code that repurposes AbstractMap.SimpleEntry:
Map<Field, String> fields = Arrays.stream(resultClass.getDeclaredFields())
.map(f -> new AbstractMap.SimpleEntry<>(f, f.getAnnotation(Column.class)))
.filter(entry -> entry.getValue() != null)
.peek(entry -> entry.getKey().setAccessible(true))
.collect(toMap(Map.Entry::getKey, entry -> entry.getValue().name()));
You can do the entire operation in one go during the collect operation without the need of a pair type:
Map<Field, String> fields = Arrays.stream(resultClass.getDeclaredFields())
.collect(HashMap::new, (m,f) -> {
Column c=f.getAnnotation(Column.class);
if(c!=null) {
f.setAccessible(true);
m.put(f, c.name());
}
}, Map::putAll);
Still, to me it looks cleaner to separate the two operations which do not becong together:
Map<Field, String> fields = Arrays.stream(resultClass.getDeclaredFields())
.collect(HashMap::new, (m,f) -> {
Column c=f.getAnnotation(Column.class);
if(c!=null) m.put(f,c.name());
}, Map::putAll);
AccessibleObject.setAccessible(
fields.keySet().stream().toArray(AccessibleObject[]::new), true);
This solution does iterate twice over the fields having the annotation, but since this performs only one security check rather than one check per field, it might still outperform all other solutions.
Generally, you shouldn’t try to optimize unless there really is a performance problem and if you do it, you should measure, not guess about the costs of the operations. The results might be surprising and iterating multiple times over a data set is not necessarily bad.
#Peter Lawrey: I tried your suggestion with an intermediary map. It works now but it is not really pretty.
this.fields = Arrays.stream(resultClass.getDeclaredFields())
.collect(HashMap<Field, Column>::new, (map, f) -> map.put(f, f.getAnnotation(Column.class)), HashMap::putAll)
.entrySet().stream()
.filter(entry -> entry.getValue() != null)
.peek(entry -> entry.getKey().setAccessible(true))
.collect(toMap(Map.Entry::getKey, entry -> entry.getValue().name()));