Java 8 method reference unhandled exception
Asked Answered
C

7

87

I'm working on project with Java 8 and found one situation which I can't understand.

I have code like this:

void deleteEntity(Node node) throws SomeException {
    for (ChildNode child: node.getChildren()) {
       deleteChild(child);
    }
}

void deleteChild(Object child) throws SomeException {
    //some code
}

This code is working fine, but I can rewrite it with a method reference:

void deleteEntity(Node node) throws SomeException {
    node.getChildren().forEach(this::deleteChild);
}

And this code doesn't compile, giving the error Incompatible thrown types *SomeException* in method reference.

Also IDEA gave me the error unhandled exception.

So, my question is why? Why code compiles with for each loop and doesn't compile with lambda?

Cyrene answered 3/9, 2014 at 11:39 Comment(1)
As an aside, this isn't a lambda expression - it's a method reference. It would be a lambda expression if you used forEach(x -> deleteChild(x)). That would fail for the same reason though.Imaginative
I
91

If you look at the Consumer<T> interface, the accept method (which is what your method reference would effectively be using) isn't declared to throw any checked exceptions - therefore you can't use a method reference which is declared to throw a checked exception. The enhanced for loop is okay, because there you're always in a context where SomeException can be thrown.

You could potentially create a wrapper which converts the checked exception to an unchecked exception, and throw that. Alternatively, you could declare your own functional interface with an accept() method which does throw a checked exception (probably parameterizing the interface with that exception), and then write your own forEach method that takes that functional interface as an input.

Imaginative answered 3/9, 2014 at 11:47 Comment(5)
Hi Thx for your question / Thx for your answer. What about not using checked exceptions from java 8 and up?Scattering
Of course they are not! :) I've been reading about people disagreeing about checked v.s. unchecked exceptions. See for example. Here the Oracle doc is pretty final about how to use checked exceptions. However they do mention the limitation checked exception impose on the use of lambdas. I was wondering if this limitation could be bad enough to avoid using checked exceptions.Scattering
@avi613: That's more of an opinion question than anything else, really...Imaginative
Please give some example of my own forEach. Do i need to override forEach from the collection?Straightjacket
@KanagaveluSugumar: You can't override it for the collection interfaces, but you could write your own static method accepting the collection.Imaginative
R
26

You may try this:

void deleteEntity(Node node) throws SomeException {
    node.getChildren().forEach(UtilException.rethrowConsumer(this::deleteChild));
}

The UtilException helper class below lets you use any checked exceptions in Java streams. Note the stream above also throws the original checked exception thrown by this::deleteChild, and not some wrapping unchecked exception.

public final class UtilException {

    @FunctionalInterface
    public interface Consumer_WithExceptions<T, E extends Exception> {
        void accept(T t) throws E;
    }

    @FunctionalInterface
    public interface BiConsumer_WithExceptions<T, U, E extends Exception> {
        void accept(T t, U u) throws E;
    }

    @FunctionalInterface
    public interface Function_WithExceptions<T, R, E extends Exception> {
        R apply(T t) throws E;
    }

    @FunctionalInterface
    public interface Supplier_WithExceptions<T, E extends Exception> {
        T get() throws E;
    }

    @FunctionalInterface
    public interface Runnable_WithExceptions<E extends Exception> {
        void run() throws E;
    }

    /** .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name)))); or .forEach(rethrowConsumer(ClassNameUtil::println)); */
    public static <T, E extends Exception> Consumer<T> rethrowConsumer(Consumer_WithExceptions<T, E> consumer) throws E {
        return t -> {
            try { consumer.accept(t); }
            catch (Exception exception) { throwAsUnchecked(exception); }
        };
    }

    public static <T, U, E extends Exception> BiConsumer<T, U> rethrowBiConsumer(BiConsumer_WithExceptions<T, U, E> biConsumer) throws E {
        return (t, u) -> {
            try { biConsumer.accept(t, u); }
            catch (Exception exception) { throwAsUnchecked(exception); }
        };
    }

    /** .map(rethrowFunction(name -> Class.forName(name))) or .map(rethrowFunction(Class::forName)) */
    public static <T, R, E extends Exception> Function<T, R> rethrowFunction(Function_WithExceptions<T, R, E> function) throws E {
        return t -> {
            try { return function.apply(t); }
            catch (Exception exception) { throwAsUnchecked(exception); return null; }
        };
    }

    /** rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))), */
    public static <T, E extends Exception> Supplier<T> rethrowSupplier(Supplier_WithExceptions<T, E> function) throws E {
        return () -> {
            try { return function.get(); }
            catch (Exception exception) { throwAsUnchecked(exception); return null; }
        };
    }

    /** uncheck(() -> Class.forName("xxx")); */
    public static void uncheck(Runnable_WithExceptions t) {
        try { t.run(); }
        catch (Exception exception) { throwAsUnchecked(exception); }
    }

    /** uncheck(() -> Class.forName("xxx")); */
    public static <R, E extends Exception> R uncheck(Supplier_WithExceptions<R, E> supplier) {
        try { return supplier.get(); }
        catch (Exception exception) { throwAsUnchecked(exception); return null; }
    }

    /** uncheck(Class::forName, "xxx"); */
    public static <T, R, E extends Exception> R uncheck(Function_WithExceptions<T, R, E> function, T t) {
        try { return function.apply(t); }
        catch (Exception exception) { throwAsUnchecked(exception); return null; }
    }

    @SuppressWarnings ("unchecked")
    private static <E extends Throwable> void throwAsUnchecked(Exception exception) throws E { throw (E)exception; }

}

Many other examples on how to use it (after statically importing UtilException):

@Test
public void test_Consumer_with_checked_exceptions() throws IllegalAccessException {
    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .forEach(rethrowConsumer(className -> System.out.println(Class.forName(className))));

    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .forEach(rethrowConsumer(System.out::println));
}

@Test
public void test_Function_with_checked_exceptions() throws ClassNotFoundException {
    List<Class> classes1
          = Stream.of("Object", "Integer", "String")
                  .map(rethrowFunction(className -> Class.forName("java.lang." + className)))
                  .collect(Collectors.toList());

    List<Class> classes2
          = Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
                  .map(rethrowFunction(Class::forName))
                  .collect(Collectors.toList());
}

@Test
public void test_Supplier_with_checked_exceptions() throws ClassNotFoundException {
    Collector.of(
          rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))),
          StringJoiner::add, StringJoiner::merge, StringJoiner::toString);
}

@Test    
public void test_uncheck_exception_thrown_by_method() {
    Class clazz1 = uncheck(() -> Class.forName("java.lang.String"));

    Class clazz2 = uncheck(Class::forName, "java.lang.String");
}

@Test (expected = ClassNotFoundException.class)
public void test_if_correct_exception_is_still_thrown_by_method() {
    Class clazz3 = uncheck(Class::forName, "INVALID");
}

Do not use it before understanding the following advantages, disadvantages, and limitations:

  • If the calling-code is to handle the checked exception you must add it to the throws clause of the method that contains the stream. The compiler will not force you to add it anymore, so it's easier to forget it.

  • If the calling-code already handles the checked exception, the compiler will remind you to add the throws clause to the method declaration that contains the stream (if you don't it will say: Exception is never thrown in body of corresponding try statement).

  • In any case, you won't be able to surround the stream itself to catch the checked exception inside the method that contains the stream (if you try, the compiler will say: Exception is never thrown in body of corresponding try statement).

  • If you are calling a method which literally can never throw the exception that it declares, then you should not include the throws clause.

    For example: new String(byteArr, "UTF-8") throws UnsupportedEncodingException, but UTF-8 is guaranteed by the Java spec to always be present. Here, the throws declaration is a nuisance and any solution to silence it with minimal boilerplate is welcome.

  • If you hate checked exceptions and feel they should never be added to the Java language to begin with (a growing number of people think this way, and I am not one of them), then just don't add the checked exception to the throws clause of the method that contains the stream. The checked exception will, then, behave just like an unchecked exception.

  • If you are implementing a strict interface where you don't have the option for adding a throws declaration, and yet throwing an exception is entirely appropriate, then wrapping an exception just to gain the privilege of throwing it results in a stacktrace with spurious exceptions which contribute no information about what actually went wrong. A good example is Runnable.run(), which does not throw any checked exceptions. In this case, you may decide not to add the checked exception to the throws clause of the method that contains the stream.

  • In any case, if you decide not to add (or forget to add) the checked exception to the throws clause of the method that contains the stream, be aware of these two consequences of throwing checked exceptions:

    1. The calling-code won't be able to catch it by name (if you try, the compiler will say: Exception is never thrown in body of corresponding try statement). It will bubble and probably be caught in the main program loop by some "catch Exception" or "catch Throwable", which may be what you want anyway.

    2. It violates the principle of least surprise: it will no longer be enough to catch RuntimeException to be able to guarantee catching all possible exceptions. For this reason, I believe this should not be done in framework code, but only in business code that you completely control.

In conclusion, I believe the limitations here are not serious, and the UtilException class may be used without fear. However, it's up to you!


References:

Reitz answered 26/12, 2014 at 20:12 Comment(0)
C
1

Check out the library Throwing Function:

By applying com.pivovarit.function functional interfaces, it's possible to regain clarity and readability:

ThrowingFunction<String, URI, URISyntaxException> toUri = URI::new;

and use them seamlessly with native java.util.function classes by using custom ThrowingFunction#unchecked adapters:

...stream()
  .map(unchecked(URI::new)) // static import of ThrowingFunction#unchecked
  .forEach(System.out::println);
Codycoe answered 16/1, 2020 at 16:24 Comment(0)
S
1

Please note that parallel stream will continue executing the elements though there is exception thrown.

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Stream;

public class ThrowingConsumerTest {

    public static void main(String[] args) throws IOException {

        List<Integer> myIntegerList = new ArrayList<>();
        myIntegerList.add(1);
        myIntegerList.add(2);
        myIntegerList.add(3);
        myIntegerList.add(null);
        myIntegerList.add(4);
        myIntegerList.add(5);
        myIntegerList.add(6);
        myIntegerList.add(7);
        myIntegerList.add(8);
        myIntegerList.add(9);
        myIntegerList.add(10);
        myIntegerList.add(11);
        myIntegerList.add(12);
        myIntegerList.add(13);
        myIntegerList.add(14);
        myIntegerList.add(15);
        myIntegerList.add(16);
        myIntegerList.add(17);
        myIntegerList.add(18);
        myIntegerList.add(19);
        forEach(myIntegerList.stream(), ThrowingConsumerTest::exceptionThrowingConsumerCode);
    }

    /**
     * Wrapper that converts Checked Exception to Runtime Exception
     */
    static <T, E extends Exception> Consumer<T> unchecked(ThrowingConsumer<T, E> consumer) {

        return (t) -> {
            try {
                consumer.accept(t);
            } catch (Throwable e) {
                //Lambda can return only RuntimeException.
                RuntimeException ex = new RuntimeException();
                ex.addSuppressed(e);
                throw ex;
            }
        };
    }

    /**
     * Wrapper that converts Runtime Exception to Checked Exception
     * Custom forEach; to accept the exception throwing consumer.
     */
    @SuppressWarnings("unchecked")
    static <T, E extends Exception> void forEach(Stream<T> s, ThrowingConsumer<T, E> consumer) throws E {

        try {

            s.parallel().forEach(unchecked(t -> consumer.accept(t)));
        } catch (RuntimeException e) {
            //Checked Exception can be return from here
            throw (E) e.getSuppressed()[0];
        }
    }

    /*
     * Consumer that throws Exception
     */
    @FunctionalInterface
    public interface ThrowingConsumer<T, E extends Exception> {
        void accept(T t) throws E;
    }

    static void exceptionThrowingConsumerCode(Object i) throws IOException {
        if (i == null) {
            throw new IOException();

        } else {
            System.out.println(i);
        }
    }
}
Straightjacket answered 28/2, 2020 at 12:37 Comment(0)
N
0

You may also declare someException so that it extends RuntimeException instead of Exception. The following example code will compile:

public class Test {

    public static void main(String[] args){
        // TODO Auto-generated method stub
        List<String> test = new ArrayList<String>();
        test.add("foo");
        test.add(null);
        test.add("bar");
        test.forEach(x -> print(x));    
    }

    public static class SomeException extends RuntimeException{
    }

    public static void print(String s) throws SomeException{
        if (s==null) throw new SomeException();
        System.out.println(s);
    }
}

The output will then be:

foo
Exception in thread "main" simpleTextLayout.Test$SomeException
at simpleTextLayout.Test.print(Test.java:22)
at simpleTextLayout.Test.lambda$0(Test.java:14)
at java.util.ArrayList.forEach(ArrayList.java:1249)
at simpleTextLayout.Test.main(Test.java:14)

You can add a try/catch block around the forEach statement, however the execution of the forEach statement will be interrupted once an exception is thrown. In the above example, the "bar" element of the list will not be printed. Also, by doing that, you will lose track of the thrown exception in your IDE.

Nutriment answered 10/9, 2016 at 23:29 Comment(0)
S
0

**If you don't want to write your own Consumer Interface and make use of it. You can use your Custom Exception with ease as shown below. You can perform like below. **

list.stream().forEach(x->{
try{
System.out.println(x/0);
}catch(ArithmeticException e){
throw new RuntimeException(new MyCustomException(FirstArgument,SecondArgument));
});
Stewart answered 2/5, 2018 at 13:8 Comment(0)
G
-1

Use generic magic:

public class TempTest {
    @FunctionalInterface
    public interface ConsumerEX<T> extends Consumer<T> {
        @Override
        default void accept(T t) {
            acceptEx(t);
        }

        <EX extends Throwable> void acceptEx(T t) throws EX;
    }

    void deleteEntity(Node node) {
        node.getChildren().forEach((ConsumerEx<ChildNode>) this::deleteChild);
    }

    void deleteChild(Object child) throws SomeException {
        //some code
        throw new SomeException();
    }

    // mock class
    private static class Node {
        public List<ChildNode> getChildren() {return Collections.singletonList(new ChildNode());}
    }
    private static class ChildNode {}
    private static class SomeException extends Throwable {}
}
Griqua answered 17/7 at 7:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.