I'm trying to filter a list of objects say BOLReference based on a key which is present in the inner object of one of the List of Objects of this BOLReference.
List<BOLReference> bolRef = complianceMongoTemplate.find(findQuery, BOLReference.class);
Optional<BOLReference> bref = bolRef.stream().filter(br -> br.getWorkflowExceptions().stream()
.filter(bk -> bk.getBusinessKeyValues().get(businessKey)
.equalsIgnoreCase("ABCD1234"))).findFirst();
While doing so I'm getting error as :
Cannot convert from Stream<WorkFlowExceptions> to boolean
My BOLReference looks like below:
private String ediTransmissionId;
private List<WorkflowExceptions> workflowExceptions;
And my WorkflowExceptions looks like:
private String category;
private Map<String,String> businessKeyValues;
Optional<BOLReference> bref = bolRef.stream()
.filter(br ->
br.getWorkflowExceptions().stream()
.anyMatch(bk ->
bk.getBusinessKeyValues().get(businessKey)
.equalsIgnoreCase("ABCD1234")))
.findFirst();
Failing anyMatch I think.
A filter clause does not return a boolean but a stream. This causes a problem because your first filter expects a boolean result, but gets a stream from the second filter. You can use anyMatch instead to achieve the desired result.
You can make stream expressions more readable by formatting them on more lines and extracting complex logic into methods.
You could fix & refactor it like this:
String businessKey = null;
String targetValue = "ABCD1234";
Optional<BOLReference> bref = bolReferences.stream()
.filter(br -> br. hasBusinessKey(businessKey, targetValue))
.findFirst();
And:
public class BOLReference {
// ...
public boolean hasBusinessKey(String key, String value) {
return getWorkflowExceptions().stream()
.anyMatch(wfe -> wfe.hasBusinessKey(key, value));
}
}
And:
public class WorkflowExceptions {
// ...
public boolean hasBusinessKey(String key, String value) {
return getBusinessKeyValues().get(key).equalsIgnoreCase(value);
}
}
Related
I want to convert below for loop to Java 8. But having problem with filtering list of status and grouping into one status and total count.
I tried but for each "LIVE", "DRAFT", "TEST" have to loop 3 times and get 3 different maps. Is it possible to get into one loop using Java-8?
Where "LIVE", "DRAFT" and "TEST" are again combination of status from workflowInstance like DRAFT = {"DRAFT_EDIT","DRAFT_SAVE"}. I want to categorize all status into 3 based on this combination defined.
Map<String, Integer> summaryMap = new HashMap<>();
int l = 0, d = 0, t = 0;
for (WorkflowInstance instance : workflowInstances) {
if (liveStatuses.contains(instance.getStatus())) {
summaryMap.put("LIVE", l++);
} else if (testStatuses.contains(instance.getStatus())) {
summaryMap.put("TEST", t++);
} else if (draftStatuses.contains(instance.getStatus())) {
summaryMap.put("DRAFT", d++);
}
}
Java-8 individually for "LIVE", "DRAFT" and "TEST":
map.put("DRAFT", workflowInstances.stream()
.filter(inst-> Constants.DRAFT_STATUS.contains(inst.getStatus()))
.collect(Collectors.groupingBy(WorkflowInstance::getStatus, Collectors.counting()))
.entrySet().stream().mapToLong(e-> e.getValue()).sum()
);
map.put("LIVE", workflowInstances.stream()
.filter(inst-> Constants.LIVE_STATUS.contains(inst.getStatus()))
.collect(Collectors.groupingBy(WorkflowInstance::getStatus, Collectors.counting()))
.entrySet().stream().mapToLong(e-> e.getValue()).sum()
);
// Similar for "TEST"
Instead of looping 3 times I want to do in 1 go and categorize them.
Any help would be appreciated.
You cannot avoid extracting the type of the status anyway. Create a dedicated method for it (I suppose the list of statuses as liveStatuses etc. are either static or instance variables. Note that you have forgotten to handle the case no one of the predefined statuses match the current one. In that case, let's use "UNDEFINED":
String extractStatus(WorkflowInstance workflowInstance) {
String status = workflowInstance.getStatus();
if (liveStatuses.contains(status)) {
return "LIVE";
} else if (testStatuses.contains(status)) {
return "TEST";
} else if (draftStatuses.contains(status)) {
return "DRAFT";
}
return "UNCATEGORIZED"; // in case nothing is matched
}
Then the collecting is fairly easy using Collectors.groupingBy with a combination of Collectors.counting:
Map<String, Long> map = workflowInstances.stream()
.collect(Collectors.groupingBy( // groups to Map
this::extractStatus, // extracted status is the key
Collectors.counting())); // value is a number of occurences
Note the result is Map<String, Long> if you insist on Map<String, Integer> you need an additional downstream collector using Collectors.collectingAndThen:
Map<String, Integer> map = workflowInstances.stream()
.collect(Collectors.groupingBy( // groups to Map
Foo::extractStatus, // extracted status is the key
Collectors.collectingAndThen( // value is collected ...
Collectors.counting(), // ... a number of occurences
count -> new BigDecimal(count) // ... as Integer from Long
.intValueExact()))); // ... but might throw an exception
Using The ArithmeticException is thrown if the number is outside bounds. Remember that Long has the way bigger range than Integer. There are many different ways of the conversion of Long->Integer but they follow the same principle.
... or use a simple trick using Collectors.summingInt(e -> 1) instead of Collectors.counting as #HadiJ suggested. It returns Integer instead:
Map<String, Integer> map = workflowInstances.stream()
.collect(Collectors.groupingBy(this::extractStatus, Collectors.summingInt(e -> 1)));
I think all you need to do is to create a more complex grouping function that would transform getStatus into one of the three types you want. You could try something like this:
Map<String, Long> summaryMap = workflowInstances.stream()
.groupingBy(a -> {
if (liveStatuses.contains(a.getStatus())) {
return "LIVE";
} else if (testStatuses.contains(a.getStatus())) {
return "TEST";
} else if (draftStatuses.contains(a.getStatus())) {
return "DRAFT";
}
}, Collectors.counting());
Check this code:
My solution add a enum to handle multiple status in different types.
public static void main(String[] args) {
List<Instance> instances = new ArrayList<>();
instances.add(new Instance("LIVE"));
instances.add(new Instance("TEST"));
instances.add(new Instance("TEST"));
instances.add(new Instance("TEST"));
instances.add(new Instance("DRAFT"));
instances.add(new Instance("DRAFT"));
Map<String, Long> counts = instances.stream()
.collect(Collectors.groupingBy(a -> TemplateStatus.getStatus(a.getStatus()),Collectors.counting());
System.out.println(counts); //output: {DRAFT=2, TEST=3, LIVE=1}
}
class Instance {
public Instance(String status) {
this.status = status;
}
private String status;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}
enum TemplateStatus {
LIVE("LIVE,LIVE2"), DRAFT("DRAFT,DRAFT_2"), TEST("TEST_1,TEST");
private List<String> status;
TemplateStatus(String s) {
status = Arrays.asList(s.split(","));
}
public static String getStatus(String s) {
if (LIVE.status.contains(s)) return "LIVE";
else if (TEST.status.contains(s)) return "TEST";
else if (DRAFT.status.contains(s)) return "DRAFT";
return "UNKNOWN";
}
}
Hope this helps you
I'm having simple DataStructure
public class DataStructure {
private String key;
private String value;
//get, set
}
And I need to return value from `List' based on key and I want to do it Java8 way, with streams. I think code speaks for himself:
public class Main {
public static void main(String args[]) {
List<DataStructure> dataList = new ArrayList<>();
dataList.add(new DataStructure("first", "123"));
dataList.add(new DataStructure("second", "456"));
System.out.println(findValueOldSchool(dataList, "third")); //works ok
System.out.println(findValueStream(dataList, "third")); //throws NoSuchElementException
}
static String findValueOldSchool(List<DataStructure> list, String key) {
for (DataStructure ds : list) {
if (key.equals(ds.getKey())) {
return ds.getValue();
}
}
return null;
}
static String findValueStream(List<DataStructure> list, String key) {
return list.stream()
.filter(ds -> key.equals(ds.getKey()))
.findFirst()
.get().getValue();
}
}
How can I modify findValueStream() to not throw NoSuchValueException while I search for non existing key? I don't want to return Optional<String> because this method is already used in a lot of places in project. And, ofcourse I'v tried map, ifPresent, anyMatch, just can't find the right way to do it.
You shall use Stream.findFirst with an Optional.orElse like :
static String findValueStream(List<DataStructure> list, String key) {
return list.stream() // initial Stream<DataStructure>
.filter(ds -> key.equals(ds.getKey())) // filtered Stream<DataStructure>
.map(DataStructure::getValue) // mapped Stream<String>
.findFirst() // first Optional<String>
.orElse(null); // or else return 'null'
}
Note: The above uses the Stream.map to map the stream of DataStructure to a corresponding stream of value.
use orElse to return a default value if the Optional has an empty state:
This also means you'll need to map to DataStructure::getValue first like so:
return list.stream()
.filter(ds -> key.equals(ds.getKey()))
.findFirst()
.map(DataStructure::getValue)
.orElse(null);
just replacing get with orElse will not suffice:
return list.stream()
.filter(ds -> key.equals(ds.getKey()))
.findFirst()
.orElse(null)
.getValue();
As this time you'll get a NullPointerException instead of a NoSuchElementException in the case of an empty optional.
I have an Entity with multiple nested lists like this :
public class DataFile {
#Id
private int id;
private List<DataObject> datas = new ArrayList<>();
}
public class DataObject {
#Id
private String type;
private List<DataValue> values = new ArrayList<>();
}
public class DataValue {
#Id
private int id;
private String dataValue;
private LocalDateTime dataDate = LocalDateTime.now();
}
If I want a specific dataValue with DataObject.type = "speType" and DataValue.id = 2, I need this:
String value = dataFile.getDatas().forEach(t -> {
if(t.getType().equals("speType")){
t.getValues().forEach(v -> {
if(v.getId(2))
return v.getDataValue();
});
}
});
Is it possible to create a simple method ?
Thanks
For a pure "stream" solution :
dataFile.getDatas()
.stream()
.filter(t -> t.getType().equals("speType"))
.flatMap(t -> t.getValues().stream())
.filter(v -> v.getId()==2)
.map(DataValue::getDataValue)
.findFirst();
I assumed that by "if(v.getId(2))" you meant "if(v.getId()==2)", if not you can easily change the code above.
You could try something like that :
Optional<String> optValue =
dataFile.getDatas()
.stream()
.filter(t -> t.getType().equals("specType"))
.flatMap(t -> t.getValues().stream()
.filter(v -> v.getId() == 2)
.map(DataValue::getDataValue)
)
.findFirst();
Stream.findFirst() allows to exit of the processing early as soon as one element of it matches to the conditions. Similarly to what you did in your actual code.
Note that Stream.findFirst() returns an Optional.
Either unwrap the object after the terminal operation such as :
String value = optValue.orElse("default value");
Or do in this stream itself :
String value =
...
.findFirst();
.orElse("defaultValue")
I'm new to java and love the Stream API.
I got this loop:
List<FileTree> expanded = new ArrayList<>();
for(FileTree item : tree){
if(item.getType().equals("tree")){
expanded.addAll(getTreeOfSubStudPackage(item.getName()));
}
else{
expanded.add(item);
}
}
And I wonder if this could be converted to a neat stream. My first guess was the following:
tree.stream().map(fileTree -> {
if(fileTree.getType().equals("tree")){
return getTreeOfSubStudPackage(fileTree.getName());
}
else{
return fileTree;
}
}).collect(Collectors.toList());
It compiles fine. But is this recommended, have I even implemented a No-Go or is there a even nicer way?
And last but not least: Is there a overhead in .stream() that would make this improvement worthless?
Appendix
List<FileTree> getTreeOfSubStudPackage(...){
//...
}
class FileTree {
private String name;
private String type;
private String mode;
private String id;
//... Public Getter And Setter ...
}
you need to do map and then flatMap.
Arrays.asList(item) is to add the element to List to be flat in the next flatMap
tree.stream()
.map(item -> item.getType().equals("tree") ? getTreeOfSubStudPackage(item.getName()) : Arrays.asList(item))
.flatMap(Collection::stream)
.collect(Collectors.toList());
Given i want to filter a List of Key-Value objects.
My (Document)-Object from the example below looks like this
{
"attributeEntityList" : [
{key: 'key1', value: 'somevalue1'},
{key: 'key2', value: 'somevalue2'},
{key: 'key3', value: 'somevalue3'}
]
}
When I pass in a list of the following keys ["key1", "key2", "key3"], I expect my function to return the whole given List of attributes.
When I pass in a list of the following keys ["key1", "key2"], I expect my function to return a list of Attributes with the given key-names.
When I pass in a list of the following keys ["key1", "key2", "faultyKey"], I expect my function to return an Empty list.
My imperative-style solution looks like this and it works okay:
private List<AttributeEntity> getAttributeEntities(List<String> keys, Document value) {
final List<AttributeEntity> documentAttributeList = value.getAttributeEntityList();
final List<AttributeEntity> resultList = new ArrayList<>();
for(String configKey: keys){
boolean keyInAttribute = false;
for(AttributeEntity documentAttribute : documentAttributeList){
if(configKey.equals(documentAttribute.getAttribute_key())){
keyInAttribute = true;
resultList.add(documentAttribute);
break;
}
}
if(!keyInAttribute){
resultList.clear();
break;
}
}
return resultList;
}
For education and fun (and maybe better scaling) I'd like to know how to convert this piece of Code into a solution using the new Java 8 streaming-api.
This is what I came up with, converting my pre-Java8-code to Java8.
To my eyes it looks much more concise and it's shorter. But it does not, what I expect it to do :/
I'm realy struggling implementing the third bulletpoint of my requirements.
It always returns all (found) Attributes, even when i pass in a not existant key.
private List<AttributeEntity> getAttributeEntities(List<String> keys, Document value) {
final List<AttributeEntity> documentAttributeList = value.getAttributeList();
return documentAttributeList.stream()
.filter(attribute ->
keys.contains(attribute.getAttribute_key())
).collect(Collectors.toList());
}
I'm thinking of implementing my own custom Collector.
Since my Collector should only return the List, when the collected results contain each given key at least once.
Any other Idea on how to achieve that?
This solution passes my tests.
But it feel's like i'm putting the cart before the horse.
It's neither concise nor short or elegant any more.
private List<AttributeEntity> getAttributeEntities(List<String> keys, Document value) {
final List<AttributeEntity> documentAttributeList = value.getAttributeList();
return documentAttributeList.stream()
.filter(attribute ->
keys.contains(attribute.getAttribute_key())
)
.collect(Collectors.collectingAndThen(Collectors.toList(), new Function<List<AttributeEntity>, List<AttributeEntity>>() {
#Override
public List<AttributeEntity> apply(List<AttributeEntity> o) {
System.out.println("in finisher code");
if (keys.stream().allMatch(key -> {
return o.stream().filter(attrbiute -> attrbiute.getAttribute_key().equals(key)).findAny().isPresent();
})) {
return o;
} else {
return new ArrayList<AttributeEntity>();
}
}
}));
}
First of all I must say that I'm also new at Java 8 features, so I'm not familiar with everything, and not very used to functional programming. I tried a different approach, dividing it all into some methods.
Here it is:
public class Main {
private static List<AttributeEntity> documentAttributeList;
static {
documentAttributeList = new ArrayList<>();
documentAttributeList.add(new AttributeEntity("key1", "value1"));
documentAttributeList.add(new AttributeEntity("key2", "value2"));
documentAttributeList.add(new AttributeEntity("key3", "value3"));
}
public static void main(String[] args) {
Main main = new Main();
List<AttributeEntity> attributeEntities = main.getAttributeEntities(Arrays.asList("key1", "key2"));
for (AttributeEntity attributeEntity : attributeEntities) {
System.out.println(attributeEntity.getKey());
}
}
private List<AttributeEntity> getAttributeEntities(List<String> keys) {
if(hasInvalidKey(keys)){
return new ArrayList<>();
} else {
return documentAttributeList.stream().filter(attribute -> keys.contains(attribute.getKey())).collect(toList());
}
}
private boolean hasInvalidKey(List<String> keys) {
List<String> attributeKeys = getAttributeKeys();
return keys.stream().anyMatch(key -> !attributeKeys.contains(key));
}
private List<String> getAttributeKeys() {
return documentAttributeList.stream().map(attribute -> attribute.getKey()).collect(toList());
}
}
If a document can never have multiple attributes with the same name, I think you can do it like this (don't have a compiler handy to try):
Map<String, AttributeEntity> filteredMap=value.getAttributeEntityList().stream()
.filter(at->keys.contains(at.getKey()))
.collect(toMap(at->at.getKey(), at->at));
return filteredMap.keySet().containsAll(keys)
? new ArrayList<>(filteredMap.values())
: new ArrayList<>();
If multiple attributes per name are allowed, you would have to use groupingBy instead of toMap. You can, of course, rewrite this with collectingAndThen but I think it would be less clear.
I came up with something.
I don't know if it it the most elegant solution but at least it works and i can reason about it.
private List<AttributeEntity> getAttributeEntities(List<String> keys, Document value) {
final List<AttributeEntity> documentAttributeList = value.getAttributeList();
boolean allKeysPresentInAnyAttribute = keys.stream()
.allMatch(key ->
documentAttributeList.stream()
.filter(attrbiute ->
attrbiute.getAttribute_key().equals(key)
)
.findAny()
.isPresent()
);
if (allKeysPresentInAnyAttribute) {
return documentAttributeList.stream()
.filter(attribute ->
keys.contains(attribute.getAttribute_key())
)
.collect(Collectors.toList());
}
return new ArrayList<>();
}
Any hints or comments greatly appreciated.