How to convert the following nested forEach loop into functional code?
ABC abc = new ABC();
for (A a : aList) {
for (String b : bList) {
if (Objects.equals(a.getName(), b)) {
abc.setId(a.getId());
abc.setValue(a.getValue());
}
}
}
I've tried to convert it this way, but it didn't work:
aList.forEach(a -> {
bList.stream()
.filter(b -> Objects.equals(b, a.getName()));
abc.setId(a.getId());
abc.setValue(a.getValue());
});
Not sure what is considered a "functional code" and why that is needed here.
My approach, assuming bList is a List<String>:
var abc = aList
.stream()
.filter(this::isKnownName)
.map(this::abcFromA)
.findFirst() // see ¹ bellow
.orElseGet(ABC::new);
using the following helper functions - not really needed, we could use lambda expressions, but I think using this is a little bit more readable ² :
private boolean isKnownName(A a) {
return bList.contains(a.getName());
}
private ABC abcFromA(A a) {
var result = new ABC();
result.setId(a.getId());
result.setValue(a.getValue());
return result;
}
¹ this code is using the first match found in bList, code in question is using the last match
² I would struggle between using this solution at all and using a single loop (with bList.contains() instead of inner loop as posted in question).
You can try
aList.forEach(a -> {
if( bList.stream()
.anyMatch(b -> Objects.equals(b, a.getName()))){
abc.setId(a.getId());
abc.setValue(a.getValue());
}
});
or better
aList.stream().filter(a -> bList.stream()
.anyMatch(b -> Objects.equals(b,a.getName())))
.findAny()
.ifPresent(a -> {
abc.setId(a.getId());
abc.setValue(a.getValue());
});
Related
I am trying to modify a Map's keys based on conditional logic and struggling. I'm new to Java 8 streams API. Let's say I have a map like this:
Map<String, String> map = new HashMap<>();
map.put("PLACEHOLDER", "some_data1");
map.put("Google", "some_data2");
map.put("Facebook", "some_data3");
map.put("Microsoft", "some_data4");
When I would like to do is find the references of PLACEHOLDER and conditionally change that key to something else based on a boolean condition. I feel like it should be something like the below, but this doesn't even compile of course.
boolean condition = foo();
map = map.entrySet().stream().filter(entry -> "PLACEHOLDER".equals(entry.getKey()))
.map(key -> {
if (condition) {
return "Apple";
} else {
return "Netflix";
}
}).collect(Collectors.toMap(e -> e.getKey(), Map.Entry::getValue));
I found this question which kind of makes me think maybe I can't do this with Java 8 stream APIs. Hopefully someone better at this than me knows how to do this. Ideone link if you want to play with it.
You've filtered out all elements that aren't PLACEHOLDER. You need to add that filter logic to your map operation:
final Map<String, String> output = input.entrySet().stream()
.map(e -> {
if (!e.getKey().equals("PLACEHOLDER")) {
return e;
}
if (condition) {
return new AbstractMap.SimpleImmutableEntry<>("Apple", e.getValue());
}
return new AbstractMap.SimpleImmutableEntry<>("Netflix", e.getValue());
}).collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
But as you are guaranteed to only have a single instance of PLACEHOLDER in the Map, you can just do
String placeholderData = input.remove("PLACEHOLDER");
if (placeholderData != null) {
input.put(condition ? "Apple" : "Netflix", placeholderData);
}
If you really want to do it using Streams, you just need to move the conditional logic to the collection phase, like that:
boolean condition = true;
map.entrySet().stream().collect(Collectors.toMap(
entry -> mapKey(entry.getKey(), condition), Map.Entry::getValue
));
where:
private static String mapKey(String key, boolean condition) {
if (!"PLACEHOLDER".equals(key)) {
return key;
}
if (condition) {
return "Apple";
} else {
return "Netflix";
}
}
However, the second part of Boris the Spider's answer using Map.remove and Map.put seems the best way to go.
I'd like to convert the following code, which breaks from the outer loop, into Java 8 Streams.
private CPBTuple getTuple(Collection<ConsignmentAlert> alertsOnCpdDay)
{
CPBTuple cpbTuple=null;
OUTER:
for (ConsignmentAlert consignmentAlert : alertsOnCpdDay) {
List<AlertAction> alertActions = consignmentAlert.getAlertActions();
for (AlertAction alertAction : alertActions) {
cpbTuple = handleAlertAction(reportDTO, consignmentId, alertAction);
if (cpbTuple.isPresent()) {
break OUTER;
}
}
}
return cpbTuple;
}
Every answer here uses flatMap, which until java-10 is not lazy. In your case that would mean that alertActions is traversed entirely, while in the for loop example - not. Here is a simplified example:
static class User {
private final List<String> nickNames;
public User(List<String> nickNames) {
this.nickNames = nickNames;
}
public List<String> getNickNames() {
return nickNames;
}
}
And some usage:
public static void main(String[] args) {
Arrays.asList(new User(Arrays.asList("one", "uno")))
.stream()
.flatMap(x -> x.getNickNames().stream())
.peek(System.out::println)
.filter(x -> x.equalsIgnoreCase("one"))
.findFirst()
.get();
}
In java-8 this will print both one and uno, since flatMap is not lazy.
On the other hand in java-10 this will print one - and this is what you care about if you want to have your example translated to stream-based 1 to 1.
Something along the lines of this should suffice:
return alertsOnCpdDay.stream()
.flatMap(s-> s.getAlertActions().stream())
.map(s-> handleAlertAction(reportDTO, consignmentId, s))
.filter(s-> s.isPresent())
.findFirst().orElse(null);
That said, a better option would be to change the method return type to Optional<CPBTuple> and then simply return the result of findFirst(). e.g.
private Optional<CPBTuple> getTuple(Collection<ConsignmentAlert> alertsOnCpdDay) {
return alertsOnCpdDay.stream()
.flatMap(s-> s.getAlertActions().stream())
.map(s-> handleAlertAction(reportDTO, consignmentId, s))
.filter(s-> s.isPresent())
.findFirst();
}
This is better because it better documents the method and helps prevent the issues that arise when dealing with nullity.
Since you break out of the loops upon the first match, you can eliminate the loops with a Stream with flatMap, which returns the first available match:
private CPBTuple getTuple(Collection<ConsignmentAlert> alertsOnCpdDay) {
return alertsOnCpdDay.stream()
.flatMap(ca -> ca.getAlertActions().stream())
.map(aa -> handleAlertAction(reportDTO, consignmentId, aa))
.filter(CPBTuple::isPresent)
.findFirst()
.orElse(null);
}
Try this out,
alertsOnCpdDay.stream()
.map(ConsignmentAlert::getAlertActions)
.flatMap(List::stream)
.map(alertAction -> handleAlertAction(reportDTO, consignmentId, alertAction))
.filter(CPBTuple::isPresent)
.findFirst().orElse(null);
Based on some sports results data, I have a Fixture object which has getHome() and getAway() method. I'd like to shorten this method which I've written to only use a single lambda function (instead of creating a new list and two lambdas), is this possible?
private Collection<FixtureResult> finalResults(Team team) {
List<FixtureResult>finalResults = new ArrayList<>();
List<FixtureResult> homeResults = resultList.stream().filter(fixture ->
fixture.getHome().equals(team))
.collect(toList());
List<FixtureResult> awayResults = resultList.stream().filter(fixture ->
fixture.getAway().equals(team))
.collect(toList());
finalResults.addAll(homeResults);
finalResults.addAll(awayResults);
return finalResults;
}
Simple enough
resultList.stream()
.filter(fixture -> fixture.getHome().equals(team) || fixture.getAway().equals(team)))
.collect(toList());
EDIT: This is on the assumption that order does not matter to you. If your final list needs to have home result and then away, have a look at Elliott Frisch's answer.
If you wan to get fancy with lambdas:
Predicate<FixtureResult> isHome = fr -> fr.getHome().equals(team)
Predicate<FixtureResult> isAway = fr -> fr.getAway().equals(team)
resultList.stream()
.filter(isHome.or(isAway))
.collect(toList()));
You could even extract the compose predicate to test it in isolation, with no streams involved, which is good for more complex predicates:
Predicate<FixtureResult> isHomeOrAway = isHome.or(isAway)
assertTrue(isHomeOrAway(homeFixture));
...
Assuming the order doesn't matter, you can do it on one line. Like,
private Collection<FixtureResult> finalResults(Team team) {
return resultList.stream()
.filter(fixture -> fixture.getHome().equals(team)
|| fixture.getAway().equals(team))
.collect(toList());
}
If the order matters (home results and then away), you can do it with a single List like
private Collection<FixtureResult> finalResults(Team team) {
List<FixtureResult> al = new ArrayList<>(resultList.stream()
.filter(fixture -> fixture.getHome().equals(team)).collect(toList()));
al.addAll(resultList.stream()
.filter(fixture -> fixture.getAway().equals(team)).collect(toList()));
return al;
}
You can simply create a conditions concatenations or can concatenate multiple filter call
Conditions concatenations
myList.stream()
.filter(element -> (condition1 && condition2 && condition3))
Multiple filter call
myList.stream()
.filter(element -> condition1)
.filter(element -> condition2)
.filter(element -> condition3)
You can do the following
someStream.filter(((Predicate<SomeClass>) someObject-> someCondition).or(someObject-> someOtherCondition))
Or you can define your own "or" function that won't cause such a deep hierarchy
#SuppressWarnings("unchecked")
<R> Predicate<R> or(Predicate<R> ...predicates) {
return r -> Arrays.stream(predicates).anyMatch(p -> p.test(r));
}
That gives you a cleaner interface without casting and the nesting
.filter(or(
yourObject -> {
return false;
},
yourObject -> {
return false;
},
yourObject -> {
return false;
},
yourObject -> {
return false;
}
))
No "if" statements, please, unless you're explaining why it's impossible to do without one.
I'm seeing how far I can go operating on streams only. I have this nuisance:
List<Cube> revised =
cubes.filter(p)
.map(c -> f(c))
.map(c -> {
if(c.prop()) {
c.addComment(comment);
}
return c;
})
.collect(Collectors.toList());
My best idea for how to do this without an "if" is
List<Cube> revised =
cubes.filter(p)
.map(c -> f(c));
revised
.filter(Cube::prop)
.forEach(c -> c.addComment(comment)); // can also map still
Is there a way to do this in one chain only? A branch basically has to happen in the stream if so. A method like forSome(predicate, lambda) would work.
Do not want to "roll my own" anything. I can use an "if" but I'm trying to learn how expressive functional style can be.
There's no need to use map that returns the same element, when you have peek. The following code "cheats" by using a short-circuit operator:
cubes.filter(p)
.map(c -> f(c))
.peek(c -> c.prop() && c.addComment(comment))
I think the "modern" way using Optional is far less readable:
cubes.filter(p)
.map(c -> f(c))
.peek(c -> Optional.of(c).filter(Cube::prop).ifPresent(c -> c.addComment(comment)))
You can implement your forSome function in following way:
public static <T> T forSome(T c, Predicate<T> condition, Consumer<T> extraBehaviour) {
if (condition.test(c)) {
extraBehaviour.accept(c);
}
return c;
}
Than you can use map operator to inject this into stream:
List<Cube> revised = cubes.stream().filter(p)
.map(c -> f(c))
.map(c -> forSome(c, Cube::prop, cube -> cube.addComment("my comment 2")))
.collect(Collectors.toList());
Just to give another example of usage we can take following example:
class StudentExam {
private final String studentName;
private final List<Character> answers;
private boolean passed = false;
StudentExam(String studentName, List<Character> answers) {
this.studentName = studentName;
this.answers = answers;
}
public void markAsPassed() {
this.passed = true;
}
public boolean isPassed() {
return passed;
}
public Character getAnswer(int index) {
return answers.get(index);
}
public String getStudentName() {
return studentName;
}
}
List<StudentExam> results = asList(
new StudentExam("John", asList(new Character[] {'A', 'B'})),
new StudentExam("Andy", asList(new Character[] {'A', 'C'})),
new StudentExam("Mary", asList(new Character[] {'B', 'B'})),
new StudentExam("Jane", asList(new Character[] {'C', 'D'}))
);
Now we can if correct answers are 'A' and 'B' than we can stream through the objects and set the appropriate status of exam.
results.stream()
.map(examResult -> forSome(
examResult,
er -> er.getAnswer(0).equals('A') || er.getAnswer(1).equals('B'),
StudentExam::markAsPassed))
.forEach(studentExam ->
studentExam.getStudentName() + " passed: " + studentExam.isPassed()));
prints:
John: passed true
Andy: passed true
Mary: passed true
Jane: passed false
I've tried to change this code to Java 8 streams. My code looks like this:
for(D d : n.getD()) {
for(M m : d.getT().getM()) {
if(m.getAC().contains(this)) {
return d;
}
}
}
and I want to convert it to java 8 streams. I've started like this:
n.getD().stream()
.map(m -> m.getT().getM())
but then I don't know if I should map again, or use a filter.
Other possible way is to use anyMatch instead of second filter
return n.getD().stream().filter(
d -> d.getT().getM().stream().anyMatch(
m -> m.getAC().contains(this)
)
).findFirst(); // result will be Optional<D>
one way to handle this:
return n.getD().stream().filter(d -> d.getT().getM().stream().filter(m -> m.getAC().contains(this)).findFirst().isPresent()).findFirst();
in this case a null value is possible.
I don't know about your domain, but to keep it readable I would probably delegate and simplify to something like this:
return n.getD().stream()
.filter(d -> d.getT().containsAC(this))
.findFirst()
.orElse(null);
And then in class T add the delegation method:
public boolean containsAC(AC ac) {
return m.stream().anyMatch(m -> m.getAC().contains(ac));
}