Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Catching exceptions out of 'stream()' or 'parallelStream()' loses correct values

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()

like image 682
hengxin Avatar asked Jan 02 '16 08:01

hengxin


People also ask

Does parallelStream maintain order?

Here we can see the order is not maintained as the list. parallelStream() works parallelly on multiple threads.

How does Java handle exceptions in streams?

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.

Which is the correct way of using parallel stream to get array having even numbers?

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.


3 Answers

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());
like image 88
Andremoniy Avatar answered Oct 01 '22 13:10

Andremoniy


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".

like image 29
JB Nizet Avatar answered Oct 01 '22 11:10

JB Nizet


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());
like image 36
TriCore Avatar answered Oct 01 '22 11:10

TriCore