为什么Java编译器允许通过null对象进行静态变量访问?

我指着一些技巧并遇到了这个。 在以下代码中:

public class TestClass1 { static int a = 10; public static void main(String ar[]){ TestClass1 t1 = null ; System.out.println(t1.a); // At this line } } 

t1对象为null 。 为什么这段代码没有抛出NullPointerException

我知道这不是访问static变量的正确方法,但问题是关于NullPointerException

要在当前答案中添加一些其他信息,如果您使用以下方法反汇编类文件:

 javap -c TestClass1 

你会得到:

 Compiled from "TestClass1.java" public class TestClass1 extends java.lang.Object{ static int a; public TestClass1(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."":()V 4: return public static void main(java.lang.String[]); Code: 0: aconst_null 1: astore_1 2: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream; 5: aload_1 6: pop 7: getstatic #3; //Field a:I 10: invokevirtual #4; //Method java/io/PrintStream.println:(I)V 13: return static {}; Code: 0: bipush 10 2: putstatic #3; //Field a:I 5: return } 

在这里,您可以看到通过getstatc指令在第7行完成对静态字段的访问。 每当通过代码访问静态字段时,将在.class程序文件中生成相应的getstatic指令。

*static指令具有这样的特殊性:在调用它们之前它们不需要在堆栈中引用对象实例(例如,在堆栈中需要对象引用的invokevirtual),它们解析字段/方法仅使用运行时常量池的索引,该索引稍后将用于求解字段引用位置。

这是警告“静态字段应该以静态方式访问”的技术原因,当您编写t1.a时,某些IDE会向您抛出,因为对象实例不需要解析静态字段。

调用静态成员或方法时不需要实例。

由于静态成员属于类而不是实例。

空引用可用于访问类(静态)变量而不会导致exception。

http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#d5e19846

如果你看到这个例子( 参见规范中的完整示例

  public static void main(String[] args) { System.out.println(favorite().mountain); //favorite() returns null } 

即使favorite()的结果为null,也不会抛出NullPointerException。 打印出“Mount”表明主要表达式确实在运行时完全评估,尽管事实上只使用其类型而不是其值来确定要访问的字段(因为字段山是静态的)。

在这样的代码中NullPointerException的原因是什么:

  TestClass t = null; t.SomeMethod(); 

如果SomeMethod是一个实例方法,它将使用标准的引用做一些事情:

  public void SomeMethod() { // Here we'll have a NullPointerException (since "this" is null) this.SomeField = ... // <- We usually omit "this" in such a code } 

由于null,我们将有NullPointerException 。 如果method,field等是静态的,那么保证了这个引用的缺失 ,所以不会有NullPointerException

  public static void SomeStaticMethod() { // You can't put "this.SomeField = ..." here, because the method is static // Ans since you can't address "this", there's no reason for NullPointerException ... } ... TestClass t = null; // Equal to "TestClass.SomeStaticMethod();" t.SomeStaticMethod(); // <- "this" is null, but it's not addressed 

静态成员由类的所有实例共享,而不是单独重新分配给每个实例。 这些是第一次遇到类时类加载器加载的。 一个相关的SO问题

请记住,这些静态成员不属于特定的类实例。 更多信息在这里。 我已经提供了一个小代码片段供参考:

 @NotThreadSafe public class Test { // For all practical purpuose the following block will be only executed once at class load time. static { System.out.println("Loaded by the classloader : " + Test.class.getClassLoader()); } // Keeps track of created instances. static int instanceCount; // A simple constructor that increments instance count. public Test(){ Test.instanceCount++; System.out.println("instance number : " + instanceCount); } public static void main(String[] args) { System.out.println("Instaintiating objects"); new Test(); new Test(); } // Where would you expect this line to get printed? // ie last statement on the console or somewhere in the middle :) static { System.out.println("It should be printed at the end or would it?"); } } 

在这种情况下, t1.a等同于TestClass1.a ,因为a是静态成员(不是实例成员)。 编译器查看t1的声明以查看它的类型,然后将其视为使用类型名称。 永远不会检查t1的值。 (但如果它是一个方法调用,比如method(args).a ,我认为该方法将被调用。但返回值将被丢弃,并且从未检查过。)( 编辑:我已经validation了该method(args)被调用,但如果函数结果为null,则不会抛出exception。)

由于是静态编译器将其转换为TestClass1.a 。 对于非静态变量,它会抛出NullPointerException

可以通过Directly类名称访问任何静态成员作为TestClass1.a,不需要实例

  System.out.println(TestClass1 .a); 

输出:10