为什么 Java 中的断言充当 "missing return statement" 而 throw 语句却没有?
Why do assertions in Java act as a "missing return statement" whereas throw statements do not?
我有一个 class 具有以下字段和方法:
private final Map<Character, String> charMap = new LinkedHashMap<>();
public Character charOf(String s) {
assert this.charMap.containsValue(s);
for (Character c : this.charMap.keySet()) {
if (this.charMap.get(c).equals(s)) return c;
}
}
编译器不喜欢这个,给我一个“缺少 return 语句”错误,而这个编译器很好:
private final Map<Character, String> charMap = new LinkedHashMap<>();
public Character charOf(String s) {
for (Character c : this.charMap.keySet()) {
if (this.charMap.get(c).equals(s)) return c;
}
throw new IllegalArgumentException("There is no mapping for \"" + s + "\"");
}
据我所知,这两种方法的功能应该完全相同,并且做的事情完全相同,前者的可读性略高(代价是错误消息不太详细)。它总是 return 一个值或抛出异常。为什么编译器没有意识到这一点?
因为两件事:
首先,断言只有在 运行 时使用 jvm 参数 -ea
启用时才会抛出异常。这意味着有可能被跳过。
其次,您在最后抛出异常,而在代码之前 运行 断言。从理论上讲,断言可以在没有循环返回的情况下为真,因为 Map
内容理论上可以在断言和循环之间改变,或者 containsValue
可以做一些编译器不期望的事情。编译器只检查语法,不检查逻辑上的不可能性。
有两个原因:
- 编译器 'smart' 不足以弄清楚
this.charMap.containsValue(s)
为真意味着 this.charMap.get(c).equals(s)
必须对某些 c
为真。它只做更简单的分析,比如检查 if 语句的两个分支是否都有 return。
- 即使它足够聪明,Java 也是一种具有可变对象和线程的语言 - 即使映射包含
assert
时的键,它也可能从在循环开始之前由另一个线程映射。
如果您想要一种具有足够 'smart' 编译器的语言,您可能需要查看像 Idris 这样的依赖类型语言。
这与 JLS 更多相关:https://docs.oracle.com/javase/specs/jls/se10/html/jls-8.html#jls-8.4.7 而不是 assert
:
If a method is declared to have a return type (§8.4.5), then a
compile-time error occurs if the body of the method can complete
normally (§14.1).
In other words, a method with a return type must return only by using
a return statement that provides a value return; the method is not
allowed to "drop off the end of its body". See §14.17 for the precise
rules about return statements in a method body.
由于您的方法必须 return 一个值(而不是 void
),因此只有 return
或 throw
return 执行流向调用者。
此规则可确保您避免这种情况(这是一个 C 示例):
#define N = 10;
const char* CHAR_MAP[N] = ...;
const char CHAR_VALUE[N] = ...;
char charOf(const char* s) {
for (int i = 0; i < N; ++i) {
if (strcmp(s, CHAR_MAP[i]) == 0) {
return CHAR_VALUE[i];
}
}
// not return value
}
我的 C 有点生疏,这可能无法编译,但重点是:在 C 中 - 至少在 C99 中 - 在这种情况下 returned 的值是 undefined 这可能会导致几个讨厌的问题,尤其是指针。
Java 中的规则确保您始终:
- Return一个值,例如:
null
.
- 抛出异常,表示错误。
这并不意味着 returned 值会使事情起作用:returning null
这里可能会产生 NullPointerException
.
此外,旁注:
public Character charOf(String s) {
for (Map.Entry<Character,String> entry : this.charMap.entrySet()) {
if (s.equals(entry.getValue()) {
return entry.getKey();
}
}
throw new IllegalArgumentException("There is no mapping for \"" + s + "\"");
}
当可以使用 entrySet()
.
时,应避免混合使用 keySet()
和 get()
我有一个 class 具有以下字段和方法:
private final Map<Character, String> charMap = new LinkedHashMap<>();
public Character charOf(String s) {
assert this.charMap.containsValue(s);
for (Character c : this.charMap.keySet()) {
if (this.charMap.get(c).equals(s)) return c;
}
}
编译器不喜欢这个,给我一个“缺少 return 语句”错误,而这个编译器很好:
private final Map<Character, String> charMap = new LinkedHashMap<>();
public Character charOf(String s) {
for (Character c : this.charMap.keySet()) {
if (this.charMap.get(c).equals(s)) return c;
}
throw new IllegalArgumentException("There is no mapping for \"" + s + "\"");
}
据我所知,这两种方法的功能应该完全相同,并且做的事情完全相同,前者的可读性略高(代价是错误消息不太详细)。它总是 return 一个值或抛出异常。为什么编译器没有意识到这一点?
因为两件事:
首先,断言只有在 运行 时使用 jvm 参数 -ea
启用时才会抛出异常。这意味着有可能被跳过。
其次,您在最后抛出异常,而在代码之前 运行 断言。从理论上讲,断言可以在没有循环返回的情况下为真,因为 Map
内容理论上可以在断言和循环之间改变,或者 containsValue
可以做一些编译器不期望的事情。编译器只检查语法,不检查逻辑上的不可能性。
有两个原因:
- 编译器 'smart' 不足以弄清楚
this.charMap.containsValue(s)
为真意味着this.charMap.get(c).equals(s)
必须对某些c
为真。它只做更简单的分析,比如检查 if 语句的两个分支是否都有 return。 - 即使它足够聪明,Java 也是一种具有可变对象和线程的语言 - 即使映射包含
assert
时的键,它也可能从在循环开始之前由另一个线程映射。
如果您想要一种具有足够 'smart' 编译器的语言,您可能需要查看像 Idris 这样的依赖类型语言。
这与 JLS 更多相关:https://docs.oracle.com/javase/specs/jls/se10/html/jls-8.html#jls-8.4.7 而不是 assert
:
If a method is declared to have a return type (§8.4.5), then a compile-time error occurs if the body of the method can complete normally (§14.1).
In other words, a method with a return type must return only by using a return statement that provides a value return; the method is not allowed to "drop off the end of its body". See §14.17 for the precise rules about return statements in a method body.
由于您的方法必须 return 一个值(而不是 void
),因此只有 return
或 throw
return 执行流向调用者。
此规则可确保您避免这种情况(这是一个 C 示例):
#define N = 10;
const char* CHAR_MAP[N] = ...;
const char CHAR_VALUE[N] = ...;
char charOf(const char* s) {
for (int i = 0; i < N; ++i) {
if (strcmp(s, CHAR_MAP[i]) == 0) {
return CHAR_VALUE[i];
}
}
// not return value
}
我的 C 有点生疏,这可能无法编译,但重点是:在 C 中 - 至少在 C99 中 - 在这种情况下 returned 的值是 undefined 这可能会导致几个讨厌的问题,尤其是指针。
Java 中的规则确保您始终:
- Return一个值,例如:
null
. - 抛出异常,表示错误。
这并不意味着 returned 值会使事情起作用:returning null
这里可能会产生 NullPointerException
.
此外,旁注:
public Character charOf(String s) {
for (Map.Entry<Character,String> entry : this.charMap.entrySet()) {
if (s.equals(entry.getValue()) {
return entry.getKey();
}
}
throw new IllegalArgumentException("There is no mapping for \"" + s + "\"");
}
当可以使用 entrySet()
.
keySet()
和 get()