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

27

我在讲一些技巧时发现了这个。以下是代码:

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


2
很遗憾(Java)编译器允许这样做,这会导致混淆。它甚至会产生警告吗? - M. Mimpen
3
如果这是一个 eclipse 警告,那么警告信息会显示为“应该以静态方式访问 static 字段”。请注意,我只进行翻译,不提供任何解释或额外的信息。 - Suresh Atta
@M.Mimpen 是的,完全正确...但是它涉及到了 NullPointer。 现在已经清楚了。 - Not a bug
7个回答

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 ”表明在运行时Primary expression确实已经完全评估,尽管仅使用其类型(因为mountain字段是静态的)来确定要访问哪个字段,而不是其值。

运行时很聪明。 - Not a bug
1
@KishanSarsechaGajjar 是的,我编辑了我的帖子 :) - Suresh Atta

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
}

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

*static指令有一个特殊性质,即在调用它们之前不需要在堆栈中引用对象实例(例如,虚拟方法调用需要在堆栈中引用对象),它们使用运行时常量池中的索引来解析字段/方法,以后将用于解决字段引用位置。

这是一些IDE会警告您的技术原因"应该以静态方式访问静态字段",因为解析静态字段不需要对象实例。


2

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

  TestClass t = null;
  t.SomeMethod();

如果SomeMethod是一个实例方法,它将使用标准的“this reference”来执行某些操作。
  public void SomeMethod() {
    // Here we'll have a NullPointerException (since "this" is null)
    this.SomeField = ... // <- We usually omit "this" in such a code
  }

由于thisnull,所以我们会有一个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

2
静态成员由类的所有实例共享,而不是单独重新分配给每个实例。这些成员在首次遇到该类时由类加载器加载。 一个相关的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? 
  // 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?");
   }
 }

我在加粗字体中提到了一些内容...如果可能的话,请在回答前先阅读问题。 但是,对于“静态”概念的清晰示例给予+1。 - Not a bug
1
我的错...回答问题时应该更小心。 - Anupam Saini

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

0

任何静态成员都可以通过直接使用类名来访问,例如TestClass1.a,不需要实例。

  System.out.println(TestClass1 .a);

输出: 10


我在粗体字中提到了一些内容...如果可能的话,请在回答前阅读问题。 - Not a bug

0

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接