Comparing a string[] to a list of objects with stream? - java

I have a list of:
String[] properties = {"prop1","prop2","prop3"};
List<CustomObject> listOfObjects; // contains objects and each object contains a property of `properties` array
class CustomObject{
String prop1;
String prop2;
String prop3;
}
I want to check every properties value to every listOfObjects objects properties and check if the value is empty. I just want to check that none of the objects have empty properties.
I know there's stream() but I'm not sure how to use it correctly.
listOfObjects.stream().anyMatch(x -> x.get(/*What do i put in here?*/) == "");
Update: here's what would basically work without stream()
for(String prop: properties) {
for(CustomObject pdo: listOfObjects) {
if(pdo.get(prop) == "") {
System.out.println("Some of the required fields are empty");
}
}
}

Just for the sake of using Streams and anyMatch you can use this operation to check if any of the properties are blank. This is just to answer your question specifically, my suggestion is to take some time and improve this as there is a lot of room.
listOfObject.stream()
.anyMatch(obj -> Arrays.stream(obj)
.anyMatch(p -> !(obj.get(p)!= null && !obj.get(p).isEmpty())));

You can just use a list of Predicates. Each of the following predicates tests one of the properties for equality against "".
List<Predicate<CustomObject>> propertiesCheckers =
Arrays.asList(pdo -> "".equals(pdo.get("prop1")),
pdo -> "".equals(pdo.get("prop2")),
pdo -> "".equals(pdo.get("prop3")));
And this stream checks each element against the predicate, returning true when the first empty value is found:
boolean anyEmpty = listOfObjects.stream()
.flatMap(ob -> propertiesCheckers.stream().map(pred -> pred.test(ob)))
.anyMatch(b -> b); //returns true if any "".equals returned true
if(anyEmpty) System.out.println("Some of the required fields are empty");

Related

Filtering Object having a null property along with Objects with non-null property matching certain criteria without getting NPE

I have a list of objects List<Objs>.
Each object has a property objId of type String which can be initialized with a comma-separated string like "1,2,3".
And I have a string array sections containing comma-separated strings like "2,4".
I'm splitting those strings using split(',') into arrays and then performing comparison for each element (e.g. "1,2" -> {"1,","2"}).
I'm trying to collect all the objects which are either match StringUtils.isEmpty(object.getObjId) or their objId match one of the sections values.
That is the desired behavior depending on the value of objId:
Case:1 objId = "1,2,3" and sections = "2,4", as long as both have one value match "2", hence object should be included into the list.
Case:2 objId = "1,3" and sections = "2", since both don't have match value and hence object should not be included into the list.
Case:3 objId = null || "" and doesn't matter what sections = "{any}" (meaning the contents of sections doesn't matter), the object should be included into the list.
Case:4 sections = null || "", only object having objId = null || "" should be included into the list.
The sample code below always returns me a NullPointerException. Could someone help to correct me?
My code:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class TestStream {
private List<Objs> getData() {
List<Objs> objects = new ArrayList<>();
Objs objs1 = new Objs();
Objs objs2 = new Objs();
Objs objs3 = new Objs();
Objs objs4 = new Objs();
objs1.setObjId("1,2,3");
objects.add(objs1);
objs1.setObjId("4,5,6");
objects.add(objs2);
objs2.setObjId("7,84,3");
objects.add(objs3);
objs3.setObjId(null);
objects.add(objs4);
return objects;
}
private List<Objs> test () {
String[] sections = "2,8".split(",");
List<Objs> objects = getData();
List<Objs> result = objects.stream()
.filter(object -> object.getObjId() == null)
.filter(object -> Arrays.stream(sections).anyMatch(object.getObjId()::contains))
.skip(0)
.limit(2)
.collect(Collectors.toList());
return result;
}
public static void main(String[] args) {
TestStream testStream= new TestStream();
List<Objs> result = testStream.test();
System.out.println(Arrays.toString(result.toArray()));
}
class Objs {
private String objId;
public void setObjId(String id) { objId = id;}
public String getObjId() { return objId;}
}
}
The skip and limit is for paging, currently I set it to skip 0 and limit for each page is 3 which means I am trying to retrieve the first page, each page can only have 3 items.
NullPointerException is being produced in the nested stream created inside the second filter:
.filter(object -> Arrays.stream(sections).anyMatch(object.getObjId()::contains))
It occurs while attempting to invoke method contains on the property of your object retrieved by getObjId() and objId happens to be null.
Why getObjId() returns null ? There's an object in your sample data which has this field set to null and in the first filter you're specifically telling that you want to retain in the stream pipeline only those objects whose objId is null:
filter(object -> object.getObjId() == null)
You might have a misconception on how filter works, it will retain in the stream only those elements for which the predicate passed to the filter would be evaluated to true, not the opposite.
Here is a quote from the documentation:
Returns a stream consisting of the elements of this stream that match the given predicate.
You can to change the predicate in the first filter to object.getObjId() != null, but that can lead to incorrect results because Case 3 of your requirements will not be fulfilled.
Case:3 objId = null || "" and doesn't matter what sections = "{any}" (meaning the contents of sections doesn't matter), the object should be included into the list.
If filter out only objects having non-null id, then we will lose the objects with id null (if any), and empty string id also might not pass the criteria. Otherwise, if we would not apply any filtering before accessing id, it will result in getting NullPointerException.
Conclusion: to retain objects having id equal to null and to an empty string and also objects having id matching to one of the strings in the sections array, we can not use two separate filters.
Instead, we need to we need to combine these filters in a single filter.
To simplify the code, we can define two predicates. One as static field for checking if the id is null or an empty a string. Another as a local variable because it'll depend on the sections array.
That's how it might look like (the full code provided in the link below):
public static final Predicate<Objs> IS_NULL_OR_EMPTY =
object -> object.getObjId() == null || object.getObjId().isEmpty();
private List<Objs> test(String section) {
String[] sections = section.split(",");
List<Objs> objects = getData();
Predicate<Objs> hasMatchingSection =
object -> Arrays.stream(sections).anyMatch(object.getObjId()::contains);
return objects.stream()
.filter(IS_NULL_OR_EMPTY.or(hasMatchingSection))
.limit(2)
.collect(Collectors.toList());
}
I didn't apply skip() because it doesn't make sense skipping 0 elements.
A link to Online Demo

How to search multiple field in List using Java

I have a List of following objects:
public class OptionDetailResponse {
private long id;
private String flavor;
private String size;
private String status;
private String barcode;
}
I want to search in a List of those objects based on all 4 fields (except id):
flavor (input from a combobox)
size (input from a combobox)
status (input from a combobox)
barcode (input from a textfield)
This is my UI with the 4 input fields:
What I tried
I tried to use Predicate<OptionDetailResponse> for searching:
Predicate<OptionDetailResponse> selectFlavor = e -> e.getParentName().equals(flavor);
Predicate<OptionDetailResponse> selectSize = e -> e.getName().equals(size);
Predicate<OptionDetailResponse> selectStatus = e -> e.getStatus().equals(status);
Predicate<OptionDetailResponse> inputBarcode = e -> e.getBarcode().contains(barcode);
List<OptionDetailResponse> list = responseList.stream().filter(
selectFlavor.and(selectSize).and(selectStatus).and(inputBarcode))
.collect(Collectors.<OptionDetailResponse>toList());
But the list returned only a correct result when selected a value for in all search-fields.
Questions
How can I have all list when all field is empty using Predicate ?
Do have other ways to search by multiple fields ?
I think you can check on nullability or on specific value which shouldn't be checked inside each of your predicates depending on value you have in unselected field. I think it can look like this:
Predicate<OptionDetailResponse> selectFlavor = e -> flavor == null || e.getParentName().equals(flavor);
or
Predicate<OptionDetailResponse> selectFlavor = e -> flavor.equals("your unselected flavor value") || e.getParentName().equals(flavor);
.. and same for other predicates.
Bear in mind that when you use a Predicate in a filter method, the result will be the list of elements which match the "test" operation of the supplied predicate.
What you have done is to create a chain of Predicates in logical AND. This means that the result of the filter will be the list of the elements which match ALL given predicates.
if you need for a different result, you can create your chain by applying by using the specific Predicate functions, and thus realize eventually a more complex condition.
Other than and(Predicate<> target), for example you have the following method:
or(Predicate<> target): short-circuiting logical OR between two Predicate
negate(): logical negation of the current instance of Predicate
not(Predicate<> target): returns a predicate that is the negation of the supplied predicate
Probably you want to use following filter on each respective field:
if a search-parameter not supplied by UI (or left empty), then don't apply predicate: means predicate matches all regardless of object's field value
if a search-parameter is supplied by UI (or not empty), then apply the predicate
That said you could use a filter combined from all filled input-fields in stream:
// renamed your stream-result variable to indicate that it was filtered
List<OptionDetailResponse> filteredResult = responseList.stream()
.filter( buildPredicateFromInputFields(flavor, size, status, barcode) )
.collect(Collectors.toList());
where the predicate passed as argument to filter is combined from the 4 fields:
// you could name the method more specific: matchesAllNonEmptyInputs
Predicate<OptionDetailResponse> buildPredicateFromInputFields(
String flavor,
String size,
String status,
String barcode
) {
// build a set of field-matchers (predicate) based on given input fields
// null or empty (or blank) fields are excluded from the set
var givenFieldPredicates = new ArrayList<Predicate<OptionDetailResponse>>(4); // max 4 entries
if (flavor != null && !flavor.isBlank()) {
givenFieldPredicates.add(obj -> flavor.equals(obj.flavor))
}
if (size != null && !size.isBlank()) {
givenFieldPredicates.add(obj -> size.equals(obj.size))
}
if (status != null && !status.isBlank()) {
givenFieldPredicates.add(obj -> status.equals(obj.size))
}
// contained (partial match allowed)
if (barcode != null && !barcode.isBlank()) {
// will throw NullPointerException if object has null barcode!
givenFieldPredicates.add(obj -> obj.barcode.contains(barcode))
}
// combined them using AND: each field predicate must match
return givenFieldPredicates.stream().reduce(x -> true, Predicate::and);
}
See also:
Baeldung's Java Tutorial: Java 8 Predicate Chain, section "6. Combining a Collection of Predicates"
We could use Function, BiFunction and method reference and a pojo to hold the way to filter a field list to build something like
#Value
public static class Filter<T> {
private Function<OptionDetailResponse, T> getter;
private BiFunction<T, T, Boolean> filter;
public Predicate<OptionDetailResponse> toPredicate(OptionDetailResponse criteria) {
return o -> filter.apply(getter.apply(o), getter.apply(criteria));
}
}
public static List<Filter<?>> filters() {
List<Filter<?>> filterList = new ArrayList<>();
filterList.add(new Filter<>(OptionDetailResponse::getFlavor, Object::equals));
filterList.add(new Filter<>(OptionDetailResponse::getSize, Object::equals));
filterList.add(new Filter<>(OptionDetailResponse::getStatus, Object::equals));
filterList.add(new Filter<>(OptionDetailResponse::getBarcode, String::contains));
return filterList;
}
public static final List<Filter<?>> FILTERS = filters();
public Predicate<OptionDetailResponse> buildPredicate(OptionDetailResponse searchCriteria) {
return FILTERS
.stream()
.filter(f -> f.getGetter().apply(searchCriteria) != null)
.map(f -> f.toPredicate(searchCriteria))
.reduce(o -> true, Predicate::and);
}
public List<OptionDetailResponse> search(List<OptionDetailResponse> responseList,
OptionDetailResponse searchCriteria) {
return responseList.stream()
.filter(buildPredicate(searchCriteria))
.collect(Collectors.toList());
}

Set value to a string variable, from an object based on the condition using Java Streams

I am new to Java streams. I have a code snippet that i need to write using java streams. I am trying to set a value to a string based on a condition. I tried to look for solutions and experimented by using anyMatch, however could not get anywhere.
String loadAGENTID = "";
for(ReportGenerationParameter rgp : loadReportTableExt.getReportGenerationParameters()) {
if (rgp.getKey().equalsIgnoreCase(RapFilter.IDUSR)) {
loadAGENT_ID = rgp.getValue();
}
}
String loadAGENTID is to be used in the code. Any suggestion is welcome. Thank you.
I have tried using Arrays.stream and anyMatch but no luck so far
boolean todoName =
Arrays.stream(loadReportTableExt.getReportGenerationParameters())
.anyMatch(item -> item.getKey().equalsIgnoreCase(RapFilter.IDUSR));
if (todoName) {
// want to set the value of the respective object.
loadAGENT_ID = item.getValue();
}
Use the filter to find the matching object and then use findFirst which returns the first matching element
String loadAGENTID = loadReportTableExt.getReportGenerationParameters()
.stream()
.filter(rgp-> rgp.getKey().equalsIgnoreCase(RapFilter.IDUSR))
.findFirst()
.map(rgp->rgp.getValue()) // returns value from first matching element
.orElse(""); // none of them matched returns default value

Java 8: How to get particular value within object inside list without converting stream back to list and fetch value at 0th location?

I need to return value from href() inside a list of links where Rel value is String.
class Links{
String rel;
String href;
}
class 2{
List<Links> links
}
Following code is doing so but it is not looking cool
return links.stream().filter(d -> StringUtils.equalsIgnoreCase(d.getRel(), "Self")).collect(Collectors.toList()).get(0).getHref();
Is there any way to fetch getHref directly from list in place of converting it back to list and get 0th element.
Yes, use findFirst():
return links.stream()
.filter(d -> StringUtils.equalsIgnoreCase(d.getRel(), "Self"))
.findFirst() // returns an Optional<Links>
.map(Links::getHref) // returns an Optional<String>
.orElse(null); // returns String (either getHref of the found Links instance, or
// null if no instance passed the filter)

Check if something is equal to null or empty using lambdas

I have a class that counts the average number of words in a sentence using Lambdas in java. The problem that I'm having is that if corp is null or is empty I need to return 0. Currently I am getting NaN if corp is either null or empty. The rest of my code does what it should, but I cannot figure this part out.
public class AverageNumberOfWordsPerSentence extends TextMetric<Double> {
#Override
public Double apply(final Corpus corp) {
Sentences sentences = new Sentences();
List<String> sentenceList = sentences.apply(corp);
LongSummaryStatistics lss = corp.texts().stream()
.map(blob -> blob.text())
.flatMap(string -> stream
(string.split("\\W+")))
.filter(string -> !string.isEmpty())
.mapToLong(String::length)
.summaryStatistics();
return (double)lss.getCount() /
sentenceList.size();
}
Change the return statement to:
return sentenceList.isEmpty() ? 0.0 : (double)lss.getCount() / sentenceList.size();
And then hope that whoever told you “not to use control structures” will accept it. Strictly speaking, the ?: operator is a control structure, but it doesn’t have a keyword like if or while.
If I've got you right, then you need to use java.util.Optional:
class AverageNumberOfWordsPerSentence {
public Double apply(final Corpus corp) {
return Optional.of(corp).map(corp -> {
Sentences sentences = new Sentences();
List<String> sentenceList = sentences.apply(corp);
LongSummaryStatistics lss = corp.texts().stream()
.map(blob -> blob.text())
.flatMap(string -> stream
(string.split("\\W+")))
.filter(string -> !string.isEmpty())
.mapToLong(String::length)
.summaryStatistics();
return (double) lss.getCount() /
sentenceList.size();
}).orElse(0);
}
}
`
From the OP's comment,
Corpus corpus = new Corpus("King", text); So if the string where king is is empty or null then I have to return 0.
it appears that there needs to be some conditional logic that bypasses the stream if a member of Corpus is null or empty. The OP didn't say what the name of the property that holds "King" is, so I'll assume it is getKing() for now.
Like what #nikelin posted, Optional will help you here. Using Optional.filter() you can branch without using control structors. For example, you could do this to test to see if the "king" value is there and if it is null or an empty string, return 0, otherwise get the text metrics:
return Optional.of(corp)
.filter(c -> c.getKing() != null && !c.getKing().isEmpty()) // skip to the orElse() if it is null or empty)
.map(c -> c.texts()) // or .map(Corpus::texts)
.map(t -> t.stream()...blah blah get the word count...)
.map(count -> (double) count / sentences)
.orElse(0.0)
Any sequence of successive .map() operations can be combined into one, your choice.
If the initial Optional.filter finds that your "king" property is not null or empty, the stream operation the stream operation proceeds, getting the texts and calculating the word count as you specified already. It then maps the word count to sentenceCount/wordCount and returns that, but if your king property is null, the filter will leave the Optional empty, the map operations will be skipped, and the value in orElse(0.0) will be returned instead.

Categories