1

I've seen a few questions related to this, but I need a clear answer. I do understand the context where lambda expressions run, and the concept of side effects, but I believe there is a workaround I'm not seeing here.

I need to map a list of personas based on their sex, but the method I use to determine their sex returns a checked exception, which is not something the Collectors.groupingBy likes, at all.

Getting rid of the checked exception is not an option, and I need to send it up to the client method who invoked my piece of code. I there anything I can do around it?

public class Example {
  public static void main(String[] args) {
    Example example = new Example();
    try {
        example.runExample();
    } catch (MException e) {
        //Place where I want to handle the exception
    }
  }

  private void runExample() throws MException{
    List<Person> personas = Arrays.asList(new Person("Sergio", "234456789", 35), new Person("Mariana", "123456789", 38));
    Map<String, List<Person>> personsBySex = personas.stream().collect(Collectors.groupingBy(persona -> {
        try {
            return getSex(persona.getSSN());
        } catch (MException e) {
        }
        return null; 
        //Compiler forces me to return a value, but I don't want to return null. 
        //I want to throw up the MException to the client method (main)
    }));
  }

  private String getSex(String ssn) throws MException {
    // Imagine here is a call to an outside service that would give me the
    // sex based on the SSN, but this service could return an exception as
    // well
    if (ssn.isEmpty())
        throw new MException();
    return ssn.startsWith("1") ? "Female" : "Male";
  }
}


class Person {
  private String name, ssn;
  private Integer age;

  public Person(String name, String ssn, Integer age) {
    this.name = name;
    this.ssn = ssn;
    this.age = age;
  }

  public String getName() {return name;}
  public String getSSN() {return ssn;}
  public Integer getAge() {return age;}
}

class MException extends Exception {
}

Thanks for any ideas!

Talenel
  • 422
  • 2
  • 6
  • 25
sercasti
  • 550
  • 3
  • 7
  • I found [this question](https://stackoverflow.com/questions/27644361/how-can-i-throw-checked-exceptions-from-inside-java-8-streams) and its comments and answers very interesting to read. Tl;dr: You have to deal with the exception, but as the answers below and in the linked question show, you can work around it with certain patterns or utils that even hide those patterns from you. – Malte Hartwig Sep 18 '17 at 10:14

2 Answers2

3

A workaround could be wrapping your checked exception with an unchecked one and throwing the latter from the catch block. Note that we are saving an MException as a cause to be able to work with further.

try {
    return getSex(persona.getSSN());
} catch (MException e) {
    throw new IllegalArgumentException(e); // choose a suitable runtime one
}

Another way to go (rather a modification of the previous one, I don't like that) would be writing a Collectors.groupingBy-friendly method to move the foregoing logic into:

private String getSexFriendly(String ssn) {
    try {
        return getSex(ssn);
    } catch (MException e) {
        throw new IllegalArgumentException(e);
    }
}

Though, we will have a good-looking lambda:

persona -> getSexFriendly(persona.getSSN())
Andrew Tobilko
  • 48,120
  • 14
  • 91
  • 142
1

Forget about wrapping your exception - you can utilize the "sneaky throws' hack here which enables you to trick the compiler to think that your exception is not checked - this exploits the type inference rule introduced in Java 8.

Let's recreate your problem:

public Integer toInteger(String string) throws IOException {
    throw new IOException("whoopsie!");
}

Stream.of("42")
  .collect(Collectors.groupingBy(o -> toInteger(o))); // does not compile

Usually, you'd need to try-catch the exception just like you did, but there is a workaround:

@Test
public void example_1() throws Exception {
    Stream.of("42")
      .collect(Collectors.groupingBy(unchecked(this::toInteger)));
}

public Integer toInteger(String string) throws IOException {
    throw new IOException("whoopsie!");
}

private static <T, R> Function<T, R> unchecked(ThrowingFunction<T, R> f) {
    return t -> {
        try {
            return f.apply(t);
        } catch (Throwable thr) {
            return ThrowingFunction.sneakyThrow(thr);
        }
    };
}

public interface ThrowingFunction<T, R> {
    R apply(T t) throws Throwable;

    @SuppressWarnings("unchecked")
    static <T extends Throwable, R> R sneakyThrow(Throwable t) throws T {
        throw (T) t;
    }
}

Firstly, you need to create your own functional interface for representing a function that can throw exceptions. In this case, ThrowingFunction.

Then, you can create a utility method that will repackage your checked lambda into a standard java.util.function. In this case, unchecked().

The last step is to create a method that sneakily throws an exception. In this case, sneakyThrow().

Actually, I think I will write an article about this.

EDIT: I wrote: http://4comprehension.com/sneakily-throwing-exceptions-in-lambda-expressions-in-java/

Grzegorz Piwowarek
  • 13,172
  • 8
  • 62
  • 93