【问题标题】:Calling of Constructors in a JavaJava中构造函数的调用
【发布时间】:2015-08-03 23:43:36
【问题描述】:

在 Java: The complete reference 一书中

// Demonstrate when constructors are called.
// Create a super class.
class A {
     A() {
        System.out.println("Inside A's constructor.");
     }
}
// Create a subclass by extending class A.
class B extends A {
      B() {
             System.out.println("Inside B's constructor.");
      }
 }
// Create another subclass by extending B.
class C extends B {
    C() {
        System.out.println("Inside C's constructor.");
    }
}
class CallingCons {
    public static void main(String args[]) {
        C c = new C();
    }
}

输出: 在 A 的构造函数内部 B 的构造函数内部 C 的构造函数内部

它演示了如何调用子类的构造函数。但是为什么在没有 super() 构造函数的情况下调用超类的构造函数。

为什么 Java 语言设计者认为有必要这样做?

【问题讨论】:

  • 除非super(...) 被显式调用为子构造函数的第一条语句,JVM 会自动添加对默认父构造函数的调用。这称为构造函数链接

标签: java oop constructor


【解决方案1】:

正如其他人指出的那样,如果您不使用 super(...) 调用启动构造函数,编译器将为您调用 super()

至于为什么,你必须首先记住构造函数的用途:初始化对象。具体是什么意思?在实践中,这意味着为对象的字段赋值,并建立不变量。

如果不调用 super()BA 类将没有机会为它们包含的任何字段执行此操作。如果这些字段是私有的,您甚至不能让C() 构造函数为它们执行此操作,因为在您的类之外无法访问私有字段(甚至您的超类的字段也无法访问)。即使您可以,这也不是一个好主意;它也会破坏封装。例如,想象一下,如果一个超类(可能是一个您不熟悉其内部结构的复杂类)突然决定更改其实现细节,则必须更改您的代码。

为了说明这一点,考虑一组非常简单的类:

public class Super {
    private final String name;

    Super() {
        name = "default";
    }

    public String name() {
        return name.toUpperCase();
    }
}

public class Sub extends Super {
    public Sub() {
        // don't do anything
    }
}

当您实例化Sub 时,它将通过调用Super 的构造函数开始。如果没有,name 字段将为 null(引用类型的默认值)。但是name() 方法不检查null;它假定引用是非空的,因为构造函数建立了该不变量。所以,在我们不调用超级构造函数的伪 Java 中,Super.name 必须变得更复杂一些——它必须检查 name == null

您可以想象,随着类获得更多字段,具有更多有趣的不变量,这个玩具示例会变得越来越复杂。强制您调用超级构造函数(显式或隐式)让该超级类的作者建立他们的不变量,从而生成更简单、更易于维护的代码。

【讨论】:

    【解决方案2】:

    每个构造函数都调用它的超类构造函数。 super() 调用发生在构造函数的第一行。来自 javadoc:

    如果构造函数没有显式调用超类构造函数,Java 编译器会自动插入对 超类的无参数构造函数。如果超类没有 有一个无参数的构造函数,你会得到一个编译时错误。 Object 确实有这样的构造函数,所以如果 Object 是唯一的 超类,没问题。

    更多here

    【讨论】:

    • 你打败了我......很有趣,看看本教程与规范相比如何使用不同的措辞。
    • 为什么 Java 语言设计者认为有必要这样做?
    【解决方案3】:

    因为它在Java Language Specification 中是这样说的。

    如果构造函数体不是以显式构造函数调用开始,并且被声明的构造函数不是原始类 Object 的一部分,则构造函数体隐式以超类构造函数调用“super();”开始,调用其直接超类的构造函数,不带参数。

    【讨论】:

    • 为什么 Java 语言设计者认为有必要这样做?
    • @bhavya - 即使您的教程中的示例没有显示它,每个 类 - 每个派生类都通过父基类 - 可能需要它自己的类- 特定的初始化(“构造”)。 “super()” 就是这样发生的。
    【解决方案4】:

    甚至它对抽象类也有作用。我们无法初始化抽象类的对象。但是 Abstract 类的 Child 类默认调用 super() 方法。所以抽象类构造函数可以初始化它的实例变量。
    例如:

    public abstract class TestA {
    
        private int a;
        public TestA()
        {
            a=10;
        }
        public int displayA()
        {
            return a;
        }
    
        abstract void display();
    
    }
    
    public class TestB extends TestA{
    
        @Override
        void display() {
    
           System.out.println("this is class B");
    
        }
    
    }
    
    package Abstract;
    
    public class TestMain {
    
    
        public static void main(String[] args) {
            TestA obj= new TestB();
            System.out.println(obj.displayA());
        }
    }
    

    输出为:10 在这里你可以看到,当我们初始化类 TestB 的对象时,默认情况下调用超级构造函数,并且 TestA 的构造函数正在分配 a 的值。如果默认不调用super,我们就不能分配抽象类的实例变量。

    【讨论】:

      【解决方案5】:

      继承基本上是继承父类的所有属性。因此,如果调用子类构造函数,它肯定也应该默认继承其所有父类属性。在下面的代码中,class A 的所有属性也应该在class B 中可用,所以如果我只是调用 B 的构造函数,则所有类 A 的属性(私有除外)也被初始化并可用,这意味着 B 继承了A的属性

      class A {
          protected int a;
          A() {
              a=12;
             System.out.println("Inside A's constructor.");
          }
      }
      
      class B extends A {
           B() {
                  System.out.println("Inside B's constructor.");
                  System.out.println(a);
           }
      }
      
      public class ConstructorInheritance {
         public static void main(String args[]) {
             B b=new B();
         }
      }
      
      output:
      Inside A's constructor.
      Inside B's constructor.
      12
      

      【讨论】:

        【解决方案6】:

        假设 C 类访问 B 类或 A 类的未初始化变量。隐式调用 B 类的构造函数-->A 类确保您始终访问继承类(A 或 B)的初始化变量

        【讨论】:

          【解决方案7】:

          《Java 编程语言》说“子类中的构造函数可以初始化其各自的状态,但是,由于遵守约定,只有超类知道如何初始化超类的状态”。

          因此,必须调用超类的构造函数。构造函数的处理方式有先后顺序:

          • 调用超类构造函数
          • 使用初始化器和初始化块初始化字段
          • 构造函数的执行体

          有关详细信息,请参阅本书的“3.2”部分。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-02-04
            • 2017-07-28
            • 2018-02-28
            • 2015-07-02
            • 1970-01-01
            • 2011-08-31
            相关资源
            最近更新 更多