Break/Stop forEachRemaning using Java8
Pergunta
Consider following snippet:
private List<User> getUsers() throws TasteException {
final int MAX_USERS = 100;
List<User> userList = new ArrayList<>(MAX_USERS);
dataModel.getUserIDs().forEachRemaining(userId -> {
if (userList.size() == 100) {
// stop
}
userList.add(new User(userId));
});
return userList;
}
break
or return
is not working here. What can I do?
Solução
The only way to stop the iteration early is to throw an exception. Using exceptions for control flow isn't advised, so I would use Stream.limit, .map, and .collect:
private List<User> getUsers() throws TasteException {
final int MAX_USERS = 100;
return dataModel.getUserIDs()
.stream()
.limit(MAX_USERS)
.map(userId -> new User(userId))
.collect(Collectors.toList());
}
If getUserIDs can't be changed to return Collection you can convert to a Spliterator first:
private List<User> getUsers() throws TasteException {
final int MAX_USERS = 10;
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(dataModel.getUserIDs(), 0), false)
.limit(MAX_USERS)
.map(userId -> new User(userId))
.collect(Collectors.toList());
}
Outras dicas
Consider using the streams properly, it seems like you want:
dataModel.getUserIDs().stream()
.limit(100)
.forEach(userId -> userList.add(new User(userId)));
This will obtain a stream of the first 100 items and perform an action on them. I cannot give a more detailed answer as I do not know the signature of dataModel.getUserIDs()
.
You can 'emulate' a break;
adding an if for a boolean check at start of the foreach lambda body, before doing any intensive operation
Note that I used an final Map<Boolean>
to hold the boolean
flag found
as it's a way to declare a boolean 'variable' outside the lambda (you know, it has to be 'final') but be able to set its value in the loop
boolean containsSecret(Iterable<String> values) {
final Map<String, Boolean> scopedLambdaBooleans = new HashMap<String, Boolean>();
scopedLambdaBooleans.put("found", false);
values.forEach(s -> {
if (scopedLambdaBooleans.get("found")) {
return; //just the overhead of a boolean if but a continue before any iteration is very near to the break; performance
}
//Logger.getAnonymousLogger().info(s);
if (secret.equals(s)) {
scopedLambdaBooleans.put("found", true);
}
});
return scopedLambdaBooleans.get("found");
}
The overhead is just a boolean if
check at start of any iteration
If you fell guilty for having added an if check
, you can compensate getting rid of the internal if
:
boolean containsSecretNoInternalIf(Iterable<String> values) {
final Map<String, Boolean> scopedLambdaBooleans = new HashMap<String, Boolean>();
scopedLambdaBooleans.put("found", false);
values.forEach(s -> {
if (scopedLambdaBooleans.get("found")) {
return; //just the overhead of a boolean if but a continue before any iteration is very near to the break; performance
}
Logger.getAnonymousLogger().info(s);
scopedLambdaBooleans.put("found", secret.equals(s));
});
return scopedLambdaBooleans.get("found");
}
Anyway that's writing a boolean 'bit' in memory at any iteration before the finding, I don't know if really better than an 'if' (is the java 'if check' on a .equals using a bit of memory to execute itself or directly cpu registers? uhm.. being on a jvm we should think it like 'Stack vs. Heap' and yes, I think on modern jvm with JIT compilers Stack has optimizations to use directly cpu registers )
Control flow (break, early return) - In the forEach examples above, a traditional continue is possible by placing a "return;" statement within the lambda. However, there is no way to break out of the loop or return a value as the result of the containing method from within the lambda. For example:
final String secret = "foo";
boolean containsSecret(Iterable<String> values) {
values.forEach(s -> {
if (secret.equals(s)) {
??? // want to end the loop and return true, but can't
}
});
}
To see more here is the link : http://www.techempower.com/blog/2013/03/26/everything-about-java-8/