我们会从头到尾分析一下:构造函数是怎么调用的,字段(包括静态字段)是怎么初始化的,按照什么顺序初始化的等等。文章中讨论的一些点我们之前已经触及到了,大家可以浏览一下关于基类构造函数的材料。首先,让我们回顾一下对象是如何创建的。你很清楚从开发者的角度来看这个过程是怎样的:他创建了一个类,写了new,然后一切就绪了:)这里我们将讨论当我们写的时候计算机和 Java 机器内部发生了什么,例如:
Cat cat = new Cat();
我们之前已经讨论过这个问题,但为了以防万一,我们会提醒您:
- 首先,分配存储对象的内存。
- 接下来,Java 机器创建对象的引用(在我们的例子中,引用是 Cat cat)。
- 最后,初始化变量并调用构造函数(我们将更详细地研究这个过程)。
前两点不应引起任何特殊问题。内存分配是一个简单的过程,只有两种可能的结果:要么有内存,要么没有 :) 而创建链接并不罕见。但第三点代表的是一整套严格按顺序执行的操作。我不喜欢死记硬背测试,但是你需要很好地理解这个过程并且你需要记住这个操作顺序. 前面几节课讲对象创建过程的时候,你对继承一无所知,所以有些东西解释起来有问题。现在你知道了很多,我们终于可以完整地考虑这个问题了:)所以第三点说“最后,变量被初始化,构造函数被调用。 ”但是这一切发生的顺序是什么?为了更好地理解,让我们创建两个超级简单的类——一个父类和一个子类:
public class Vehicle {
public static int vehicleCounter = 0;
private String description = "Vehicle";
public Vehicle() {
}
public String getDescription() {
return description;
}
}
public class Truck extends Vehicle {
private static int truckCounter = 0;
private int yearOfManufacture;
private String model;
private int maxSpeed;
public Truck(int yearOfManufacture, String model, int maxSpeed) {
this.yearOfManufacture = yearOfManufacture;
this.model = model;
this.maxSpeed = maxSpeed;
Vehicle.vehicleCounter++;
truckCounter++;
}
}
该类Truck是卡车的一个实现,其中包含代表其年份、型号和最大速度的字段。现在我们要创建一个这样的对象:
public class Main {
public static void main(String[] args) throws IOException {
Truck truck = new Truck(2017, "Scania S 500 4x2", 220);
}
}
对于 Java 机器,该过程将如下所示:
-
发生的第一件事是类的静态变量
Vehicle被初始化。是的,我说的是Vehicle类,不是Truck。静态变量在调用构造函数之前初始化,这从父类开始。让我们尝试验证这一点。我们将类vehicleCounter中的字段设置Vehicle为 10 并尝试在Vehicle和Truck构造函数中显示它。public class Vehicle { public static int vehicleCounter = 10; private String description = "Vehicle"; public Vehicle() { System.out.println(vehicleCounter); } public String getDescription() { return description; } } public class Truck extends Vehicle { private static int truckCount = 0; private int yearOfManufacture; private String model; private int maxSpeed; public Truck(int yearOfManufacture, String model, int maxSpeed) { System.out.println(vehicleCounter); this.yearOfManufacture = yearOfManufacture; this.model = model; this.maxSpeed = maxSpeed; Vehicle.vehicleCounter++; truckCount++; } }我们特意将 println 语句放在构造函数的最开头,以确保在显示
Truck时卡车的字段尚未初始化。vehicleCounter结果如下:
10 10 -
父类的静态变量初始化完成后,再对子类的静态变量进行初始化。在我们的例子中,这是班级
truckCounter的领域Truck。让我们做另一个实验,我们将尝试在初始化其他字段之前显示构造函数
truckCounter内部的值:Truckpublic class Truck extends Vehicle { private static int truckCounter = 10; private int yearOfManufacture; private String model; private int maxSpeed; public Truck(int yearOfManufacture, String model, int maxSpeed) { System.out.println(truckCounter); this.yearOfManufacture = yearOfManufacture; this.model = model; this.maxSpeed = maxSpeed; Vehicle.vehicleCounter++; truckCounter++; } }如您所见,在构造函数开始时,值 10已经分配给我们的静态变量
Truck。 -
现在还不是构造函数的时间!变量初始化继续。 第三次初始化父类的非静态变量。 如您所见,继承使创建对象的过程变得非常复杂,但是您对此无能为力:您只需要记住编程中的一些东西:)
作为实验,我们可以
description在Vehicle类中为变量赋一些初始值,然后在构造函数中更改它。public class Vehicle { public static int vehicleCounter = 10; private String description = "Initial value of the description field"; public Vehicle() { System.out.println(description); description = "Vehicle"; System.out.println(description); } public String getDescription() { return description; } }让我们运行
main()创建卡车的方法:public class Main { public static void main(String[] args) throws IOException { Truck truck = new Truck(2017, "Scania S 500 4x2", 220); } }我们得到以下结果:
Initial value of the description field Vehicle这证明在
Vehicle构造函数开始时该description字段已经被赋值。 -
最后,是构造函数的时间了! 更准确地说,是基类构造函数的时候了。它在对象创建过程的第四步被调用。
这也很容易验证。让我们尝试向控制台输出两行:一行在
Vehicle基类构造函数中,第二行在Truck构造函数中。我们需要确信Vehicle首先显示里面的行:public Vehicle() { System.out.println("Hello from the Vehicle constructor!"); } public Truck(int yearOfManufacture, String model, int maxSpeed) { System.out.println("Hello from the Truck constructor!"); this.yearOfManufacture = yearOfManufacture; this.model = model; this.maxSpeed = maxSpeed; Vehicle.vehicleCounter++; truckCounter++; }我们将运行我们的
main()方法并查看结果:Hello from the Vehicle constructor! Hello from the Truck constructor!出色的。这意味着我们没有弄错 :) 让我们继续。
-
现在是初始化子类(即我们的类)的非静态字段的
Truck时候了。被实例化的类中的字段直到第五步才被初始化!令人惊讶,但确实如此 :) 同样,我们将做一个简单的检查——就像父类一样:我们将一些初始值赋给变量,maxSpeed并在Truck构造函数中检查该值是否在构造函数启动之前分配:public class Truck extends Vehicle { private static int truckCounter = 10; private int yearOfManufacture; private String model; private int maxSpeed = 150; public Truck(int yearOfManufacture, String model, int maxSpeed) { System.out.println("Initial value of maxSpeed = " + this.maxSpeed); this.yearOfManufacture = yearOfManufacture; this.model = model; this.maxSpeed = maxSpeed; Vehicle.vehicleCounter++; truckCounter++; } }控制台输出:
Initial value of maxSpeed = 150可以看到, 构造函数启动时
Truck,maxSpeed已经等于150了! -
Truck调用子类的构造函数。只有在这一点上,最后,我们正在实例化的类的构造函数才会被调用!
只有在第六步中,这些字段才会被赋予我们作为参数传递给卡车的值。
如您所见,“构建”一辆卡车,即对象创建过程,并不简单。但我们似乎已将其分解为最小的部分 :)
为什么理解这个过程如此重要?想象一下,如果您不知道“幕后”到底发生了什么,那么创建一个普通对象的结果会是多么出乎意料:)现在是时候返回课程并完成一些任务了!祝你好运,很快见到你!:)
GO TO FULL VERSION