Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling exceptions with streams

I have a Map<String,List<String>> and want it to turn into Map<String,List<Long>> because each String in the list represents a Long :

Map<String,List<String>> input = ...;
Map<String,List<Long>> output= 
input.entrySet()
       .stream()
       .collect(toMap(Entry::getKey, e -> e.getValue().stream()
                                                      .map(Long::valueOf)
                                                      .collect(toList()))
               );

My main issue is each String may not represent correctly a Long; there may be some issue. Long::valueOf may raise exceptions. If this is the case, I want to return a null or empty Map<String,List<Long>>

Because I want to iterate after over this output map. But I cannot accept any error conversion; not even a single one. Any idea as to how I can return an empty output in case of incorrect String -> Long conversion?

like image 228
AntonBoarf Avatar asked Oct 02 '19 12:10

AntonBoarf


People also ask

Can you handle exception in stream?

From a stream processing, we can throw a RuntimeException. It is meant to be used if there is a real problem, the stream processing is stopped; Or if we don't want to stop the whole processing, we only need to throw a caught Exception. Then it has to be handled within the stream.

What are the 3 types of exceptions?

There are three types of exception—the checked exception, the error and the runtime exception.

How do you handle exceptions in multithreading?

Uncaught exception handler will be used to demonstrate the use of exception with thread. It is a specific interface provided by Java to handle exception in the thread run method. There are two methods to create a thread: Extend the thread Class (java.

How do you handle exceptions in a Stream pipeline?

Here you’ll see three primary ways to handle exceptions in a stream pipeline and each approach will include several examples. Imagine you have a collection of integers, and you want to divide each of them by a constant. A scaling operation like that can be expressed using a stream as in Example 1. Example 1.

How to throw checked exceptions in Java streams?

This approach uses an utility method from the Apache Commons Lang library to throw checked exceptions in Java Streams. Hence, you need to have a dependency on this library for this. The Apache Commons Lang has an utility class called Streams which provides functions for working with Java streams.

Is it possible to terminate a stream if an exception occurs?

The only problem left is that when an exception occurs, the processing of your stream stops immediately. If that is no problem for you, then go for it. I can imagine, however, that direct termination is not ideal in many situations. When working with streams, we probably don't want to stop processing the stream if an exception occurs.

Should I use try or either for exception handling in Java?

Anyway, in both cases, if you use the Try or the Either, you solve the initial problem of exception handling and do not let your stream terminate because of a RuntimeException. Both the Either and the Try are very easy to implement yourself. On the other hand, you can also take a look at functional libraries that are available.


3 Answers

I personally like to provide an Optional input around number parsing:

public static Optional<Long> parseLong(String input) {
    try {
        return Optional.of(Long.parseLong(input));
    } catch (NumberFormatException ex) {
        return Optional.empty();
    }
}

Then, using your own code (and ignoring bad input):

Map<String,List<String>> input = ...;
Map<String,List<Long>> output= 
input.entrySet()
       .stream()
       .collect(toMap(Entry::getKey, e -> e.getValue().stream()
                                                      .map(MyClass::parseLong)
                                                      .filter(Optional::isPresent)
                                                      .map(Optional::get)
                                                      .collect(toList()))
               );

Additionally, consider a helper method to make this more succinct:

public static List<Long> convertList(List<String> input) {
    return input.stream()
        .map(MyClass::parseLong).filter(Optional::isPresent).map(Optional::get)
        .collect(Collectors.toList());
}

public static List<Long> convertEntry(Map.Entry<String, List<String>> entry) {
    return MyClass.convertList(entry.getValue());
}

Then you can filter the results in your stream's collector:

Map<String, List<Long>> converted = input.entrySet().stream()
    .collect(Collectors.toMap(Entry::getKey, MyClass::convertEntry));

You could also keep the empty Optional objects in your lists, and then by comparing their index in the new List<Optional<Long>> (instead of List<Long>) with the original List<String>, you can find the string which caused any erroneous inputs. You could also simply log these failures in MyClass#parseLong

However, if your desire is to not operate on any bad input at all, then surrounding the entire stream in what you're attempting to catch (per Naman's answer) is the route I would take.

like image 74
Rogue Avatar answered Oct 22 '22 14:10

Rogue


How about an explicit catch over the exception:

private Map<String, List<Long>> transformInput(Map<String, List<String>> input) {
    try {
        return input.entrySet()
                .stream()
                .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().stream()
                        .map(Long::valueOf)
                        .collect(Collectors.toList())));
    } catch (NumberFormatException nfe) {
        // log the cause
        return Collections.emptyMap();
    }
}
like image 37
Naman Avatar answered Oct 22 '22 13:10

Naman


You can create a StringBuilder for key with exception and check if ele is numeric as below,

 public static Map<String, List<Long>> transformInput(Map<String, List<String>> input) {
    StringBuilder sb = new StringBuilder();
    try {
    return input.entrySet()
            .stream()
            .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().stream()
                    .map(ele->{
                        if (!StringUtils.isNumeric(ele)) {
                            sb.append(e.getKey()); //add exception key
                            throw new NumberFormatException();
                        }
                        return Long.valueOf(ele);
                    })
                    .collect(Collectors.toList())));
} catch (NumberFormatException nfe) {
    System.out.println("Exception key "+sb);
    return Collections.emptyMap();
}
}

Hope it helps.

like image 1
Vikas Yadav Avatar answered Oct 22 '22 15:10

Vikas Yadav