CodeGym /Java 博客 /随机的 /基类构造器
John Squirrels
第 41 级
San Francisco

基类构造器

已在 随机的 群组中发布
你好!上次我们谈到了构造函数,并学到了很多关于它们的知识。现在我们要谈谈基类构造函数
基类构造函数 - 1
什么是基类?它与这样一个事实有关,即在 Java 中,几个不同的类可以有一个共同的起源。
基类构造函数 - 2
这叫做继承。多个子类可以有一个共同的祖先。例如,假设我们有一个Animal类:

public class Animal {
  
   String name;
   int age;
}
我们可以声明 2 个子类:CatDog。这是使用关键字extends完成的。

public class Cat extends Animal {

}

public class Dog extends Animal {
  
}
我们将来可能会发现这很有帮助。例如,如果有一个抓老鼠的任务,我们将 Cat 在我们的程序中创建一个对象。如果任务是追逐一根棍子,那么我们将使用一个 Dog 对象。如果我们创建一个模拟兽医诊所的程序,它将与Animal 班级一起工作(因此能够同时治疗猫和狗)。 记住当一个对象被创建时,它的基类的构造函数首先被调用是非常重要的。只有在该构造函数完成后,程序才会执行与我们正在创建的对象对应的类的构造函数。换句话说,在创建Cat对象时,首先运行构造函数Animal然后才是Cat构造函数执行。要查看这一点,请向CatAnimal构造函数添加一些控制台输出。

public class Animal {

   public Animal() {
       System.out.println("Animal constructor executed");
   }
}


public class Cat extends Animal {

   public Cat() {
       System.out.println("Cat constructor executed!");
   }

   public static void main(String[] args) {
       Cat cat = new Cat();
   }
}
控制台输出: Animal constructor executed Cat constructor executed! 确实,它确实是这样工作的!为什么?原因之一是避免复制两个类之间共享的字段。例如,每只动物都有心脏和大脑,但并非每只动物都有尾巴。我们可以在父类中声明所有动物共有的大脑心脏Animal字段,并在子类中声明尾巴Cat字段。. 现在我们将声明一个Cat类构造函数,它接受所有 3 个字段的参数。

public class Cat extends Animal {

   String tail;

   public Cat(String brain, String heart, String tail) {
       this.brain = brain;
       this.heart = heart;
       this.tail = tail;
   }

   public static void main(String[] args) {
       Cat cat = new Cat("Brain", "Heart", "Tail");
   }
}
注意:即使类没有大脑和心脏领域,构造函数也能正常Cat工作。这些字段是从Animal基类“继承”而来的。继承类可以访问基类的字段,因此它们在我们的Cat类中是可见的。因此,我们不需要在Cat类中复制这些字段。我们可以把它们从Animal课堂上带走。更重要的是,我们可以在子类构造函数中显式调用基类构造函数。基类也称为“超类”。这就是为什么 Java 使用关键字super来表示基类。在前面的例子中

public Cat(String brain, String heart, String tail) {
       this.brain = brain;
       this.heart = heart;
       this.tail = tail;
   }
我们分别分配父类中的每个字段。我们实际上不必这样做。调用父类构造函数并传递必要的参数就足够了:

public class Animal {

   String brain;
   String heart;

   public Animal(String brain, String heart) {
       this.brain = brain;
       this.heart = heart;
   }

public class Cat extends Animal {

   String tail;

   public Cat(String brain, String heart, String tail) {
       super(brain, heart);
       this.tail = tail;
   }

   public static void main(String[] args) {
       Cat cat = new Cat("Brain", "Heart", "Tail");
   }
}
Cat构造函数中,我们调用了Animal构造函数并传递了两个字段。我们只有一个字段要显式初始化:tail,它不在Animal. 还记得我们提到过创建对象时首先调用父类构造函数吗?这就是为什么super() 应该始终在构造函数中排在第一位! 否则,将违反构造函数逻辑,程序将产生错误。

public class Cat extends Animal {

   String tail;

   public Cat(String brain, String heart, String tail) {
       this.tail = tail;
       super(brain, heart);// Error!
   }

   public static void main(String[] args) {
       Cat cat = new Cat("Brain", "Heart", "Tail");
   }
}
编译器知道创建子类的对象时,首先调用基类的构造函数。如果您尝试手动更改此行为,编译器将不允许。

对象是如何创建的

我们之前看过一个带有基类和父类的示例:AnimalCat。以这两个类为例,我们现在来看看创建对象和初始化变量的过程。我们知道有静态变量和实例(非静态)变量。我们也知道Animal基类有变量,Cat子类有自己的。为清楚起见,我们将分别向AnimalCat类添加一个静态变量。类中的 animalCount 变量将代表地球上动物物种Animal总数,而catCount变量将表示猫科动物的数量。此外,我们将为两个类中的所有非静态变量分配起始值(然后将在构造函数中更改)。

public class Animal {

   String brain = "Initial value of brain in the Animal class";
   String heart = "Initial value of heart in the Animal class";

   public static int animalCount = 7700000;

   public Animal(String brain, String heart) {
       System.out.println("Animal base class constructor is running");
       System.out.println("Have the variables of the Animal class already been initialized?");
       System.out.println("Current value of static variable animalCount = " + animalCount);
       System.out.println("Current value of brain in the Animal class = " + this.brain);
       System.out.println("Current value of heart in the Animal class = " + this.heart);
       System.out.println("Have the variables of the Cat class already been initialized?");
       System.out.println("Current value of static variable catCount = " + Cat.catCount);

       this.brain = brain;
       this.heart = heart;
       System.out.println("Animal base class constructor is done!");
       System.out.println("Current value of brain = " + this.brain);
       System.out.println("Current value of heart = " + this.heart);
   }
}

public class Cat extends Animal {

   String tail = "Initial value of tail in the Cat class";

   static int catCount = 37;

   public Cat(String brain, String heart, String tail) {
       super(brain, heart);
       System.out.println("The cat class constructor has started (The Animal constructor already finished)");
       System.out.println("Current value of static variable catCount = " + catCount);
       System.out.println("Current value of tail = " + this.tail);
       this.tail = tail;
       System.out.println("Current value of tail = " + this.tail);
   }

   public static void main(String[] args) {
       Cat cat = new Cat("Brain", "Heart", "Tail");
   }
}
所以我们正在创建该类的一个新实例Cat,它继承了Animal. 我们添加了一些详细的控制台输出以查看正在发生的事情以及发生的顺序。Cat这是创建对象 时将显示的内容:Animal 基类构造函数正在运行 Animal 类的变量是否已经初始化?静态变量animalCount当前值=7700000 Animal类brain当前值=Animal类brain初始值Animal类heart当前值=Animal类heart初始值已有Cat类变量初始化了吗?静态变量的当前值 catCount = 37 Animal 基类构造完成!brain 当前值 = Brain 当前值 heart = Heart cat 类构造函数已经启动(Animal 构造函数已经完成) 静态变量 catCount 当前值 = 37 tail 当前值 = Cat 类中 tail 的初始值 tail 当前值 =尾巴 因此,现在我们可以清楚地看到创建新对象时变量初始化和构造函数调用的顺序:
  1. 初始化基类( ) 的静态变量。Animal在我们的例子中,Animal类的变量animalCount设置为 7700000。

  2. 子类( ) 的静态变量Cat被初始化。

    注意:我们仍在Animal构造函数中,我们已经显示:

    Animal 基类构造函数正在运行
    Animal 类的变量是否已经初始化?
    静态变量animalCount当前值=7700000
    Animal类brain当前值=Animal类brain初始值Animal类
    heart当前值=Animal类heart初始值
    已有Cat类变量初始化了吗?
    静态变量 catCount 的当前值 = 37


  3. 然后初始化基类非静态变量。我们专门为它们分配了初始值,然后在构造函数中将其替换。Animal 的构造函数还没有完成,但是 brain 和 heart 的初始值已经赋值了:

    Animal 基类构造函数正在运行
    Animal 类的变量是否已经初始化?
    静态变量animalCount当前值=7700000
    Animal类brain当前值=Animal类brain初始值
    Animal类heart当前值=Animal类heart初始值


  4. 基类构造函数启动。
    我们已经确信这一步是第四步:在构造函数开始的前三步中Animal,许多变量已经被赋值。


  5. 子类( ) 的非静态字段Cat被初始化。
    这发生在Cat构造函数开始运行之前。
    当它开始运行时,tail 变量已经有了一个值:

    cat类的构造函数已经启动(Animal的构造函数已经完成) 静态变量catCount的当前值=37 tail的当前值=Cat类中tail的初始值


  6. Cat调用子类的构造函数

    这就是在 Java 中创建对象的样子!

    我必须说我们不是死记硬背的忠实拥护者,但最好记住变量初始化和构造函数调用的顺序

    这将大大增加您对程序流程以及任何特定时刻对象状态的理解。

    此外,许多类不使用继承。在这种情况下,与基类相关的步骤不适用。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION