Java 中涉及 throws 子句的类型推断解决过程
Type Inference resolution procedure involving throws clause in Java
考虑 JLS §18.1.3 中的以下文章 - 边界
在这里,当我们尝试确定推理变量的边界集时 - 我们会遇到以下情况之一:
...
- throws α: The inference variable α appears in a throws clause.
...
A bound of the form throws α is purely informational: it directs resolution to
optimize the instantiation of α so that, if possible, it is not a checked exception type.
我认为这种说法是错误的:
- 这是因为理想情况下,提到 throws 子句是为了处理代码执行过程中可能发生的已检查异常。
- 那为什么 JLS 仍然阻止 α 成为 Checked Exception?
- 理想情况下,推理变量 α 必须被限制为 Checked 类型 的异常,而不是 Unchecked变体.
我的理解是正确的还是我遗漏了什么?
我认为你的interpretation/understanding这个说法有点误导:
A bound of the form throws α is purely informational: it directs resolution to optimize the instantiation of α so that, if possible, it is not a checked exception type.
那一行指的是分辨率,据我了解,它不是关于 throws α
在哪里,而是关于 α
在哪里被推断,可以想象是方法的调用。
考虑这个 class:
static class MyClass {
public static void main(String[] args) {
MyClass.<RuntimeException>something(0); // same as MyClass.something(1);
try {
MyClass.<IOException>something(2);
} catch (IOException ex) {
// checked exception
}
}
/**
* Will throw IOException if argument is 2, a RuntimeException otherwise
*/
static <T extends Exception> void something(int a) throws T {
if (a == 2) {
throw (T) new IOException(); //of course it's a bad cast
} else {
throw (T) new Exception();
}
}
}
分析完两个something
方法,重点关注main
方法中的调用:
调用 MyClass.<IOException>something(0)
需要一个 IOException。调用者知道它(假设完整记录合同而不是紧密耦合的代码),并处理异常。
这已经告诉你变量可以是一个检查异常,与你的想法相反。
相反,调用 MyClass.<RuntimeException>something(0)
期望基于类似原因出现运行时异常。
如何推断 α
(上例中的 T
)允许编译器跳过强制调用者 catch/handle 异常(如果要查看边界,否则必须)
现在关于“优化”:被限制为 extends Exception
的类型变量可以合理地预期解析为检查异常。但是,如果调用者知道它应该是一个运行时异常,它可以“通知”编译器它将是一个运行时异常。这就是我通过在类型见证中指定 RuntimeException
所做的(当没有明确给出类型参数时,RuntimeException
也是已解析的类型)。
我们可以花几天时间来解释“优化”,但至少我作为调用者不必 try/catch 调用,而且我仍然没有扰乱编译器(第一次调用)。
考虑这个例子:
public class Main {
public static void main(String[] args) {
foo(() -> System.out.println("Foo"));
}
public static <T extends Exception> void foo(ThrowingRunnable<T> runnable) throws T {
runnable.run();
}
}
interface ThrowingRunnable<T extends Exception> {
void run() throws T;
}
在调用foo
时类型参数T
的推断过程中,会在类型变量T
上绑定一个“throws”,而T
是推断为RuntimeException
。如果不是这个界限,T
会因为 T extends Exception
界限而被推断为 Exception
。这意味着我需要做:
try {
foo(() -> System.out.println("Foo"));
catch (Exception e) {
// This won't be reached!
}
我不得不处理异常,即使我在 lambda 中所做的只是打印东西!这似乎不太好,是吗?
这个bound的目的是如果方法没有理由抛出checked exception,它就不会抛出checked exception,这样就不用写那么多try...到处都是。 foo
将 仅 如果您执行以下操作会抛出检查异常:
foo(() -> new FileOutputStream("foo"));
如果绑定的效果是强制 T
成为一个检查异常,它根本就不会很有用。
考虑 JLS §18.1.3 中的以下文章 - 边界
在这里,当我们尝试确定推理变量的边界集时 - 我们会遇到以下情况之一:
...
- throws α: The inference variable α appears in a throws clause.
...
A bound of the form throws α is purely informational: it directs resolution to optimize the instantiation of α so that, if possible, it is not a checked exception type.
我认为这种说法是错误的:
- 这是因为理想情况下,提到 throws 子句是为了处理代码执行过程中可能发生的已检查异常。
- 那为什么 JLS 仍然阻止 α 成为 Checked Exception?
- 理想情况下,推理变量 α 必须被限制为 Checked 类型 的异常,而不是 Unchecked变体.
我的理解是正确的还是我遗漏了什么?
我认为你的interpretation/understanding这个说法有点误导:
A bound of the form throws α is purely informational: it directs resolution to optimize the instantiation of α so that, if possible, it is not a checked exception type.
那一行指的是分辨率,据我了解,它不是关于 throws α
在哪里,而是关于 α
在哪里被推断,可以想象是方法的调用。
考虑这个 class:
static class MyClass {
public static void main(String[] args) {
MyClass.<RuntimeException>something(0); // same as MyClass.something(1);
try {
MyClass.<IOException>something(2);
} catch (IOException ex) {
// checked exception
}
}
/**
* Will throw IOException if argument is 2, a RuntimeException otherwise
*/
static <T extends Exception> void something(int a) throws T {
if (a == 2) {
throw (T) new IOException(); //of course it's a bad cast
} else {
throw (T) new Exception();
}
}
}
分析完两个something
方法,重点关注main
方法中的调用:
调用 MyClass.<IOException>something(0)
需要一个 IOException。调用者知道它(假设完整记录合同而不是紧密耦合的代码),并处理异常。
这已经告诉你变量可以是一个检查异常,与你的想法相反。
相反,调用 MyClass.<RuntimeException>something(0)
期望基于类似原因出现运行时异常。
如何推断 α
(上例中的 T
)允许编译器跳过强制调用者 catch/handle 异常(如果要查看边界,否则必须)
现在关于“优化”:被限制为 extends Exception
的类型变量可以合理地预期解析为检查异常。但是,如果调用者知道它应该是一个运行时异常,它可以“通知”编译器它将是一个运行时异常。这就是我通过在类型见证中指定 RuntimeException
所做的(当没有明确给出类型参数时,RuntimeException
也是已解析的类型)。
我们可以花几天时间来解释“优化”,但至少我作为调用者不必 try/catch 调用,而且我仍然没有扰乱编译器(第一次调用)。
考虑这个例子:
public class Main {
public static void main(String[] args) {
foo(() -> System.out.println("Foo"));
}
public static <T extends Exception> void foo(ThrowingRunnable<T> runnable) throws T {
runnable.run();
}
}
interface ThrowingRunnable<T extends Exception> {
void run() throws T;
}
在调用foo
时类型参数T
的推断过程中,会在类型变量T
上绑定一个“throws”,而T
是推断为RuntimeException
。如果不是这个界限,T
会因为 T extends Exception
界限而被推断为 Exception
。这意味着我需要做:
try {
foo(() -> System.out.println("Foo"));
catch (Exception e) {
// This won't be reached!
}
我不得不处理异常,即使我在 lambda 中所做的只是打印东西!这似乎不太好,是吗?
这个bound的目的是如果方法没有理由抛出checked exception,它就不会抛出checked exception,这样就不用写那么多try...到处都是。 foo
将 仅 如果您执行以下操作会抛出检查异常:
foo(() -> new FileOutputStream("foo"));
如果绑定的效果是强制 T
成为一个检查异常,它根本就不会很有用。