16

可能重复:
Java 中空引用上的静态字段

我知道静态方法是在类级别的。所以我知道我不需要创建实例来调用静态方法。但我也知道我可以将静态方法称为实例方法。这是我感到困惑的地方,因为我期待NullPointerException从空对象调用静态方法(如调用实例方法)。我真的很感激一些解释为什么我在NullPointerException这里期望是错误的。

这是示例代码:

public class SampleClass {

    public static int getSumStatic(int x, int y){
        return x+y;
    }

    public int getDifferenceInstance(int x, int y){
        return x-y;
    }
}

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
    }
}
4

6 回答 6

14

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

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

来自Java 语言规范

第 15.12.1 节

❖ 如果形式是Primary.NonWildTypeArgumentsopt Identifier,那么方法的名称就是标识符。令 T 为 Primary 表达式的类型。如果 T 是类或接口类型,则要搜索的类或接口是 T,如果 T 是类型变量,则为 T 的上限。

第 15.12.4.1 节:

  1. 如果涉及包含 Primary 的 MethodInvocation 的第二个产生式,则有两个子情况:

❖ 如果调用方式是静态的,那么就没有目标引用。表达式 Primary 被评估,但结果随后被丢弃。

于 2012-09-30T21:58:34.357 回答
3

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

您可以在why-isnt-calling-a-static-method-by-way-of-an-instance-an-error-for-the-java-co看到一些关于这个问题的信息

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

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

SampleClass sampleObject;
sampleObject.getSumStatic(2, 2) 

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

于 2012-09-30T22:06:30.140 回答
2

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

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

SampleClass.getSumStatic(2, 2);
于 2012-09-30T21:59:46.150 回答
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 处的指令执行传统的静态调用:它是invokestatic调用Example.staticMethod(). 正如您所料,这不涉及实例变量。

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

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

于 2012-09-30T22:20:13.083 回答
1

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

没有隐含thispublic static int getSumStatic因此无法访问空指针。

于 2012-09-30T21:58:54.293 回答
0

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

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

于 2012-09-30T22:02:13.887 回答