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

时间:2014-01-10 06:16:14

标签: java static nullpointerexception javac

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

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

7 个答案:

答案 0 :(得分:27)

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

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

  

可以使用空引用来访问类(静态)变量,而不会导致异常。

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”表明主表达式确实在运行时完全评估,尽管事实上只使用其类型而不是其值来确定要访问的字段(因为该字段)山是静止的)。

答案 1 :(得分:11)

如果您使用以下代码反汇编类文件,请在当前答案中添加一些其他信息。

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."<init>":()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),他们仅使用run time constant pool的索引来解析字段/方法,该索引稍后将用于解决字段引用位置。

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

答案 2 :(得分:2)

代码中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 。如果方法,领域等 是静态它的保证了这个参考的缺席,所以会有 没有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

答案 3 :(得分:2)

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

请记住,这些静态成员不属于特定的类实例。 More info here. 我已经提供了一个小代码片段供参考:

@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? 
  // i.e last statement on the console or somewhere in the middle :)
  static {
    System.out.println("It should be printed at the end or would it?");
   }
 }

答案 4 :(得分:1)

在这种情况下,

t1.a相当于TestClass1.a,因为a是静态成员(不是实例成员)。编译器查看t1的声明以查看它的类型,然后将其视为使用类型名称。永远不会检查t1的值。 (但如果它是方法调用,如method(args).a,我认为该方法将被调用。但返回值将被丢弃,并且从未检查过。)(编辑:我'已验证调用了method(args),但如果函数结果为null,则不会抛出异常。)

答案 5 :(得分:0)

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

答案 6 :(得分:-1)

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

  System.out.println(TestClass1 .a);

输出: 10