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()
or parallelStream()
?
How can I get the result of intList = [1,2]
(i.e., ignore the ones after the first NumberFormatException
is thrown) or even better intList = [1,2,4,6]
(i.e., ignore the bad ones with NumberFormatException
) with stream()
or parallelStream()
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());
I am using org.apache.commons.lang3.math.NumberUtils
:
.mapToInt(s -> NumberUtils.toInt(s, Integer.MIN_VALUE))
.filter(x -> x > Integer.MIN_VALUE)