捕获通用异常
Catching a generic exception
问题
我正在 Java 中编写一个 Result
类型,我发现它需要一个方法来执行可能会失败的操作,然后将值或异常封装在一个新的结果对象。
我曾希望这会奏效:
@FunctionalInterface
public interface ThrowingSupplier<R, E extends Throwable>
{
R get() throws E;
}
public class Result<E extends Throwable, V>
{
...
public static <E extends Throwable, V> Result<E, V> of(ThrowingSupplier<V, E> v)
{
try
{
return value(v.get());
}
catch(E e)
{
return error(e);
}
}
...
}
但是Java 无法捕获由类型参数定义的异常。
我也尝试过使用 instanceof
,但这也不能用于泛型。有什么办法可以实现这个方法吗?
定义
这是我添加of
方法之前的结果类型。它旨在类似于 Haskell's Either
and rust's Result
, while also having a meaningful bind
operation:
public class Result<E extends Throwable, V>
{
private Either<E, V> value;
private Result(Either<E, V> value)
{
this.value = value;
}
public <T> T match(Function<? super E, ? extends T> ef, Function<? super V, ? extends T> vf)
{
return value.match(ef, vf);
}
public void match(Consumer<? super E> ef, Consumer<? super V> vf)
{
value.match(ef, vf);
}
/**
* Mirror of haskell's Monadic (>>=)
*/
public <T> Result<E, T> bind(Function<? super V, Result<? extends E, ? extends T>> f)
{
return match(
(E e) -> cast(error(e)),
(V v) -> cast(f.apply(v))
);
}
/**
* Mirror of Haskell's Monadic (>>) or Applicative (*>)
*/
public <T> Result<E, T> then(Supplier<Result<? extends E, ? extends T>> f)
{
return bind((__) -> f.get());
}
/**
* Mirror of haskell's Applicative (<*)
*/
public Result<E, V> peek(Function<? super V, Result<? extends E, ?>> f)
{
return bind(v -> f.apply(v).then(() -> value(v)));
}
public <T> Result<E, T> map(Function<? super V, ? extends T> f)
{
return match(
(E e) -> error(e),
(V v) -> value(f.apply(v))
);
}
public static <E extends Throwable, V> Result<E, V> error(E e)
{
return new Result<>(Either.left(e));
}
public static <E extends Throwable, V> Result<E, V> value(V v)
{
return new Result<>(Either.right(v));
}
/**
* If the result is a value, return it.
* If it is an exception, throw it.
*
* @return the contained value
* @throws E the contained exception
*/
public V get() throws E
{
boolean has = match(
e -> false,
v -> true
);
if (has)
{
return value.fromRight(null);
}
else
{
throw value.fromLeft(null);
}
}
/**
* Upcast the Result's type parameters
*/
private static <E extends Throwable, V> Result<E, V> cast(Result<? extends E, ? extends V> r)
{
return r.match(
(E e) -> error(e),
(V v) -> value(v)
);
}
}
和 Either
类型,旨在紧密反映 Haskell's Either
:
/**
* A container for a disjunction of two possible types
* By convention, the Left constructor is used to hold an error value and the Right constructor is used to hold a correct value
* @param <L> The left alternative type
* @param <R> The right alternative type
*/
public abstract class Either<L, R>
{
public abstract <T> T match(Function<? super L, ? extends T> lf, Function<? super R, ? extends T> rf);
public abstract void match(Consumer<? super L> lf, Consumer<? super R> rf);
public <A, B> Either<A, B> bimap(Function<? super L, ? extends A> lf, Function<? super R, ? extends B> rf)
{
return match(
(L l) -> left(lf.apply(l)),
(R r) -> right(rf.apply(r))
);
}
public L fromLeft(L left)
{
return match(
(L l) -> l,
(R r) -> left
);
}
public R fromRight(R right)
{
return match(
(L l) -> right,
(R r) -> r
);
}
public static <L, R> Either<L, R> left(L value)
{
return new Left<>(value);
}
public static <L, R> Either<L, R> right(R value)
{
return new Right<>(value);
}
private static <L, R> Either<L, R> cast(Either<? extends L, ? extends R> either)
{
return either.match(
(L l) -> left(l),
(R r) -> right(r)
);
}
static class Left<L, R> extends Either<L, R>
{
final L value;
Left(L value)
{
this.value = value;
}
@Override
public <T> T match(Function<? super L, ? extends T> lf, Function<? super R, ? extends T> rf)
{
return lf.apply(value);
}
@Override
public void match(Consumer<? super L> lf, Consumer<? super R> rf)
{
lf.accept(value);
}
}
static class Right<L, R> extends Either<L, R>
{
final R value;
Right(R value)
{
this.value = value;
}
@Override
public <T> T match(Function<? super L, ? extends T> lf, Function<? super R, ? extends T> rf)
{
return rf.apply(value);
}
@Override
public void match(Consumer<? super L> lf, Consumer<? super R> rf)
{
rf.accept(value);
}
}
}
用法示例
这个的主要用途是将异常抛出操作转换为单子操作。这允许在流和其他功能上下文中使用(已检查的)异常抛出方法,还允许在 return 类型上进行模式匹配和绑定。
private static void writeFiles(List<String> filenames, String content)
{
filenames.stream()
.map(
(String s) -> Result.of(
() -> new FileWriter(s) //Open file for writing
).peek(
(FileWriter f) -> Result.of(
() -> f.write(content) //Write file contents
)
).peek(
(FileWriter f) -> Result.of(
() -> f.close()) //Close file
)
).forEach(
r -> r.match(
(IOException e) -> System.out.println("exception writing to file: " + e), //Log exception
(FileWriter f) -> System.out.println("successfully written to file '" + f + "'") //Log success
)
);
}
您需要访问异常的 class,然后在 catch 块中使用一些泛型。
一种简单的方法是将 Class<E>
class 传递给 Result.of
方法:
public static <E extends Throwable, V> Result<E, V> of(
ThrowingSupplier<V, E> v,
Class<E> errorType) {
try {
return value(v.get());
} catch(Throwable e) {
if (errorType.isInstance(e)) {
return error(errorType.cast(e));
}
throw new RuntimeException(e); // rethrow as runtime?
}
}
用法:
Result.of(() -> new FileWriter(s), IOException.class)
Class.isInstance
is the dynamic equivalent of the instanceof
static operator, while Class.cast
与静态转换相同:(E) e
,只是我们没有从编译器那里得到警告。
编辑: 您需要考虑当捕获的 Throwable
不是您期望的异常类型时该怎么做。我已将其包装在 RuntimeException
中并重新抛出。这允许继续为您的 monad 使用流畅的样式,但不再透明,因为现在任何异常都包含在未经检查的异常中。也许您可以向 Result.of
添加第三个参数来处理这种特定情况...
更新: 这似乎根本不起作用。我暂时将它保留在这里,因为我已经链接到其他地方,并且因为它使用了其他接受的答案中提供的方法,我想继续调查。
使用评论中链接的 and the answer,我推导出了一个与原始问题具有相同方法签名的解决方案,并且我创建了一个 class 封装了此功能以供将来使用。
Result
实现:
public class Result<E extends Exception, V>
{
...
public static <E extends Exception, V> Result<E, V> of(ThrowingSupplier<V, E> v)
{
try
{
return value(v.get());
}
catch(Exception e)
{
Class<E> errType = Reflector.getType();
if (errType.isInstance(e))
{
return error(errType.cast(e));
}
else
{
throw (RuntimeException) e;
}
}
}
...
}
和 Reflector
:
import java.lang.reflect.ParameterizedType;
/**
* This class only exists to provide a generic superclass to {@link Reflector}
* @param <E> The type for the subclass to inspect
*/
abstract class Reflected<E>
{ }
/**
* This class provides the ability to obtain information about its generic type parameter.
* @param <E> The type to inspect
* @author
*/
@Deprecated
public class Reflector<E> extends Reflected<E>
{
/**
* Returns the class corresponding to the type {@code <E>}.
* @param <E> The type to inspect
* @return The class corresponding to the type {@code <E>}
*/
public static <E> Class<E> getType()
{
return new Reflector<E>().getParameterType();
}
private Reflector() {}
private Class<E> getParameterType()
{
final ParameterizedType type = (ParameterizedType) this.getClass().getGenericSuperclass();
return (Class<E>) type.getActualTypeArguments()[0];
}
}
只需使用接口满足契约的乐观假设,就像普通 Java 代码总是会做的那样(由编译器强制执行)。如果有人绕过了这个异常检查,你没有责任解决这个问题:
public static <E extends Exception, V> Result<E, V> of(ThrowingSupplier<V, E> v) {
try {
return value(v.get());
}
catch(RuntimeException|Error x) {
throw x; // unchecked throwables
}
catch(Exception ex) {
@SuppressWarnings("unchecked") E e = (E)ex;
return error(e);
}
}
请注意,即使 Java 编程语言也同意可以继续这个假设,例如
public static <E extends Exception, V> Result<E, V> of(ThrowingSupplier<V, E> v) throws E {
try {
return value(v.get());
}
catch(RuntimeException|Error x) {
throw x; // unchecked throwables
}
catch(Exception ex) {
throw ex; // can only be E
}
}
是有效的Java代码,因为正常情况下,get
方法只能抛出E
或者unchecked throwables,所以这里重新抛出ex
是有效的, 当 throws E
被声明时。当我们想要构造一个用 E
.
参数化的 Result
时,我们只需要避免 Java 语言的缺陷
问题
我正在 Java 中编写一个 Result
类型,我发现它需要一个方法来执行可能会失败的操作,然后将值或异常封装在一个新的结果对象。
我曾希望这会奏效:
@FunctionalInterface
public interface ThrowingSupplier<R, E extends Throwable>
{
R get() throws E;
}
public class Result<E extends Throwable, V>
{
...
public static <E extends Throwable, V> Result<E, V> of(ThrowingSupplier<V, E> v)
{
try
{
return value(v.get());
}
catch(E e)
{
return error(e);
}
}
...
}
但是Java 无法捕获由类型参数定义的异常。
我也尝试过使用 instanceof
,但这也不能用于泛型。有什么办法可以实现这个方法吗?
定义
这是我添加of
方法之前的结果类型。它旨在类似于 Haskell's Either
and rust's Result
, while also having a meaningful bind
operation:
public class Result<E extends Throwable, V>
{
private Either<E, V> value;
private Result(Either<E, V> value)
{
this.value = value;
}
public <T> T match(Function<? super E, ? extends T> ef, Function<? super V, ? extends T> vf)
{
return value.match(ef, vf);
}
public void match(Consumer<? super E> ef, Consumer<? super V> vf)
{
value.match(ef, vf);
}
/**
* Mirror of haskell's Monadic (>>=)
*/
public <T> Result<E, T> bind(Function<? super V, Result<? extends E, ? extends T>> f)
{
return match(
(E e) -> cast(error(e)),
(V v) -> cast(f.apply(v))
);
}
/**
* Mirror of Haskell's Monadic (>>) or Applicative (*>)
*/
public <T> Result<E, T> then(Supplier<Result<? extends E, ? extends T>> f)
{
return bind((__) -> f.get());
}
/**
* Mirror of haskell's Applicative (<*)
*/
public Result<E, V> peek(Function<? super V, Result<? extends E, ?>> f)
{
return bind(v -> f.apply(v).then(() -> value(v)));
}
public <T> Result<E, T> map(Function<? super V, ? extends T> f)
{
return match(
(E e) -> error(e),
(V v) -> value(f.apply(v))
);
}
public static <E extends Throwable, V> Result<E, V> error(E e)
{
return new Result<>(Either.left(e));
}
public static <E extends Throwable, V> Result<E, V> value(V v)
{
return new Result<>(Either.right(v));
}
/**
* If the result is a value, return it.
* If it is an exception, throw it.
*
* @return the contained value
* @throws E the contained exception
*/
public V get() throws E
{
boolean has = match(
e -> false,
v -> true
);
if (has)
{
return value.fromRight(null);
}
else
{
throw value.fromLeft(null);
}
}
/**
* Upcast the Result's type parameters
*/
private static <E extends Throwable, V> Result<E, V> cast(Result<? extends E, ? extends V> r)
{
return r.match(
(E e) -> error(e),
(V v) -> value(v)
);
}
}
和 Either
类型,旨在紧密反映 Haskell's Either
:
/**
* A container for a disjunction of two possible types
* By convention, the Left constructor is used to hold an error value and the Right constructor is used to hold a correct value
* @param <L> The left alternative type
* @param <R> The right alternative type
*/
public abstract class Either<L, R>
{
public abstract <T> T match(Function<? super L, ? extends T> lf, Function<? super R, ? extends T> rf);
public abstract void match(Consumer<? super L> lf, Consumer<? super R> rf);
public <A, B> Either<A, B> bimap(Function<? super L, ? extends A> lf, Function<? super R, ? extends B> rf)
{
return match(
(L l) -> left(lf.apply(l)),
(R r) -> right(rf.apply(r))
);
}
public L fromLeft(L left)
{
return match(
(L l) -> l,
(R r) -> left
);
}
public R fromRight(R right)
{
return match(
(L l) -> right,
(R r) -> r
);
}
public static <L, R> Either<L, R> left(L value)
{
return new Left<>(value);
}
public static <L, R> Either<L, R> right(R value)
{
return new Right<>(value);
}
private static <L, R> Either<L, R> cast(Either<? extends L, ? extends R> either)
{
return either.match(
(L l) -> left(l),
(R r) -> right(r)
);
}
static class Left<L, R> extends Either<L, R>
{
final L value;
Left(L value)
{
this.value = value;
}
@Override
public <T> T match(Function<? super L, ? extends T> lf, Function<? super R, ? extends T> rf)
{
return lf.apply(value);
}
@Override
public void match(Consumer<? super L> lf, Consumer<? super R> rf)
{
lf.accept(value);
}
}
static class Right<L, R> extends Either<L, R>
{
final R value;
Right(R value)
{
this.value = value;
}
@Override
public <T> T match(Function<? super L, ? extends T> lf, Function<? super R, ? extends T> rf)
{
return rf.apply(value);
}
@Override
public void match(Consumer<? super L> lf, Consumer<? super R> rf)
{
rf.accept(value);
}
}
}
用法示例
这个的主要用途是将异常抛出操作转换为单子操作。这允许在流和其他功能上下文中使用(已检查的)异常抛出方法,还允许在 return 类型上进行模式匹配和绑定。
private static void writeFiles(List<String> filenames, String content)
{
filenames.stream()
.map(
(String s) -> Result.of(
() -> new FileWriter(s) //Open file for writing
).peek(
(FileWriter f) -> Result.of(
() -> f.write(content) //Write file contents
)
).peek(
(FileWriter f) -> Result.of(
() -> f.close()) //Close file
)
).forEach(
r -> r.match(
(IOException e) -> System.out.println("exception writing to file: " + e), //Log exception
(FileWriter f) -> System.out.println("successfully written to file '" + f + "'") //Log success
)
);
}
您需要访问异常的 class,然后在 catch 块中使用一些泛型。
一种简单的方法是将 Class<E>
class 传递给 Result.of
方法:
public static <E extends Throwable, V> Result<E, V> of(
ThrowingSupplier<V, E> v,
Class<E> errorType) {
try {
return value(v.get());
} catch(Throwable e) {
if (errorType.isInstance(e)) {
return error(errorType.cast(e));
}
throw new RuntimeException(e); // rethrow as runtime?
}
}
用法:
Result.of(() -> new FileWriter(s), IOException.class)
Class.isInstance
is the dynamic equivalent of the instanceof
static operator, while Class.cast
与静态转换相同:(E) e
,只是我们没有从编译器那里得到警告。
编辑: 您需要考虑当捕获的 Throwable
不是您期望的异常类型时该怎么做。我已将其包装在 RuntimeException
中并重新抛出。这允许继续为您的 monad 使用流畅的样式,但不再透明,因为现在任何异常都包含在未经检查的异常中。也许您可以向 Result.of
添加第三个参数来处理这种特定情况...
更新: 这似乎根本不起作用。我暂时将它保留在这里,因为我已经链接到其他地方,并且因为它使用了其他接受的答案中提供的方法,我想继续调查。
使用评论中链接的
Result
实现:
public class Result<E extends Exception, V>
{
...
public static <E extends Exception, V> Result<E, V> of(ThrowingSupplier<V, E> v)
{
try
{
return value(v.get());
}
catch(Exception e)
{
Class<E> errType = Reflector.getType();
if (errType.isInstance(e))
{
return error(errType.cast(e));
}
else
{
throw (RuntimeException) e;
}
}
}
...
}
和 Reflector
:
import java.lang.reflect.ParameterizedType;
/**
* This class only exists to provide a generic superclass to {@link Reflector}
* @param <E> The type for the subclass to inspect
*/
abstract class Reflected<E>
{ }
/**
* This class provides the ability to obtain information about its generic type parameter.
* @param <E> The type to inspect
* @author
*/
@Deprecated
public class Reflector<E> extends Reflected<E>
{
/**
* Returns the class corresponding to the type {@code <E>}.
* @param <E> The type to inspect
* @return The class corresponding to the type {@code <E>}
*/
public static <E> Class<E> getType()
{
return new Reflector<E>().getParameterType();
}
private Reflector() {}
private Class<E> getParameterType()
{
final ParameterizedType type = (ParameterizedType) this.getClass().getGenericSuperclass();
return (Class<E>) type.getActualTypeArguments()[0];
}
}
只需使用接口满足契约的乐观假设,就像普通 Java 代码总是会做的那样(由编译器强制执行)。如果有人绕过了这个异常检查,你没有责任解决这个问题:
public static <E extends Exception, V> Result<E, V> of(ThrowingSupplier<V, E> v) {
try {
return value(v.get());
}
catch(RuntimeException|Error x) {
throw x; // unchecked throwables
}
catch(Exception ex) {
@SuppressWarnings("unchecked") E e = (E)ex;
return error(e);
}
}
请注意,即使 Java 编程语言也同意可以继续这个假设,例如
public static <E extends Exception, V> Result<E, V> of(ThrowingSupplier<V, E> v) throws E {
try {
return value(v.get());
}
catch(RuntimeException|Error x) {
throw x; // unchecked throwables
}
catch(Exception ex) {
throw ex; // can only be E
}
}
是有效的Java代码,因为正常情况下,get
方法只能抛出E
或者unchecked throwables,所以这里重新抛出ex
是有效的, 当 throws E
被声明时。当我们想要构造一个用 E
.
Result
时,我们只需要避免 Java 语言的缺陷