为什么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