抛出异常的Java 8 Lambda函数?

Tri*_*Man 438 java lambda java-8

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

Function<String, Integer>
Run Code Online (Sandbox Code Playgroud)

但是,如果函数抛出异常,这不起作用,比如说定义为:

Integer myMethod(String s) throws IOException
Run Code Online (Sandbox Code Playgroud)

我该如何定义这个参考?

jas*_*son 382

您需要执行以下操作之一.

  • 哇。Java比我想象的更糟糕 (30认同)
  • 如果使用默认方法,您实际上可以扩展`Consumer`或`Function` - 请参阅下面的答案. (7认同)
  • 更通用的方法是定义像这样的checked函数@FunctionalInterface public interface CheckedFunction <T,R,E extends Exception> {R apply(T t)throws E; 通过这种方式,您还可以定义函数抛出的异常,并可以为任何代码重用该接口. (7认同)
  • 次优化:也可以使用方法引用`this :: myWrappedMethod`而不是`(String t) - > myWrappedMethod(t)`. (5认同)
  • 我认为这可以作为[one-liner](http://stackoverflow.com/a/30246026/1153071)来完成. (2认同)

jlb*_*jlb 186

实际上,您可以使用Java 8的默认方法扩展Consumer(Function等等)处理异常的新接口!

考虑这个接口(扩展Consumer):

@FunctionalInterface
public interface ThrowingConsumer<T> extends Consumer<T> {

    @Override
    default void accept(final T elem) {
        try {
            acceptThrows(elem);
        } catch (final Exception e) {
            // Implement your own exception handling logic here..
            // For example:
            System.out.println("handling an exception...");
            // Or ...
            throw new RuntimeException(e);
        }
    }

    void acceptThrows(T elem) throws Exception;

}
Run Code Online (Sandbox Code Playgroud)

然后,例如,如果您有一个列表:

final List<String> list = Arrays.asList("A", "B", "C");
Run Code Online (Sandbox Code Playgroud)

如果你想forEach使用一些抛出异常的代码来使用它(例如with ),你通常会设置一个try/catch块:

final Consumer<String> consumer = aps -> {
    try {
        // maybe some other code here...
        throw new Exception("asdas");
    } catch (final Exception ex) {
        System.out.println("handling an exception...");
    }
};
list.forEach(consumer);
Run Code Online (Sandbox Code Playgroud)

但是使用这个新接口,您可以使用lambda表达式对其进行实例化,编译器不会抱怨:

final ThrowingConsumer<String> throwingConsumer = aps -> {
    // maybe some other code here...
    throw new Exception("asdas");
};
list.forEach(throwingConsumer);
Run Code Online (Sandbox Code Playgroud)

或者甚至只是把它变得更简洁!:

list.forEach((ThrowingConsumer<String>) aps -> {
    // maybe some other code here...
    throw new Exception("asda");
});
Run Code Online (Sandbox Code Playgroud)

更新:看起来有一个非常好的Durian实用程序库部分名为Errors,它可以用来更灵活地解决这个问题.例如,在上面的实现中,我明确定义了错误处理策略(System.out...throw RuntimeException),而Durian的错误允许您通过一大套实用程序方法动态应用策略.感谢分享,@ NedTwigg!

样品用法:

list.forEach(Errors.rethrow().wrap(c -> somethingThatThrows(c)));
Run Code Online (Sandbox Code Playgroud)

  • 因此,您有一组接口(函数,消费者,供应商......)和一组用于处理错误的策略(Throwing,System.out.println,...).我认为[有一种方法](http://stackoverflow.com/a/30246026/1153071)可以轻松使用任何类型功能的任何策略,而无需复制粘贴"ThrowingConsumer,ThrowingFunction等". (13认同)
  • 一段时间后...我决定使用未经检查的异常,而不使用任何附加的功能接口或新库 - &gt;简单的道路,更少的打字,更快的交付,不是吗。 (2认同)

Ned*_*igg 59

我认为榴莲的Errors课程结合了上述各种建议的许多优点.

要在项目中包含榴莲,您可以:

  • 榴莲的维护者在这里。怎么了 如果用户发现错误或重要的缺少功能,我们将迅速发布错误修正。该库很简单,因此我们没有任何错误报告,因此我们不需要发布任何错误修正。 (3认同)
  • 请注意,[Durian](https://mvnrepository.com/search?q=durian) 自 2016 年 6 月以来没有新版本。不是表演障碍,而是要记住的事情。 (2认同)

ass*_*ias 27

这不是特定于Java 8.您正在尝试编译等效于:

interface I {
    void m();
}
class C implements I {
    public void m() throws Exception {} //can't compile
}
Run Code Online (Sandbox Code Playgroud)

  • 问题是_"我如何定义此引用?"_.这实际上并没有回答这个问题; 它只是澄清了问题所在. (14认同)

Ada*_*son 13

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

Function<String, Integer>不扔IOException,所以你不能把任何代码放在其中throws IOException.如果你正在调用一个期望a的方法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...)
    }
}
Run Code Online (Sandbox Code Playgroud)

或者,如果您传递lambda的方法是您自己编写的方法,则可以定义新的功能接口并将其用作参数类型而不是Function<String, Integer>:

public interface FunctionThatThrowsIOException<I, O> {
    O apply(I input) throws IOException;
}
Run Code Online (Sandbox Code Playgroud)

  • @Gangnus:`@ FunctionalInterface`注释不需要它可用于lambdas.虽然建议进行健全性检查. (10认同)

Dan*_*ich 8

如果您不介意使用第三方库(Vavr),您可以写

CheckedFunction1<String, Integer> f = this::myMethod;
Run Code Online (Sandbox Code Playgroud)

它还有所谓的Try monad来处理错误:

Try(() -> f.apply("test")) // results in a Success(Integer) or Failure(Throwable)
        .map(i -> ...) // only executed on Success
        ...
Run Code Online (Sandbox Code Playgroud)

在这里阅读更多.

免责声明:我是Vavr的创始人.


小智 7

你可以使用unthrow包装

Function<String, Integer> func1 = s -> Unthrow.wrap(() -> myMethod(s));
Run Code Online (Sandbox Code Playgroud)

要么

Function<String, Integer> func2 = s1 -> Unthrow.wrap((s2) -> myMethod(s2), s1);
Run Code Online (Sandbox Code Playgroud)


myu*_*yui 7

Sneaky throw 习惯用法可以绕过CheckedExceptionLambda 表达式。将 a 包装CheckedException在 aRuntimeException中不利于严格的错误处理。

它可以用作ConsumerJava 集合中使用的函数。

这是jib's answer的简单改进版本。

import static Throwing.rethrow;

@Test
public void testRethrow() {
    thrown.expect(IOException.class);
    thrown.expectMessage("i=3");

    Arrays.asList(1, 2, 3).forEach(rethrow(e -> {
        int i = e.intValue();
        if (i == 3) {
            throw new IOException("i=" + i);
        }
    }));
}
Run Code Online (Sandbox Code Playgroud)

这只是wrapps在拉姆达重新抛出。它会CheckedException重新Exception抛出在你的 lambda 中抛出的任何内容。

public final class Throwing {
    private Throwing() {}

    @Nonnull
    public static <T> Consumer<T> rethrow(@Nonnull final ThrowingConsumer<T> consumer) {
        return consumer;
    }

    /**
     * The compiler sees the signature with the throws T inferred to a RuntimeException type, so it
     * allows the unchecked exception to propagate.
     * 
     * http://www.baeldung.com/java-sneaky-throws
     */
    @SuppressWarnings("unchecked")
    @Nonnull
    public static <E extends Throwable> void sneakyThrow(@Nonnull Throwable ex) throws E {
        throw (E) ex;
    }

}
Run Code Online (Sandbox Code Playgroud)

在这里找到完整的代码和单元测试。


Joh*_*nyO 6

但是,您可以创建自己的FunctionalInterface,如下所示。

@FunctionalInterface
public interface UseInstance<T, X extends Throwable> {
  void accept(T instance) throws X;
}
Run Code Online (Sandbox Code Playgroud)

然后使用Lambdas或引用实现它,如下所示。

import java.io.FileWriter;
import java.io.IOException;

//lambda expressions and the execute around method (EAM) pattern to
//manage resources

public class FileWriterEAM  {
  private final FileWriter writer;

  private FileWriterEAM(final String fileName) throws IOException {
    writer = new FileWriter(fileName);
  }
  private void close() throws IOException {
    System.out.println("close called automatically...");
    writer.close();
  }
  public void writeStuff(final String message) throws IOException {
    writer.write(message);
  }
  //...

  public static void use(final String fileName, final UseInstance<FileWriterEAM, IOException> block) throws IOException {

    final FileWriterEAM writerEAM = new FileWriterEAM(fileName);    
    try {
      block.accept(writerEAM);
    } finally {
      writerEAM.close();
    }
  }

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

    FileWriterEAM.use("eam.txt", writerEAM -> writerEAM.writeStuff("sweet"));

    FileWriterEAM.use("eam2.txt", writerEAM -> {
        writerEAM.writeStuff("how");
        writerEAM.writeStuff("sweet");      
      });

    FileWriterEAM.use("eam3.txt", FileWriterEAM::writeIt);     

  }


 void writeIt() throws IOException{
     this.writeStuff("How ");
     this.writeStuff("sweet ");
     this.writeStuff("it is");

 }

}
Run Code Online (Sandbox Code Playgroud)


Pro*_*mer 6

这里已经发布了很多精彩的回复。只是试图从不同的角度来解决问题。这只是我的2分钱,如果我有错误的地方请纠正我。

FunctionInterface 中的 throws 子句不是一个好主意

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

  • 在我看来,这就像 Stream/Lambda 的反模式。整个想法是调用者将决定提供什么代码以及如何处理异常。在许多情况下,IOException 可能不适用于客户端。例如,如果客户端从缓存/内存获取值而不是执行实际 I/O。

  • 此外,流中的异常处理变得非常可怕。例如,如果我使用你们的 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;
                });
    
    Run Code Online (Sandbox Code Playgroud)

    丑陋不是吗?此外,正如我在第一点中提到的, doSomeOperation 方法可能会也可能不会抛出 IOException (取决于客户端/调用者的实现),但由于您的FunctionalInterface 方法中的 throws 子句,我总是必须编写试着抓。

如果我确实知道这个 API 会抛出 IOException,我该怎么办

  • 那么我们可能会将FunctionalInterface 与典型接口混淆。如果您知道这个 API 会抛出 IOException,那么您很可能也知道一些默认/抽象行为。我认为您应该定义一个接口并部署您的库(使用默认/抽象实现),如下所示

    public interface MyAmazingAPI {
        Integer myMethod(String s) throws IOException;
    }
    
    Run Code Online (Sandbox Code Playgroud)

    但是,对于客户端来说,try-catch 问题仍然存在。如果我在流中使用你的 API,我仍然需要在可怕的 try-catch 块中处理 IOException。

  • 提供默认的流友好API如下

    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();
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    默认方法以消费者对象作为参数,该对象将负责处理异常。现在,从客户的角度来看,代码将如下所示

    strStream.map(str -> amazingAPIs.myMethod(str, Exception::printStackTrace))
                    .filter(Optional::isPresent)
                    .map(Optional::get).collect(toList());
    
    Run Code Online (Sandbox Code Playgroud)

    很好吧?当然,可以使用记录器或其他处理逻辑来代替 Exception::printStackTrace。

  • 您还可以公开类似于https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html#exceptionally-java.util.function.Function-的方法 。这意味着您可以公开另一个方法,该方法将包含先前方法调用的异常。缺点是您现在正在使 API 有状态,这意味着您需要处理线程安全性,这最终会影响性能。只是一个值得考虑的选择。


yoh*_*han 5

使用函数包装器的另一个解决方案是返回结果的包装器实例(例如成功),如果一切顺利,或者返回一个实例(例如失败)。

一些代码来澄清事情:

public interface ThrowableFunction<A, B> {
    B apply(A a) throws Exception;
}

public abstract class Try<A> {

    public static boolean isSuccess(Try tryy) {
        return tryy instanceof Success;
    }

    public static <A, B> Function<A, Try<B>> tryOf(ThrowableFunction<A, B> function) {
        return a -> {
            try {
                B result = function.apply(a);
                return new Success<B>(result);
            } catch (Exception e) {
                return new Failure<>(e);
            }
        };
    }

    public abstract boolean isSuccess();

    public boolean isError() {
        return !isSuccess();
    }

    public abstract A getResult();

    public abstract Exception getError();
}

public class Success<A> extends Try<A> {

    private final A result;

    public Success(A result) {
        this.result = result;
    }

    @Override
    public boolean isSuccess() {
        return true;
    }

    @Override
    public A getResult() {
        return result;
    }

    @Override
    public Exception getError() {
        return new UnsupportedOperationException();
    }

    @Override
    public boolean equals(Object that) {
        if(!(that instanceof Success)) {
            return false;
        }
        return Objects.equal(result, ((Success) that).getResult());
    }
}

public class Failure<A> extends Try<A> {

    private final Exception exception;

    public Failure(Exception exception) {
        this.exception = exception;
    }

    @Override
    public boolean isSuccess() {
        return false;
    }

    @Override
    public A getResult() {
        throw new UnsupportedOperationException();
    }

    @Override
    public Exception getError() {
        return exception;
    }
}
Run Code Online (Sandbox Code Playgroud)

一个简单的用例:

List<Try<Integer>> result = Lists.newArrayList(1, 2, 3).stream().
    map(Try.<Integer, Integer>tryOf(i -> someMethodThrowingAnException(i))).
    collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)


Ser*_*gio 5

我在lambda中遇到了Class.forName和Class.newInstance的问题,所以我做到了:

public Object uncheckedNewInstanceForName (String name) {

    try {
        return Class.forName(name).newInstance();
    }
    catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
        throw new RuntimeException(e);
    }
}
Run Code Online (Sandbox Code Playgroud)

在lambda内部,没有调用Class.forName(“ myClass”)。newInstance(),而是调用了uncheckedNewInstanceForName(“ myClass”)


Pao*_*loC 5

您可以.

扩展@marcg UtilException<E extends Exception>在必要时添加泛型:这样,编译器将强制您再次添加throw子句和所有内容,就像您可以在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 { }
}
Run Code Online (Sandbox Code Playgroud)


jus*_*hey 5

创建将传播已检查异常的自定义返回类型。这是创建一个镜像现有功能接口的新接口的替代方法,只需对功能接口的方法上的“抛出异常”进行轻微修改即可。

定义

已检查值供应商

public static interface CheckedValueSupplier<V> {
    public V get () throws Exception;
}
Run Code Online (Sandbox Code Playgroud)

检查值

public class CheckedValue<V> {
    private final V v;
    private final Optional<Exception> opt;

    public Value (V v) {
        this.v = v;
    }

    public Value (Exception e) {
        this.opt = Optional.of(e);
    }

    public V get () throws Exception {
        if (opt.isPresent()) {
            throw opt.get();
        }
        return v;
    }

    public Optional<Exception> getException () {
        return opt;
    }

    public static <T> CheckedValue<T> returns (T t) {
        return new CheckedValue<T>(t);
    }

    public static <T> CheckedValue<T> rethrows (Exception e) {
        return new CheckedValue<T>(e);
    }

    public static <V> CheckedValue<V> from (CheckedValueSupplier<V> sup) {
        try {
            return CheckedValue.returns(sup.get());
        } catch (Exception e) {
            return Result.rethrows(e);
        }
    }

    public static <V> CheckedValue<V> escalates (CheckedValueSupplier<V> sup) {
        try {
            return CheckedValue.returns(sup.get());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

用法

//  Don't use this pattern with FileReader, it's meant to be an
//  example.  FileReader is a Closeable resource and as such should
//  be managed in a try-with-resources block or in another safe
//  manner that will make sure it is closed properly.

//  This will not compile as the FileReader constructor throws
//  an IOException.
    Function<String, FileReader> sToFr =
        (fn) -> new FileReader(Paths.get(fn).toFile());

// Alternative, this will compile.
    Function<String, CheckedValue<FileReader>> sToFr = (fn) -> {
        return CheckedValue.from (
            () -> new FileReader(Paths.get("/home/" + f).toFile()));
    };

// Single record usage
    // The call to get() will propagate the checked exception if it exists.
    FileReader readMe = pToFr.apply("/home/README").get();


// List of records usage
    List<String> paths = ...; //a list of paths to files
    Collection<CheckedValue<FileReader>> frs =
        paths.stream().map(pToFr).collect(Collectors.toList());

// Find out if creation of a file reader failed.
    boolean anyErrors = frs.stream()
        .filter(f -> f.getException().isPresent())
        .findAny().isPresent();
Run Code Online (Sandbox Code Playgroud)

这是怎么回事?

创建抛出受检查异常的单个功能接口 ( CheckedValueSupplier)。这将是唯一允许检查异常的功能接口。所有其他功能接口将利用CheckedValueSupplier来包装任何引发已检查异常的代码。

该类CheckedValue将保存执行引发已检查异常的任何逻辑的结果。这可以防止已检查异常的传播,直到代码尝试访问 实例包含的值为止CheckedValue

这种方法的问题。

  • 我们现在抛出“异常”,有效地隐藏了最初抛出的特定类型。
  • CheckedValue#get()在调用之前我们不知道发生了异常。

消费者等

某些功能接口(Consumer例如)必须以不同的方式处理,因为它们不提供返回值。

代替 Consumer 的函数

一种方法是使用函数而不是使用者,这在处理流时适用。

    List<String> lst = Lists.newArrayList();
// won't compile
lst.stream().forEach(e -> throwyMethod(e));
// compiles
lst.stream()
    .map(e -> CheckedValueSupplier.from(
        () -> {throwyMethod(e); return e;}))
    .filter(v -> v.getException().isPresent()); //this example may not actually run due to lazy stream behavior
Run Code Online (Sandbox Code Playgroud)

升级

或者,您始终可以升级到RuntimeException. 还有其他答案涵盖了从Consumer.

不要消费。

只需避免使用函数式接口并使用老式的 for 循环即可。