Using Java 8, I want two combine to collections: one collection is a java.util.List
; the other is an com.fasterxml.jackson.databind.node.ArrayNode
.
Here's a non-Java 8 way of handling that:
private void mergeMemberAndRelationshipData(final List<JsonNode> members, final ArrayNode relationships) {
assert members != null : "members cannot be null";
assert relationships != null : "relationships cannot be null";
for (int i = 0; i < members.size(); i++) {
final ObjectNode relationship = (ObjectNode) relationships.get(i);
final JsonNode member = members.get(i);
if (member != null && !member.hasNonNull(ExceptionMapper.errorCodeStr)) {
relationship.put(FIRST_NAME, this.jsonHelpers.safeGetString(member, FIRST_NAME));
relationship.put(LAST_NAME, this.jsonHelpers.safeGetString(member, LAST_NAME));
relationship.put(EMAIL, this.jsonHelpers.safeGetString(member, EMAIL));
relationship.put(MEMBER_SINCE, this.jsonHelpers.safeGetString(member, MEMBER_SINCE));
} else {
LOGGER.error("No corresponding Member, {}, found for Relationship: {}", member, relationship);
}
}
}
I wanna stream. I like streams. Okay, then, let's give it a shot:
private void mergeMemberAndRelationshipData(final List<JsonNode> members, final ArrayNode relationships) {
assert members != null : "members cannot be null";
assert relationships != null : "relationships cannot be null";
int i = 0;
members.stream().forEach(member -> {
final ObjectNode relationship = (ObjectNode) relationships.get(i++);
if (member != null) {
relationship.put(FIRST_NAME, this.jsonHelpers.safeGetString(member, FIRST_NAME));
relationship.put(LAST_NAME, this.jsonHelpers.safeGetString(member, LAST_NAME));
relationship.put(EMAIL, this.jsonHelpers.safeGetString(member, EMAIL));
relationship.put(MEMBER_SINCE, this.jsonHelpers.safeGetString(member, MEMBER_SINCE));
} else {
LOGGER.warn("No corresponding member found for relationship: {}", relationship);
}
});
}
But no, that won't work: Local variable i defined in an enclosing scope must be final or effectively final
. Yup, that makes sense. Kinda.
Alright, so I want to stream one collection, A
, and for each object in A
, do some operation on the corresponding object in collection, B
. I need to know the index to do that.
How can I use a counter, declared outside the scope of an anonymous function, inside of that anonymous function?
I can solve this in a coupla inventive, creative ways:
private void mergeMemberAndRelationshipData(final List<JsonNode> members, final ArrayNode relationships) {
assert members != null : "members cannot be null";
assert relationships != null : "relationships cannot be null";
int[] i = new int[1];
i[0] = 0;
members.stream().forEach(member -> {
final ObjectNode relationship = (ObjectNode) relationships.get(i[0]);
i[0] = i[0]++;
if (member != null) {
relationship.put(FIRST_NAME, this.jsonHelpers.safeGetString(member, FIRST_NAME));
relationship.put(LAST_NAME, this.jsonHelpers.safeGetString(member, LAST_NAME));
relationship.put(EMAIL, this.jsonHelpers.safeGetString(member, EMAIL));
relationship.put(MEMBER_SINCE, this.jsonHelpers.safeGetString(member, MEMBER_SINCE));
} else {
LOGGER.warn("No correspodning member foudn for relationship: {}", relationship);
}
});
}
Or I can use an AtomicInteger
thusly:
AtomicInteger i = new AtomicInteger(0);
...
final ObjectNode relationship = (ObjectNode) relationships.get(i.getAndIncrement());
But that's a misuse of AtomicInteger
; that's meant for shared values amongst threads. Not quite what's happening here, and it's way too heavyweight for this to be an appropriate use (in my opinion, please feel free to disagree with me).
So, this is a very common thing to do (linking two ordered lists and operating on their elements). It's super easy pre-Java 8 (technically still is, but you pedants get my point, I'm sure). Is there a Java-8-ey way to accomplish this using streams?
For context, where i work, we use a micro-service architecture approach. This means that we have a bunch of services that are responsible for a discrete, encapsulated set of responsibilities (e.g., MemberService handles member CRUD, and CompanyService handles company CRUD). On top of all that, we have an orchestration/authentication/authorization service that makes these disparate calls and then stitches them together. So, in the method I've pasted, this is the orchestration layer, where I get my two datasets and now I want to merge them together. This is why I don't have a LinkedList or some other similar solution.