简体   繁体   中英

Why does invoking a method on a null reference compile successfully?

Trying to execute this piece of code

public class Main {
    public static void main(String... args) {
        new Main();
    }

    Main() {
        A a = null;
        a.foo();
    }
}

class A {
    void foo() {}
}

I will get, obviously, a NullPointerException since A has been initialized to null . The code compiles because I (the coder) know that the variable a can only be null at this location, while it (the compiler)... well, actually it knows that too, to the point that I get the warning message

Null pointer access: The variable a can only be null at this location

My question is, given that both me and it know that an exception is going to be thrown, why is my code allowed to compile? Is there any chance not to get an exception there?

EDIT:

Put in another way, why do I get a compiler error in someting like this

    try {
        throw new Exception();
        int x = 0;
    } catch (Exception e) {
    }

and not in this

    try {
        if(true)throw new Exception();
        int x = 0;
    } catch (Exception e) {
    }

Put very poorly: how does the compiler discriminates between blocking or not blocking "obvious errors"?

It's allowed to compile as Java Language Specification does not forbid it. Who knows, probably you want to catch this NullPointerException in the caller. That would be not the best code design, but it's allowed by JLS.

Note that Eclipse compiler can be configured to display an error at this point. By default it issues a warning:

$ 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)

Also static code analyzers like FindBugs will trigger a warning on this code.

The question why it is not forbidden by JLS would be opinion based. However note that such feature cannot be added now, because this may break the backwards compatibility. I believe, there's an existing code which may actually rely on such behavior.

My question is, given that both me and it know that an exception is going to be thrown, why is my code allowed to compile

null is not a compile time constant . The compiler does not know at compile time that A a = null; evaluates to null .

Further reading :

Why isn't null a compile time constant

From Java spec

4.12.2

A variable of a class type T can hold a null reference or a reference to an instance of class T or of any class that is a subclass of T.

Also, For instance consider :

         while(true)
        {
        //stay blank
        }

This gives error in my NetBeans that following line is unreachable.

Again,

       while(true)
        {
           if(false)break;
        }

Now what you expect?Same thing? Its not.It goes into infinite loop. Similarly, you should be able to catch NPE in your code.It is allowed.And so there might exist many combinations for different type of such ambiguity.Which is rather difficult to choose & restrict.

I May not be fully correct, But the parsers are to be blamed.I implemented a Java parser 1.5 which basically calls a compilationUnit() .This gets started by getting the stream of the source .

The stream is tokenised (creates tokens -keywords,variables..all smaller parts).It may then make several calls- EG if the Java program has to start with a keyword, it may then look for a access specifier then.After that, the parser need to have the keyword "class".It the find other possibilities after the key word. EG. I may write public class{} or public int i; .If it gets the correct keyword, parsing continues else a ParseException is thrown.

For instance I made a small change in the 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();

        } 
    }

To:

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;
        } 
    }

Recovery block looks like:

void recover(ParseException ex, int recoveryPoint) {
        syntaxErrors.add(ex);
        Token t;
        do {
            t = getNextToken();
        } while (t.kind != EOF && t.kind != recoveryPoint);
    }

So what I just did? I recovered the exception thrown by the parser even if there is no method or just statements.EG

class A{
Date d;
System.out.print("No exception");
}

I don't get error now.I made changes.So, if you want your parser to report an Exception in such cases, you can implement your parser :) hope you get it now.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM