为什么在null引用上调用方法成功编译?

时间:2015-07-05 17:18:37

标签: java exception compilation

尝试执行这段代码

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) {
    }

非常糟糕:编译器如何区分阻塞或不阻塞"明显错误"?

4 个答案:

答案 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)

  

我的问题是,鉴于我和它都知道会抛出异常,为什么我的代码允许编译

null不是compile time constant。编译器在编译时不知道A a = null;求值为null

进一步阅读:

Why isn't null a compile time constant

答案 2 :(得分:1)

来自Java spec

<强> 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");
}

我现在没有收到错误。我做了更改。所以,如果你希望你的解析器在这种情况下报告一个异常,你可以实现你的解析器:)希望你现在得到它。