In the following code, when catching NumberFormatException
out of for
iteration, the strings in appropriate form appearing in strList
before the first bad one (i.e., "illegal_3"
) have been parsed successfully (i.e., "1"
and "2"
have been parsed as integers 1
and 2
).
public void testCaughtRuntimeExceptionOutOfIteration() {
List<String> strList = Stream.of("1", "2", "illegal_3", "4", "illegal_5", "6").collect(Collectors.toList());
List<Integer> intList = new ArrayList<>();
try{
for (String str : strList) {
intList.add(Integer.parseInt(str));
}
} catch (NumberFormatException nfe) {
System.err.println(nfe.getMessage());
}
List<Integer> expectedIntList = Stream.of(1, 2).collect(Collectors.toList());
// passed
assertEquals("The first two elements have been parsed successfully.", expectedIntList, intList);
}
However, when replacing for
iteration by stream()
or parallelStream()
, I lose 1
and 2
.
public void testCaughtRuntimeExceptionOutOfStream() {
List<String> strList = Stream.of("1", "2", "illegal_3", "4", "illegal_5", "6").collect(Collectors.toList());
List<Integer> intList = new ArrayList<>();
try{
intList = strList.stream() // same with "parallelStream()"
.map(Integer::parseInt)
.collect(Collectors.toList());
} catch (NumberFormatException nfe) {
System.err.println(nfe.getMessage());
}
List<Integer> expectedIntList = Stream.of(1, 2).collect(Collectors.toList());
// failed: expected:<[1,2]>, but was:<[]>
assertEquals("The first two elements have been parsed successfully.", expectedIntList, intList);
}
What is the specification of the control flow of exceptions thrown from within
stream()
orparallelStream()
?How can I get the result of
intList = [1,2]
(i.e., ignore the ones after the firstNumberFormatException
is thrown) or even betterintList = [1,2,4,6]
(i.e., ignore the bad ones withNumberFormatException
) withstream()
orparallelStream()
Here we can see the order is not maintained as the list. parallelStream() works parallelly on multiple threads.
To take a look at checked exceptions, we will use the example of Thread. sleep() method, which throws an InterruptedException . This is because lambdas are after all anonymous class implementations. We need to handle the exception within the implementation of the interface the lambda is implementing.
You have to convert it to a Stream<Integer> (via boxed() ) before collecting it to a List<Integer> . And the return type of the method should be List<Integer> . Save this answer.
Why not just wrap lambda-body in try...catch
?
Also you can filter null
values after map
:
intList = strList.stream()// same with "parallelStream()"
.map(x -> {
try {
return Integer.parseInt(x);
} catch (NumberFormatException nfe) {
System.err.println(nfe.getMessage());
}
return null;
})
.filter(x -> x!= null)
.collect(Collectors.toList());
This will give you desired intList = [1,2,4,6]
.
Edit: To reduce the "heaviness" of a try/catch in a lamdba you can add a helper method.
static Integer parseIntOrNull(String s) {
try {
return Integer.parseInt(s);
} catch (NumberFormatException nfe) {
System.err.println(nfe.getMessage());
}
return null;
}
intList = strList.stream()
.map(x -> parseIntOrNull(x))
.filter(x -> x!= null)
.collect(Collectors.toList());
Or to avoid using null, you can return a Stream
static Stream<Integer> parseIntStream(String s) {
try {
return Stream.of(Integer.parseInt(s));
} catch (NumberFormatException nfe) {
System.err.println(nfe.getMessage());
}
return Stream.empty();
}
intList = strList.stream()
.flatMap(x -> parseIntStream(x))
.collect(Collectors.toList());
A method can't both return a value, and throw an exception. That is impossible.
So you can't expect collect()
to both return a list, and throw an exception. Since if throws an exception, it can't return a new list.
If your for loop code was actually similar to the stream code, you would have the same problem:
public void testCaughtRuntimeExceptionOutOfIteration() {
List<String> strList = Stream.of("1", "2", "illegal_3", "4", "illegal_5", "6").collect(Collectors.toList());
List<Integer> intList = new ArrayList<>();
try{
intList = collectToIntegers(strList);
} catch (NumberFormatException nfe) {
System.err.println(nfe.getMessage());
}
List<Integer> expectedIntList = Stream.of(1, 2).collect(Collectors.toList());
// fails
assertEquals("The first two elements have been parsed successfully.", expectedIntList, intList);
}
private List<Integer> collectToIntegers(List<String> strList) {
List<Integer> result = new ArrayList<>();
for (String str : strList) {
result.add(Integer.parseInt(str));
}
return result;
}
In short: don't confuse "creating and returning a new list", with "taking a list and add elements to it".
I don't know how many times I encountered a situation where I just wanted to ignore the NumberFormatException. I would probably create a separate re-usable method to parse integer silently and return OptionalInt value.
Here is the utils class
public class IntUtils {
// ... other utility methods
public static OptionalInt parseInt(String s, Consumer<? super Exception> exceptionConsumer) {
try {
return OptionalInt.of(Integer.parseInt(s));
} catch (NumberFormatException e) {
if (exceptionConsumer != null) {
// let the caller take the decision
exceptionConsumer.accept(e);
} else {
// default behavior
e.printStackTrace();
}
}
return OptionalInt.empty();
}
public static OptionalInt parseInt(String s) {
return parseInt(s, null);
}
}
Here is the test method
List<Integer> collect1 = strStream.map(str -> IntUtils.parseInt(str, Exception::printStackTrace))
.filter(OptionalInt::isPresent)
.map(OptionalInt::getAsInt).collect(toList());
// or
List<Integer> collect2 = strStream.map(IntUtils::parseInt)
.filter(OptionalInt::isPresent)
.map(OptionalInt::getAsInt).collect(toList());
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With