I am pretty new to java8 streams. I was trying to work on collection of objects using stream. But not able to achieve in precise way.
Below is the snippet which I achieved (which is giving wrong result). expected end result is List<String> of "Names email#test.com".
recordObjects is collection of object
choices = recordObjects.stream()
.filter(record -> record.getAttribute
(OneRecord.AT_RECORD_SUBMITTER_TABLE_EMAIL) != null)
.filter(record -> !record.getAttributeAsString
(OneRecord.AT_RECORD_SUBMITTER_TABLE_EMAIL).isEmpty())
.map(record -> record.getMultiValuedAttribute
(OneRecord.AT_RECORD_SUBMITTER_TABLE_EMAIL, String.class))
.flatMap(Collection::stream)
.map(email -> getFormattedEmailAddress(ATTRI_AND_RECORD_CONTACT_DEFAULT_NAME, email))
.collect(Collectors.toList());
but below is the exact logic i want to implement using streams.
for (CallerObject record : recordObjects) {
List<String> emails = record.getMultiValuedAttribute(
OneRecord.AT_RECORD_SUBMITTER_TABLE_EMAIL, String.class);
List<String> names = record.getMultiValuedAttribute(
OneRecord.AT_RECORD_SUBMITTER_TABLE_NAME, String.class);
int N = emails.size();
for (int i = 0 ; i < N ; i++) {
if(!isNullOrEmpty(emails.get(i)))
{
choices.add(getFormattedEmailAddress(isNullOrEmpty(names.get(i)) ?
ATTRI_AND_RECORD_CONTACT_DEFAULT_NAME : names.get(i) , emails.get(i)));
}
}
}
Since we don't know the getFormattedEmailAddress method, I used String.format instead to achieve the desired representation "Names email#test.com":
// the mapper function: using String.format
Function<RecordObject, String> toEmailString = r -> {
String email = record.getMultiValuedAttribute(OneRecord.AT_RECORD_SUBMITTER_TABLE_EMAIL, String.class);
String name = record.getMultiValuedAttribute(OneRecord.AT_RECORD_SUBMITTER_TABLE_NAME, String.class);
if (email != null) {
return String.format("%s %s", name, email);
} else {
return null;
}
};
choices = recordObjects.stream()
.map(toEmailString) // map to email-format or null
.filter(Objects::nonNull) // exclude null strings where no email was found
.collect(Collectors.toList());
Changed your older version code to Java 8
final Function<RecordedObject, List<String>> filteredEmail = ro -> {
final List<String> emails = ro.getMultiValuedAttribute(
OneRecord.AT_RECORD_SUBMITTER_TABLE_EMAIL, String.class);
final List<String> names = ro.getMultiValuedAttribute(
OneRecord.AT_RECORD_SUBMITTER_TABLE_NAME, String.class);
return IntStream.range(0, emails.size())
.filter(index -> !isNullOrEmpty(emails.get(index)))
.map(index -> getFormattedEmailAddress(isNullOrEmpty(names.get(index)) ?
ATTRI_AND_RECORD_CONTACT_DEFAULT_NAME : names.get(index) , emails.get(index)))
.collect(Collectors.toList());
};
recordObjects
.stream()
.map(filteredEmail)
.flatMap(Collection::stream)
.collect(Collectors.toList());
My code is currently working using the query below and I am converting the query to JPA Specification.
#Query("SELECT DISTINCT h, SUM(m.annualIncome) " +
"FROM Household h LEFT JOIN h.familyMemberList m " +
"GROUP BY h.id " +
"HAVING SUM(m.annualIncome) < 100000 " +
"AND (:householdSize IS NULL OR COUNT(m) = :householdSize) " +
"AND (:householdIncome IS NULL OR SUM(m.annualIncome) = :householdIncome)")
List<Household> findGrantEligibleHouseholds(#Param("householdSize") long householdSize, #Param("householdIncome") long householdIncome);
This is what I have done so far which is working but in an unclean manner.
public static Specification<Household> grantEligibleHouseholdsSpecification(HouseholdCriteria criteria) {
return Specification.where(
(root, query, builder) -> {
List<Predicate> searchCriteria = new ArrayList<>();
final Join<Household, FamilyMember> householdFamilyMemberJoin = root.join(Household_.familyMemberList, JoinType.LEFT);
if(criteria.getHousingType() != null) {
searchCriteria.add(builder.equal(root.get(Household_.housingType), criteria.getHousingType()));
}
query.groupBy(root.get(Household_.id));
if(criteria.getHouseholdIncome() != null && criteria.getHouseholdSize() != null) {
query.having(builder.lt(builder.sum(householdFamilyMemberJoin.get(FamilyMember_.annualIncome)),100000)
,builder.equal(builder.count(householdFamilyMemberJoin),criteria.getHouseholdSize())
,builder.equal(builder.sum(householdFamilyMemberJoin.get(FamilyMember_.annualIncome)),criteria.getHouseholdIncome()));
}
else if(criteria.getHouseholdIncome() != null) {
query.having(builder.lt(builder.sum(householdFamilyMemberJoin.get(FamilyMember_.annualIncome)),100000)
,builder.equal(builder.sum(householdFamilyMemberJoin.get(FamilyMember_.annualIncome)),criteria.getHouseholdIncome() ));
}
else if(criteria.getHouseholdSize() != null) {
query.having(builder.lt(builder.sum(householdFamilyMemberJoin.get(FamilyMember_.annualIncome)),100000)
,builder.equal(builder.count(householdFamilyMemberJoin),criteria.getHouseholdSize()));
}
else {
query.having(builder.lt(builder.sum(householdFamilyMemberJoin.get(FamilyMember_.annualIncome)),100000));
}
query.multiselect();
return builder.and(searchCriteria.toArray(new Predicate[searchCriteria.size()]));
}
);
}
How do I improve this code so in the future it can accept more criteria without going through so many null checks like this? Thanks!
You can use same approach as for searchCriteria - collect multiple predicates into list:
final List<Predicate> havingPredicates = new ArrayList<>();
// default predicates
havingPredicates.add(builder.lt(
builder.sum(householdFamilyMemberJoin.get(FamilyMember_.annualIncome)),
100000));
// custom predicates
if (criteria.getHouseholdIncome() != null) {
havingPredicates.add(builder.equal(
builder.sum(householdFamilyMemberJoin.get(FamilyMember_.annualIncome)),
criteria.getHouseholdIncome()));
}
if (criteria.getHouseholdSize() != null) {
havingPredicates.add(builder.equal(
builder.count(householdFamilyMemberJoin),
criteria.getHouseholdSize()));
}
query.having(havingPredicates.toArray(new Predicate[0]));
Is there a way to make the code below simpler and shorter?
boolean clicked = false;
for (WebElement anchor : anchorLinksForProducts) {
if (anchor.getAttribute("href").contains(id)) {
anchor.click();
clicked = true;
break;
}
}
Assert.assertTrue(clicked, "Could not find a valid product link with id : " + id);
A refinement to #Mureinik's answer.
anchorLinksForProducts.stream()
.filter(a -> a.getAttribute("href").contains(id))
.findFirst()
.ifPresentOrElse(
WebElement::click,
() -> fail("Could not find a valid product link with id : " + id)
);
There's a little more overhead in this code, but that is always the case with streams and not a problem if it is test code.
A stream should make the code shorter:
Optional<WebElement> anchor =
anchorLinksForProducts.stream()
.filter(a -> a.getAttribute("href").contains(id))
.findFirst();
if (anchor.isPresent()) {
anchor.get().click();
} else {
fail("Could not find a valid product link with id : " + id);
}
I've been trying to get an Observable to return back a Map<String,JSONObject>, but it fails to do so. The print statements show the correct return value, but not sure why this is returning an incomplete value.
#Override
#Retryable(value = {CouchbaseException.class})
public Mono<Map<String, JsonObject>> query(String query, List<String> params) {
N1qlParams n1qlParams = N1qlParams.build()
.adhoc(true);
Map<String, JsonObject> resultMap = new HashMap();
N1qlQuery n1qlQuery = N1qlQuery.parameterized(query, JsonArray.from(params), n1qlParams);
Observable<AsyncN1qlQueryResult> queryResult = bucket.async().query(n1qlQuery)
.timeout(5000, TimeUnit.MILLISECONDS)
.retryWhen(anyOf(BackpressureException.class).max(3)
.delay(Delay.exponential(TimeUnit.MILLISECONDS, 32, 2)).build())
.retryWhen(anyOf(TemporaryFailureException.class)
.max(2)
.delay(Delay.fixed(500, TimeUnit.MILLISECONDS)).build())
.onErrorResumeNext(exp -> {
return Observable.error(exp);
});
Observable<Map<String, JsonObject>> metricsMap = queryResult.flatMap(qr ->
qr.info()).map(n1qlMetrics -> {
resultMap.put("metrics", n1qlMetrics.asJsonObject());
log.info("METRICS: " + resultMap.toString());
return resultMap;
});
Observable<Map<String, JsonObject>> resultObsMap = queryResult.flatMap(asyncN1qlQueryResult ->
asyncN1qlQueryResult.errors().flatMap(error -> {
return Observable
.error(new Exception("unable to execute n1ql query " + error.toString()));
})
.switchIfEmpty(asyncN1qlQueryResult.rows())
).map(row -> {
JsonObject json = ((AsyncN1qlQueryRow) row).value();
return json;
}).map(results -> {
resultMap.put("results", results);
log.info("RESULTS: " + resultMap.toString());
return resultMap;
});
// log.info("resultObsMap: " + resultObsMap..toString());
//
Observable<Map<String, JsonObject>> retObservable = Observable
.zip(resultObsMap, metricsMap, (r, m) -> {
r.putAll(m);
return r;
});
//retObservable.subscribe(System.out::println);
return Mono.from(RxReactiveStreams.toPublisher(retObservable));
}
The 2 print statements print the below CORRECT values:
2020-07-30 09:41:19.402 INFO [cb-computations-4] com.aexp.eimp.dbtestsuite.couchbase.service.impl.CouchbaseAsyncServiceImpl - METRICS: {metrics={"executionTime":"780.964µs","resultCount":2,"resultSize":2858,"elapsedTime":"841.098µs"}}
2020-07-30 09:41:19.406 INFO [cb-computations-3]
com.aexp.eimp.dbtestsuite.couchbase.service.impl.CouchbaseAsyncServiceImpl - RESULTS: {metrics={"executionTime":"780.964µs","resultCount":2,"resultSize":2858,"elapsedTime":"841.098µs"}, results={"bucket_admin":{"name":"${__RandomString(20,abcdefghijklmnopqrstuvwxyz,)}","notificationEnrollment":{"insertTS":1503530927070,"id":"0000255BAIHSDCY","type":"ENCRYPTEDACCOUNT","issuer":"001","domain":"","apps":{"app":{"enrollmentInfoList":[{"features":[{"featureUserIdentifier":{"id":"BAIHSDCY0000255","type":"ENCRYPTEDACCOUNT","issuer":"001","domain":"CARD"},"featureInfoList":[{"featureName":"PAYMENT_RECEIVED_ALERT","attributes":[{"value":"8","key":"daysBeforeDue"}],"featurePreferenceValue":true},{"featureName":"PAYMENT_REMINDER_ALERT","attributes":[{"value":"8","key":"daysBeforeDue"}],"featurePreferenceValue":true},{"featureName":"STMT_NOTIFICATION_ALERT","attributes":[{"value":"8","key":"daysBeforeDue"}],"featurePreferenceValue":true},{"featureName":"OFFERS","attributes":[{"value":"8","key":"daysBeforeDue"}],"featurePreferenceValue":true},{"featureName":"GOLDEN_GOOSE_ALERT","attributes":[{"value":"8","key":"daysBeforeDue"}],"featurePreferenceValue":true}]}],"appVersion":"1.0","insertTS":1503530927066,"type":"Push","value":true,"deviceInfo":{"deviceType":"deviceType","deviceModel":"deviceModel","osVersion":"OSVersion","deviceId":"17BBE19B241C219C3FE766FAE1887CE2","deviceOS":"deviceOS","deviceToken":"0838273194687468409056870471453029636922734387856368468017913851"},"status":"ACTIVE"}],"applicationGroup":"INTERNAL__10","id":"com.americanexpress.mobile.wallet.carbon.notifications.messages"}}},"id":"100173"}}}
But the return value is incorrect and does not make sense - you can see here that the values in the JSON fields are missing, and the whole response seems to be incomplete.
{"metrics":{"cryptoManager":null,"empty":false,"names":["executionTime","resultCount","resultSize","elapsedTime"]},"results":{"cryptoManager":null,"empty":false,"names":["bucket_admin"]}}
We're expecting
{metrics:{"executionTime":"780.964µs","resultCount":2,"resultSize":2858,"elapsedTime":"841.098µs"}, results:{"bucket_admin":{"name":"${__RandomString(20,abcdefghijklmnopqrstuvwxyz,)}","notificationEnrollment":{"insertTS":1503530927070,"id":"0000255BAIHSDCY","type":"ENCRYPTEDACCOUNT","issuer":"001","domain":"","apps":{"app":{"enrollmentInfoList":[{"features":[{"featureUserIdentifier":{"id":"BAIHSDCY0000255","type":"ENCRYPTEDACCOUNT","issuer":"001","domain":"CARD"},"featureInfoList":[{"featureName":"PAYMENT_RECEIVED_ALERT","attributes":[{"value":"8","key":"daysBeforeDue"}],"featurePreferenceValue":true},{"featureName":"PAYMENT_REMINDER_ALERT","attributes":[{"value":"8","key":"daysBeforeDue"}],"featurePreferenceValue":true},{"featureName":"STMT_NOTIFICATION_ALERT","attributes":[{"value":"8","key":"daysBeforeDue"}],"featurePreferenceValue":true},{"featureName":"OFFERS","attributes":[{"value":"8","key":"daysBeforeDue"}],"featurePreferenceValue":true},{"featureName":"GOLDEN_GOOSE_ALERT","attributes":[{"value":"8","key":"daysBeforeDue"}],"featurePreferenceValue":true}]}],"appVersion":"1.0","insertTS":1503530927066,"type":"Push","value":true,"deviceInfo":{"deviceType":"deviceType","deviceModel":"deviceModel","osVersion":"OSVersion","deviceId":"17BBE19B241C219C3FE766FAE1887CE2","deviceOS":"deviceOS","deviceToken":"0838273194687468409056870471453029636922734387856368468017913851"},"status":"ACTIVE"}],"applicationGroup":"INTERNAL__10","id":"com.americanexpress.mobile.wallet.carbon.notifications.messages"}}},"id":"100173"}}}
Any idea what I am doing incorrect here?
I have a string:
"1, 2, 3 , -4"
it is split by ", ".
I have a function to convert a number to a enum type which works fine. I want to use java 8 to convert this string to a list of enum objects.
Pattern pattern = Pattern.compile(", ");
List<f> fList = pattern.splitAsStream(str)
.map(s -> {
try {
return this.getEnumObject(Integer.valueOf(s), f.class);
}
catch (NoEleException e) {
e.printStackTrace();
}
})
.collect(Collectors.toList());
This gives me an error:
missing return type.
How could I fix it?
Currently, if an exception occurs no result will be returned hence the compilation error. You'll need to return a value after the catch block .
Basically to ways of managing this:
catching the exception and return some value or encapsulate values in Optionals and filter accordingly
Throwing a RuntimeException which chains the original one
In the first case we use Optional to put something into the stream on error, and then manage these empty values further in the stream:
pattern.splitAsStream(str)
.map(s -> {
try {
return Optional.of(this.getEnumObject(Integer.valueOf(s), f.class));
}
catch (NoEleException e) {
e.printStackTrace();
return Optional.empty();
}
})
.filter(Optional::isPresent) // remove empty optionals
.map(Optional::get) // unwrap them
.collect(Collectors.toList());
In the second case the stream is stopped and you can then try to catch the RuntimeException and unchain the original one:
pattern.splitAsStream(str)
.map(s -> {
try {
return Optional.of(this.getEnumObject(Integer.valueOf(s), f.class));
}
catch (NoEleException e) {
e.printStackTrace();
throw new RuntimeException(e); // stop the stream
}
})
.collect(Collectors.toList());
You can create Null Object like MissingElement, return it in catch and them filter it out after map.
If you are certain that this won't happen you could return null in the catch and filter for non null before collecting:
Pattern pattern = Pattern.compile(", ");
List<f> fList = pattern.splitAsStream(str)
.map(s -> {
try {
return this.getEnumObject(Integer.valueOf(s), f.class);
}
catch (Exception e) {
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toList());