7

As far as I know, one could not handle exception thrown in the lambda if the abstract method implemented by the lambda doesn't have throws in its signature.

I encountered following code, it works. Why openStream() doesn't demand handling IOException? I can see try-catch in the tryWithResources but I don't understand the mechanism behind it.

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.function.Function;
import java.util.function.Supplier;

public class Main {

    public static <AUTOCLOSEABLE extends AutoCloseable, OUTPUT> Supplier<OUTPUT> tryWithResources(
            Callable<AUTOCLOSEABLE> callable, Function<AUTOCLOSEABLE, Supplier<OUTPUT>> function,
            Supplier<OUTPUT> defaultSupplier) {
        return () -> {
            try (AUTOCLOSEABLE autoCloseable = callable.call()) {
                return function.apply(autoCloseable).get();
            } catch (Throwable throwable) {
                return defaultSupplier.get();
            }
        };
    }

    public static <INPUT, OUTPUT> Function<INPUT, OUTPUT> function(Supplier<OUTPUT> supplier) {
        return i -> supplier.get();
    }

    public static void main(String... args) {
        Map<String, Collection<String>> anagrams = new ConcurrentSkipListMap<>();
        int count = tryWithResources(
                () -> new BufferedReader(new InputStreamReader(
                        new URL("http://www.puzzlers.org/pub/wordlists/unixdict.txt").openStream())),
                reader -> () -> reader.lines().parallel().mapToInt(word -> {
                    char[] chars = word.toCharArray();
                    Arrays.parallelSort(chars);
                    String key = Arrays.toString(chars);
                    Collection<String> collection = anagrams.computeIfAbsent(key, function(ArrayList::new));
                    collection.add(word);
                    return collection.size();
                }).max().orElse(0), () -> 0).get();
        anagrams.values().stream().filter(ana -> ana.size() >= count).forEach((list) -> {
            for (String s : list)
                System.out.print(s + " ");
            System.out.println();
        });
    }
}
Tunaki
  • 132,869
  • 46
  • 340
  • 423
Yoda
  • 17,363
  • 67
  • 204
  • 344

2 Answers2

9

I've simplified your example to the core part:

public static void main(String[] args) {
    withCallable(() -> new URL("url").openStream()); // compiles
    withSupplier(() -> new URL("url").openStream()); // does not compile
}

public static <T> void withCallable(Callable<T> callable) { }

public static <T> void withSupplier(Supplier<T> callable) { }

If you try with this, you will see that withCallable will compile fine but that withSupplier does not compile; even if the lambda expression is compatible with the signature of both functional interfaces.

The reason behind this is that the functional method of the Callable interface, which is call(), declares throws Exception in its signature. Supplier.get() does not.

Quoting the JLS section 11.2.3:

It is a compile-time error if a lambda body can throw some exception class E when E is a checked exception class and E is not a subclass of some class declared in the throws clause of the function type targeted by the lambda expression.

Tunaki
  • 132,869
  • 46
  • 340
  • 423
1

As far as I know, one could not handle exception thrown in the lambda if the abstract method implemented by the lambda doesn't have throws in its signature.

There's a workaround, known as sneaky throw; here a simpler example without the functional helpers.

By having a few static helper functions that turn your throwing signatures into non-throwing ones which trick the compiler into rethrowing checked exceptions you can turn

.stream().map(x -> {
  try {
    return this.throwingFunction(x)
  } catch(Exception e) {
    throw new RuntimeException(e);
  }
}).forEach(...)

into

.stream().map(uncheckedFunc(this::throwingFunction)).forEach(...)

without wrapping the exception into a generic runtime exception.

Community
  • 1
  • 1
the8472
  • 40,999
  • 5
  • 70
  • 122