为什么我没有得到NullPointerException?

可能重复:
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 xy; } } 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 } } 

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

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

从Java语言规范 :

第15.12.1节

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

第15.12.4.1节:

  1. 如果涉及包含主节点的MethodInvocation的第二个制作,则有两个子句:

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

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

你可以看一下这个问题, 为什么 – isnt-calling-a-static-method-by-an-instance-an-error-for-the-java-co

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

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

 SampleClass sampleObject; sampleObject.getSumStatic(2, 2) 

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

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

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

 SampleClass.getSumStatic(2, 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处的指令执行传统的静态调用:它是一个调用Example.staticMethod()invokestatic指令。 这不涉及实例变量,正如您所期望的那样。

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

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

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

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

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

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