Lambdas that bypass try/catch blocks for checked exceptions

huangapple go评论82阅读模式
英文:

Lambdas that bypass try/catch blocks for checked exceptions

问题

以下是您要翻译的内容:

由于我试图提取一些我在大多数项目中使用的常见包装 lambda 例程,我已经能够创建CheckedFunction,由PermeableFunction FunctionalInterface 子类化,绕过了 try/catch 块的需要。我已经在 Oracle jdks for windows(v1.8.0_251)/linux(v1.8.0_261) 和其他几个在线编译器上进行了测试(不确定它们使用了哪种实现)。

不确定这是否实际违反了规范,或者是标准允许的内容... 根据我对文档的解释,这不应该是可能的:

更确切地说,假设 B 是一个类或接口,A 是 B 的超类或超接口,并且 B 中的方法声明 n 覆盖或隐藏了 A 中的方法声明 m。那么:

  • 如果 n 有一个提到任何已检查异常类型的 throws 子句,那么 m 必须有一个 throws 子句,否则会出现编译时错误。
  • 对于 n 的 throws 子句中列出的每个已检查异常类型,在 m 的 throws 子句的擦除(§4.6)中必须出现相同的异常类或其超类型;否则,将出现编译时错误。
  • 如果 m 的未擦除 throws 子句不包含在 n 的 throws 子句中的每个异常类型的超类型,将出现编译时未经检查的警告。

这是我使用的示例代码:

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.function.Function;

public class Main {

    public static void main(String[] args) {

        PermeableFunction<Path, Long> function = PermeableFunction.from(Files::size);

        Path doesNotExist = Paths.get("/does/not/exist");

        // function.apply(doesNotExist); -> 抛出 WrappedException
        function.applyChecked(doesNotExist); // 无需 try/catch 块,抛出 NoSuchFileException!
    }
}

interface PermeableFunction<T,R> extends CheckedFunction<T, R, RuntimeException> {

    static <T, R> PermeableFunction<T, R> from(WrappedFunction<T, R> wrappedFunction) {

        return CheckedFunction.<T,R, RuntimeException>from(wrappedFunction)::applyChecked;
    }
}

interface CheckedFunction<T, R, E extends Exception> extends WrappedFunction<T, R> {

    @Override
    R applyChecked(T t) throws E;

    static <T, R, E extends Exception> CheckedFunction<T, R, E> from(WrappedFunction<T, R> wrappedFunction) {

        return wrappedFunction::applyChecked;
    }
}

interface WrappedFunction<T, R> extends Function<T, R> {

    R applyChecked(T t) throws Exception;

    @Override
    default R apply(T t) {

        try {

            return applyChecked(t);

        } catch (Exception e) {

            throw new WrappedException(e);
        }
    }
}

class WrappedException extends RuntimeException {

    public WrappedException(Throwable cause) {
        super(cause);
    }
}

CheckedFunction 也允许像这样遮蔽 throwable:

Lambdas that bypass try/catch blocks for checked exceptions

所以这是我的问题:

这是否应该报告给实现者,还是这是标准强加的一般问题?

我已将翻译的部分返回给您。

英文:

As a result of me trying to extract some common wrapping lambda routines that I use in most of my projects, I've been able to create CheckedFunction, subclassed by PermeableFunction FunctionalInterface that bypasses the need of try/catch blocks. I've tested that on Oracle jdks for windows(v1.8.0_251)/linux(v1.8.0_261) and several other online compilers(not sure which implementation is used there).

Wasn't sure if this actually violates the specification or is something allowed by the standard... According to my interpretaion of the docs this should not be possible:

> More precisely, suppose that B is a class or interface, and A is a superclass or superinterface of B, and a method declaration n in B overrides or hides a method declaration m in A. Then:
>
> - If n has a throws clause that mentions any checked exception types, then m must have a throws clause, or a compile-time error occurs.<br/>
> - For every checked exception type listed in the throws clause of n, that same exception class or one of its supertypes must occur in the erasure (§4.6) of the throws clause of m; otherwise, a compile-time error occurs.<br/>
> - If the unerased throws clause of m does not contain a supertype of each exception type in the throws clause of n, a compile-time unchecked warning occurs.

Here is the sample code that I've used:

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.function.Function;

public class Main {

    public static void main(String[] args) {

        PermeableFunction&lt;Path, Long&gt; function = PermeableFunction.from(Files::size);

        Path doesNotExist = Paths.get(&quot;/does/not/exist&quot;);

        // function.apply(doesNotExist); -&gt; throws WrappedException
        function.applyChecked(doesNotExist); // throws NoSuchFileException without the need of a try/catch block!
    }
}

interface PermeableFunction&lt;T,R&gt; extends CheckedFunction&lt;T, R, RuntimeException&gt; {

    static &lt;T, R&gt; PermeableFunction&lt;T, R&gt; from(WrappedFunction&lt;T, R&gt; wrappedFunction) {

        return CheckedFunction.&lt;T,R, RuntimeException&gt;from(wrappedFunction)::applyChecked;
    }
}

interface CheckedFunction&lt;T, R, E extends Exception&gt; extends WrappedFunction&lt;T, R&gt; {

    @Override
    R applyChecked(T t) throws E;

    static &lt;T, R, E extends Exception&gt; CheckedFunction&lt;T, R, E&gt; from(WrappedFunction&lt;T, R&gt; wrappedFunction) {

        return wrappedFunction::applyChecked;
    }
}

interface WrappedFunction&lt;T, R&gt; extends Function&lt;T, R&gt; {

    R applyChecked(T t) throws Exception;

    @Override
    default R apply(T t) {

        try {

            return applyChecked(t);

        } catch (Exception e) {

            throw new WrappedException(e);
        }
    }
}

class WrappedException extends RuntimeException {

    public WrappedException(Throwable cause) {
        super(cause);
    }
}

CheckedFunction also allows shadowing of the throwable like so:

Lambdas that bypass try/catch blocks for checked exceptions

So here is my question:

Is this something that should be reported to the implementer(s) or it's a general issue imposed by the standard?

答案1

得分: 6

你的方法如下:

static <T, R, E extends Exception> CheckedFunction<T, R, E> from(WrappedFunction<T, R> wrappedFunction) {
    return wrappedFunction::applyChecked;
}

被我的 Eclipse 版本以及所有 JDK 从 9 到 14 的 javac 拒绝了。只有 JDK 8 接受了它,所以这是一个 bug,但不值得报告,因为更新版本不再支持它。

话虽如此,通过泛型类型系统绕过异常检查是可能的。

当你将方法更改为:

static <T, R, E extends Exception> CheckedFunction<T, R, E> from(WrappedFunction<T, R> wrappedFunction) {
    return (CheckedFunction<T, R, E>)(CheckedFunction<T, R, Exception>)wrappedFunction::applyChecked;
}

所有编译器都将接受它,但会产生一个“unchecked”警告。这是一个已知的情况。

你可以简化示例如下:

public class Main {
    public static void main(String[] args) {
        CheckedFunction<Path, Long, RuntimeException> function = (CheckedFunction)
            (CheckedFunction<Path, Long, IOException>)Files::size;
        Path doesNotExist = Paths.get("/does/not/exist");

        function.applyChecked(doesNotExist); // 不需要 try/catch 块,会抛出 NoSuchFileException!
    }

    interface CheckedFunction<T, R, E extends Exception> {
        R applyChecked(T t) throws E;
    }
}

还有其他几种可能的变化,甚至不使用 lambda 表达式。它只需要使用类型参数和与该类型参数相关的未检查操作的 throws 声明。

例如:

public class Main {
    public static void main(String[] args) {
        try {
            Files.size(Paths.get("/does/not/exist"));
        }
        catch(IOException ex) {
            doThrow(ex); // 抛出未声明的 IOException
        }
    }

    static <T extends Throwable> void doThrow(Throwable t) throws T {
        throw (T)t;
    }
}

如前所述,这是已知的情况,重要的是,你永远不应忽略“unchecked”警告。

英文:

Your method

static &lt;T, R, E extends Exception&gt; CheckedFunction&lt;T, R, E&gt; from(WrappedFunction&lt;T, R&gt; wrappedFunction) {
return wrappedFunction::applyChecked;
}

was rejected by my Eclipse version, as well as javac of all JDKs from 9 to 14. Only JDK 8 accepted it, so it’s a bug, but not worth reporting, as newer versions do not have it.

That said, subverting the exception checking via the generic type system is possible.

When you change the method to

static &lt;T, R, E extends Exception&gt; CheckedFunction&lt;T, R, E&gt; from(WrappedFunction&lt;T, R&gt; wrappedFunction) {
return (CheckedFunction)(CheckedFunction&lt;T, R, Exception&gt;)wrappedFunction::applyChecked;
}

all compilers will accept it, but produce an “unchecked” warning. This is a known thing.

You can simplify the example to:

public class Main {
public static void main(String[] args) {
CheckedFunction&lt;Path, Long, RuntimeException&gt; function = (CheckedFunction)
(CheckedFunction&lt;Path, Long, IOException&gt;)Files::size;
Path doesNotExist = Paths.get(&quot;/does/not/exist&quot;);
function.applyChecked(doesNotExist); // throws NoSuchFileException without the need of a try/catch block!
}
interface CheckedFunction&lt;T, R, E extends Exception&gt; {
R applyChecked(T t) throws E;
}
}

There are several variations possible, even without lambda expressions. All it needs, is a throws declaration using a type parameter and an unchecked operation regarding this type parameter.

E.g.

public class Main {
public static void main(String[] args) {
try {
Files.size(Paths.get(&quot;/does/not/exist&quot;));
}
catch(IOException ex) {
doThrow(ex); // throws undeclared IOException
}
}
static &lt;T extends Throwable&gt; void doThrow(Throwable t) throws T {
throw (T)t;
}
}

As said, this is known and the takeaway is, you should never ignore “unchecked” warnings.

huangapple
  • 本文由 发表于 2020年8月13日 17:17:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/63391917.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定