尝试执行这段代码
public class Main {
public static void main(String... args) {
new Main();
}
Main() {
A a = null;
a.foo();
}
}
class A {
void foo() {}
}
显然,我会得到一个NullPointerException
,因为A已初始化为null
。代码编译因为我(编码器)知道变量a
只能在这个位置为空,而它(编译器)......好吧,实际上它也知道这一点,我得到了警告信息
Null pointer access: The variable a can only be null at this location
我的问题是,鉴于我和它都知道会抛出异常,为什么我的代码允许编译?有没有机会不在那里得到例外?
换句话说,为什么我会在某些方面遇到编译器错误
try {
throw new Exception();
int x = 0;
} catch (Exception e) {
}
而不在此
try {
if(true)throw new Exception();
int x = 0;
} catch (Exception e) {
}
非常糟糕:编译器如何区分阻塞或不阻塞"明显错误"?
答案 0 :(得分:3)
允许编译,因为Java语言规范不禁止它。谁知道,可能你想在调用者中捕获这个NullPointerException
。这不是最好的代码设计,但JLS允许这样做。
请注意,Eclipse编译器可以配置为此时显示错误。默认情况下,它会发出警告:
$ java -jar org.eclipse.jdt.core_3.10.2.v20150120-1634.jar -source 1.7 -cp rt.jar Main.java
----------
1. WARNING in L:\Lan\Projects\JavaTest\EcjTest\src\Main.java (at line 8)
a.foo();
^
Null pointer access: The variable a can only be null at this location
----------
1 problem (1 warning)
此类静态代码分析器(如FindBugs)也会触发此代码的警告。
JLS没有禁止它的原因是基于意见。但请注意,此功能现在无法添加,因为这可能会破坏向后兼容性。我相信,现有的代码实际上可能依赖于这种行为。
答案 1 :(得分:2)
答案 2 :(得分:1)
<强> 4.12.2 强>
类类型T的变量可以包含空引用或引用 到类T的实例或任何类的子类T的实例。
另外, 例如,考虑:
while(true)
{
//stay blank
}
这会在我的NetBeans中出现以下行无法访问的错误。
再次
while(true)
{
if(false)break;
}
现在你的期望是什么?同样的事情?它不是。它进入无限循环。 同样地,你应该能够在你的代码中捕获NPE。这是允许的。因此可能存在许多组合,用于不同类型的这种歧义。这很难选择&amp;限制。
答案 3 :(得分:1)
我可能不完全正确,但解析器应该受到指责。我实现了Java parser 1.5
,基本上调用了compilationUnit()
。这是通过获取源流来开始的。
流被标记化(创建令牌 - 关键字,变量......所有较小的部分)。然后可以进行多次调用 - 如果Java程序必须以关键字开头,则可以查找访问说明符。之后,解析器需要有关键字“class”。在关键字之后找到其他可能性。例如。我可能会写public class{}
或public int i;
。如果它获得了正确的关键字,则解析继续,否则会抛出ParseException
。
例如,我对Parser进行了一些小改动:
private Token jj_consume_token(int kind) throws ParseException {
Token oldToken;
if ((oldToken = token).next != null) {
token = token.next;
} else {
token = token.next = token_source.getNextToken();
}
jj_ntk = -1;
if (token.kind == kind) {
jj_gen++;
if (++jj_gc > 100) {
jj_gc = 0;
for (int i = 0; i < jj_2_rtns.length; i++) {
JJCalls c = jj_2_rtns[i];
while (c != null) {
if (c.gen < jj_gen) {
c.first = null;
}
c = c.next;
}
}
}
return token;
}
jj_kind = kind;
throw generateParseException();
}
}
要:
private Token jj_consume_token(int kind) throws ParseException {
try{
Token oldToken;
if ((oldToken = token).next != null) {
token = token.next;
} else {
token = token.next = token_source.getNextToken();
}
jj_ntk = -1;
if (token.kind == kind) {
jj_gen++;
if (++jj_gc > 100) {
jj_gc = 0;
for (int i = 0; i < jj_2_rtns.length; i++) {
JJCalls c = jj_2_rtns[i];
while (c != null) {
if (c.gen < jj_gen) {
c.first = null;
}
c = c.next;
}
}
}
return token;
}
token = oldToken;
jj_kind = kind;
throw generateParseException();
} catch (ParseException ex) {
recover(ex,SEMICOLON);
return token;
}
}
恢复块看起来像:
void recover(ParseException ex, int recoveryPoint) {
syntaxErrors.add(ex);
Token t;
do {
t = getNextToken();
} while (t.kind != EOF && t.kind != recoveryPoint);
}
那我刚刚做了什么?我恢复了解析器抛出的异常,即使没有方法或只是statement.EG
class A{
Date d;
System.out.print("No exception");
}
我现在没有收到错误。我做了更改。所以,如果你希望你的解析器在这种情况下报告一个异常,你可以实现你的解析器:)希望你现在得到它。