我知道如何创建一个有String参数并返回int的方法的引用,它是:

Function<String, Integer>

然而,如果函数抛出异常,比如它被定义为:

Integer myMethod(String s) throws IOException

我该如何定义这个引用呢?


当前回答

你可以。

扩展@marcg的UtilException,并在必要的地方添加泛型<E extends Exception>:这样,编译器将强制您再次添加抛出子句,一切就像您可以在java 8的流上本机抛出受控异常一样。

public final class LambdaExceptionUtil {

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

    /**
     * .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) {
                throwActualException(exception);
                return null;
            }
        };
    }

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

}

public class LambdaExceptionUtilTest {

    @Test
    public void testFunction() throws MyTestException {
        List<Integer> sizes = Stream.of("ciao", "hello").<Integer>map(rethrowFunction(s -> transform(s))).collect(toList());
        assertEquals(2, sizes.size());
        assertEquals(4, sizes.get(0).intValue());
        assertEquals(5, sizes.get(1).intValue());
    }

    private Integer transform(String value) throws MyTestException {
        if(value==null) {
            throw new MyTestException();
        }
        return value.length();
    }

    private static class MyTestException extends Exception { }
}

其他回答

这里已经贴出了很多很棒的回复。只是试图用不同的角度来解决问题。这只是我的两毛钱,如果我哪里说错了,请指正。

在FunctionalInterface中抛出子句不是一个好主意

我认为强制抛出IOException可能不是一个好主意,原因如下

This looks to me like an anti-pattern to Stream/Lambda. The whole idea is that the caller will decide what code to provide and how to handle the exception. In many scenarios, the IOException might not be applicable for the client. For example, if the client is getting value from cache/memory instead of performing actual I/O. Also, the exceptions handling in streams becomes really hideous. For example, here is my code will look like if I use your API acceptMyMethod(s -> { try { Integer i = doSomeOperation(s); return i; } catch (IOException e) { // try catch block because of throws clause // in functional method, even though doSomeOperation // might not be throwing any exception at all. e.printStackTrace(); } return null; }); Ugly isn't it? Moreover, as I mentioned in my first point, that the doSomeOperation method may or may not be throwing IOException (depending on the implementation of the client/caller), but because of the throws clause in your FunctionalInterface method, I always have to write the try-catch.

如果我知道这个API抛出IOException怎么办

Then probably we are confusing FunctionalInterface with typical Interfaces. If you know this API will throw IOException, then most probably you also know some default/abstract behavior as well. I think you should define an interface and deploy your library (with default/abstract implementation) as follows public interface MyAmazingAPI { Integer myMethod(String s) throws IOException; } But, the try-catch problem still exists for the client. If I use your API in stream, I still need to handle IOException in hideous try-catch block. Provide a default stream-friendly API as follows public interface MyAmazingAPI { Integer myMethod(String s) throws IOException; default Optional<Integer> myMethod(String s, Consumer<? super Exception> exceptionConsumer) { try { return Optional.ofNullable(this.myMethod(s)); } catch (Exception e) { if (exceptionConsumer != null) { exceptionConsumer.accept(e); } else { e.printStackTrace(); } } return Optional.empty(); } } The default method takes the consumer object as argument, which will be responsible to handle the exception. Now, from client's point of view, the code will look like this strStream.map(str -> amazingAPIs.myMethod(str, Exception::printStackTrace)) .filter(Optional::isPresent) .map(Optional::get).collect(toList()); Nice right? Of course, logger or other handling logic could be used instead of Exception::printStackTrace. You can also expose a method similar to https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html#exceptionally-java.util.function.Function- . Meaning that you can expose another method, which will contain the exception from previous method call. The disadvantage is that you are now making your APIs stateful, which means that you need to handle thread-safety and which will be eventually become a performance hit. Just an option to consider though.

public void frankTest() {
    int pageId= -1;

    List<Book> users= null;
    try {
        //Does Not Compile:  Object page=DatabaseConnection.getSpringConnection().queryForObject("SELECT * FROM bookmark_page", (rw, n) -> new Portal(rw.getInt("id"), "", users.parallelStream().filter(uu -> uu.getVbid() == rw.getString("user_id")).findFirst().get(), rw.getString("name")));

        //Compiles:
        Object page= DatabaseConnection.getSpringConnection().queryForObject("SELECT * FROM bookmark_page", (rw, n) -> { 
            try {
                final Book bk= users.stream().filter(bp -> { 
                    String name= null;
                    try {
                        name = rw.getString("name");
                    } catch (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    return bp.getTitle().equals(name); 
                }).limit(1).collect(Collectors.toList()).get(0);
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            return new Portal(rw.getInt("id"), "", users.get(0), rw.getString("name")); 
        } );
    } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

免责声明:我还没有使用过Java 8,只是阅读过它。

Function<String, Integer>不抛出IOException,所以你不能在其中放入任何抛出IOException的代码。如果你正在调用一个期望Function<String, Integer>的方法,那么你传递给该方法的lambda不能抛出IOException,句号。你可以这样写一个lambda(我认为这是lambda语法,不确定):

(String s) -> {
    try {
        return myMethod(s);
    } catch (IOException ex) {
        throw new RuntimeException(ex);
        // (Or do something else with it...)
    }
}

或者,如果你传递lambda的方法是你自己写的,你可以定义一个新的函数接口,并使用它作为参数类型,而不是Function<String, Integer>:

public interface FunctionThatThrowsIOException<I, O> {
    O apply(I input) throws IOException;
}

对我来说,首选的解决方案是使用Lombok。这是一个很好的图书馆。

而不是:

Integer myMethod(String s) throws IOException

你会有

import lombok.SneakyThrows;

@SneakyThrows
Integer myMethod(String s)

异常仍然被抛出,但您不需要使用throws声明它。

可以使用取消抛出包装器

Function<String, Integer> func1 = s -> Unthrow.wrap(() -> myMethod(s));

or

Function<String, Integer> func2 = s1 -> Unthrow.wrap((s2) -> myMethod(s2), s1);