【问题标题】:A call to a static method within the parameter list of super() is valid in Java. Why?在 super() 的参数列表中调用静态方法在 Java 中是有效的。为什么?
【发布时间】:2012-12-20 23:34:37
【问题描述】:

我们看下面的Java代码sn-p。

package trickyjava;

class A
{
    public A(String s)
    {
        System.out.println(s);
    }
}

final class B extends A
{
    public B()
    {
        super(method());      // Calling the following method first.      
    }

    private static String method()
    {
        return "method invoked";
    }
}

final public class Main
{
    public static void main(String[] args)
    {
        B b = new B();
    }
}

按照惯例,Java 中的 super() 构造函数必须是相关构造函数主体中的第一条语句。在上面的代码中,我们调用了 super() 构造函数参数列表本身 super(method()); 中的 static 方法。


表示在构造函数B()中对super的调用中,正在调用一个方法 在调用 super 之前调用!这应该被编译器禁止,但效果很好。这在某种程度上等同于以下陈述。

String s = method();
super(s);

但是,导致编译时错误表明“对 super 的调用必须是构造函数中的第一条语句”是非法的。为什么?为什么它等效的 super(method()); 是有效的并且编译器不再抱怨?

【问题讨论】:

  • super 是构造函数中的第一条语句,尽管没有被首先调用。规范声明The first statement of a constructor body may be an explicit invocation of another constructor ...,而不是必须首先调用它。

标签: java static


【解决方案1】:

这里的关键是static 修饰符。静态方法绑定到,实例方法(普通方法)绑定到对象(类实例)。构造函数从一个类初始化一个对象,因此该类必须已经完全加载。因此,调用静态方法作为构造函数的一部分是没有问题的。

加载类和创建对象的事件顺序如下:

  1. 加载类
  2. 初始化静态变量
  3. 创建对象
  4. 初始化对象
  5. 对象现在可以使用了

(简化*)

当对象构造函数被调用时,静态方法和变量就可用了。

将类及其static 成员视为该类对象的蓝图。只有当蓝图已经存在时,您才能创建对象。

构造函数也称为初始化器。如果您从构造函数中抛出异常并打印堆栈跟踪,您会注意到它在堆栈帧中被称为<init>。实例方法只能在构造对象后调用。不能在构造函数中使用实例方法作为 super(...) 调用的参数。

如果您创建同一类的多个对象,则步骤 1 和 2 只发生一次。

(*为清楚起见,省略了静态初始化器和实例初始化器)

【讨论】:

  • 实际上,如果您从其他地方(例如,静态方法或公共静态字段,或初始化方法)。你就是不能用“this”。
【解决方案2】:

是的,检查一下 JVM 规范(虽然承认是旧的):

在实例 init 方法中,在调用同一类中的另一个 init 方法或超类中的 init 方法之前,不得出现对“this”的引用(包括 return 的隐式引用)。

据我所知,这确实是唯一真正的限制。

【讨论】:

    【解决方案3】:

    要求首先调用超级构造函数的目的是确保“超级对象”在使用之前被完全初始化(它实际上没有强制执行,因为超级构造函数可能会泄漏this,但那是另一件事)。

    this 上调用非静态方法将允许该方法看到未初始化的字段,因此被禁止。静态方法只有在将this 作为参数传递时才能看到这些字段。由于在超级构造函数调用表达式中访问thissuper 是非法的,并且对超级的调用发生在声明可能指向this 的任何变量之前,因此允许在超级构造函数调用表达式中调用静态方法是安全的。

    它也很有用,因为它允许以任意复杂的方式计算超级构造函数的参数。如果不允许调用静态方法,就不可能在这样的计算中使用控制流语句。一些简单的事情:

    class Sub extends Super {
        Sub(Integer... ints) {
            super(Arrays.asList(ints));
        }
    }
    

    这是不可能的。

    【讨论】:

      【解决方案4】:

      这是一种情况,java 语法隐藏了真正发生的事情,而 C# 使它更清晰一些。

      在 C# 中,你的 B 看起来像

      class B : A {
          public B() : base(method()) {
          }
      
          private static String method() {
              return "method invoker";
          }
      }
      

      虽然 java 语法将 super(method) 放在构造函数中,但它并没有真正在那里调用:所有父初始化都在您的子类构造函数之前运行。 C# 代码更清楚地显示了这一点;通过将super(method()) 放在java 构造函数的第一行,您只是告诉java 使用超类的参数化构造函数而不是无参数版本;这样,您可以将变量传递给父构造函数,并且它们将用于父级字段的初始化您的孩子的构造函数代码运行之前。

      super(method()) 有效的原因(作为 java 构造函数中的第一行)是因为 method() 正在加载静态元素——在非静态元素之前,包括构造函数——这允许它不仅要在B() 之前调用,还要在A(String) 之前调用。通过说

      public B() {
         String s = method();
         super(s);
      }
      

      您告诉 java 编译器使用默认构造函数初始化超级对象(因为对 super() 的调用不是第一行)并且您已准备好初始化子类,但是编译器随后变得混乱当它看到您在 super() 已经运行之后尝试使用 super(String) 进行初始化时。

      【讨论】:

        【解决方案5】:

        在 java 中调用 super 是必须的,以允许父类在子类开始之前初始化。

        在上面的例子中,如果java在调用super之前允许String s= method();,它打开了在调用super之前可以做的事情的闸门。那会冒很多风险,本质上是允许使用半生不熟的课程。这是正确的不允许的。它将允许在正确创建对象状态(其中一些可能属于父对象)之前对其进行修改。

        super(method());调用的情况下,我们仍然坚持先完成父初始化的策略。我们只能使用静态成员,并且子类的静态成员在任何子对象创建之前都是可用的。因此该方法可用并且可以调用。

        【讨论】:

          【解决方案6】:

          好的..我认为,这个可能是相关的,如果我们用 Super 调用某个成员,那么它首先尝试在 super 类中调用,如果它没有找到相同的,那么它会尝试在子类中调用相同的方法。

          PS:如果我错了,请纠正我

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-12-29
            • 2021-10-22
            • 2011-03-12
            • 1970-01-01
            相关资源
            最近更新 更多