为什么我没有得到NullPointerException?

时间:2012-09-30 21:55:38

标签: java nullpointerexception

  

可能重复:
  Static fields on a null reference in Java

我理解静态方法是在类级别上。所以我知道我不需要创建实例来调用静态方法。但我也知道我可以调用静态方法LIKE一个实例方法。这是我感到困惑的地方,因为我在从null对象调用静态方法时期望NullPointerException(如在调用实例方法中)。我真的很感激为什么我错在这里期待NullPointerException的一些解释。

以下是示例代码:

public class SampleClass {

    public static int getSumStatic(int x, int y){
        return x+y;
    }

    public int getDifferenceInstance(int x, int y){
        return x-y;
    }
}

public class TestClass {

    public static void main (String[] args){        
    SampleClass sc=null;

    System.out.println(SampleClass.getSumStatic(2, 2)); //as expected

    //I was expecting NullPointerException in the next line, since I am accessing null object
    System.out.println(sc.getSumStatic(4,5)); //static method , executes perfectly  

    System.out.println(sc.getDifferenceInstance(6,4));//throws NullPointerException
    }
}

6 个答案:

答案 0 :(得分:14)

通过实例调用静态方法不需要实例存在。只要编译器能够确定变量的类型,它就会在评估sc表达式并丢弃结果后静态地进行等效调用:

System.out.println(SampleClass.getSumStatic(4,5));

来自Java Language Specification

  

第15.12.1节

     

❖如果表单是Primary.NonWildTypeArgumentsopt Identifier,则表示名称   方法是标识符。设T为主表达式的类型。如果T是类或接口类型,则要搜索的类或接口是T,如果T是类型变量,则是T的上限。

     

第15.12.4.1节:

     
      
  1. 如果MethodInvocation的第二个产品(包括主要产品)是   涉及,然后有两个子类:
  2.         

    ❖如果调用模式是静态的,则没有目标引用。该   表达Primary,但会丢弃结果。

答案 1 :(得分:3)

这是java设计师的某种设计错误。 您应该在类上调用静态方法,因为它属于类而不属于对象。

您可以在why-isnt-calling-a-static-method-by-way-of-an-instance-an-error-for-the-java-co

看到有关此问题的一些信息

有趣的是,无法对尚未初始化的对象变量调用静态方法。但是如果使用 null 初始化对象,一切都很好。

我认为这是有效的,因为与此变量一起存储的对象通过作业提供类型信息。

SampleClass sampleObject;
sampleObject.getSumStatic(2, 2) 

将无法编译,因为该对象未初始化,因此在java编译器的语法树中没有设置类型信息

答案 2 :(得分:2)

Java将允许您仅基于引用访问静态方法,即使引用为null也是如此。只有参考类型很重要。

您通常应该使用类名来调用静态方法:

SampleClass.getSumStatic(2, 2);

答案 3 :(得分:2)

作为对dasblinkenlight(绝对正确)响应的补充说明,您可以看到生成的Java字节码的差异(您可以使用javap -c轻松查看)。

考虑以下(更简单)类:

public class Example {
  public static void staticMethod() {}
  public void virtualMethod() {}
}

使用它的应用程序:

public class ExampleApplication {
  public static void main(String[] args) {
    Example ex = null;
    Example.staticMethod();
    ex.staticMethod();
    ex.virtualMethod();
  }
}

让我们看看为ExampleApplication.main(String[])生成的字节码:

public static void main(java.lang.String[]);
  Code:
   0:   aconst_null
   1:   astore_1
   2:   invokestatic    #2; //Method Example.staticMethod:()V
   5:   aload_1
   6:   pop
   7:   invokestatic    #2; //Method Example.staticMethod:()V
   10:  aload_1
   11:  invokevirtual   #3; //Method Example.virtualMethod:()V
   14:  return

单步执行此操作(通过偏移量,即上面输出中的数字列):

偏移0和1的指令加载null,然后将其存储到局部变量1(ex)。

偏移2处的指令执行传统的静态调用:它是invokestatic指令,调用Example.staticMethod()。这不涉及实例变量,正如您所期望的那样。

接下来是我们实例上对静态方法的调用。偏移量5处的指令将ex加载到堆栈上(请记住它为空),但偏移量为6的pop会立即撤消此操作。因此,偏移量7处的invokestatic与偏移量2处的javac完全相同:空值不在VM堆栈上,并且要调用的方法由ex编译,而不管invokevirtual的价值是什么。

相比之下,虚拟(即非静态)方法将实例推送到堆栈(偏移量10),然后当它在堆栈上时,执行查找virtualMethod()的{​​{1}}指令在实例上找到要执行的方法。这是抛出NullPointerException的步骤,因为该查找无法继续。 (请注意,在静态情况下,此步骤是不必要的,这也是在初始VM中静态方法调用更快的原因。)

答案 4 :(得分:1)

您正在通过强类型为SampleClass的变量访问静态方法。在编译期间,该方法被解析为直接在类定义上调用(因此它是一个静态方法),因此它实际上已被解析。

this中没有隐式public static int getSumStatic,因此无法访问空指针。

答案 5 :(得分:0)

我认为类实例实例的静态函数在编译时基本上被它们的静态类信息替换(它们也不像实例方法那样使用继承)。

Instance方法尝试访问null的方法并抛出NullPointerException。