コンストラクターがどのように呼び出されるのか、フィールド (静的フィールドを含む) がどのようにどのような順序で初期化されるのかなど、最初から最後まで分析します。この記事で説明したいくつかの点については以前に触れたので、ざっと目を通すことができます。基本クラスのコンストラクターのマテリアル。まず、オブジェクトがどのように作成されるかを思い出してください。開発者の観点からこのプロセスがどのように見えるかをよく覚えているでしょう。開発者はクラスを作成し、 を書き込みnew、すべての準備が整います:) ここでは、次のような書き込みを行うときにコンピュータと Java マシンの内部で何が起こるかについて説明します。
Cat cat = new Cat();
これについては以前にもお話しましたが、念のため思い出してください。
- まず、オブジェクトを保存するためのメモリが割り当てられます。
- 次に、Java マシンはオブジェクトへの参照を作成します (この場合、参照は Cat cat です)。
- 最後に、変数が初期化され、コンストラクターが呼び出されます (このプロセスについては後で詳しく説明します)。
最初の 2 つの点については、特別な疑問は生じません。メモリ割り当ては単純なプロセスであり、考えられる結果は 2 つだけです。メモリがあるかないかのどちらかです :) そして、リンクの作成は珍しいことではありません。しかし、3 番目の点は、厳密な順序で実行される一連の操作全体を表します。私は詰め込みテストは好きではありませんが、このプロセスをよく理解し、この一連の操作を覚える必要があります。。前回のレッスンでオブジェクトの作成プロセスについて説明したとき、継承についてはまだ何も知らなかったため、いくつかのことを説明するのが困難でした。これで、かなり多くのことがわかったので、ようやくこの質問を完全に検討することができます :) 3 番目のポイントは、「最後に、変数が初期化され、コンストラクターが呼び出されます。」とありますが、これはすべてどのような順序で行われるのでしょうか? 理解を深めるために、親と子という 2 つの非常に単純なクラスを作成してみましょう。
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クラスは、年式、モデル、最高速度を表すフィールドを持つトラックの実装です。ここで、そのようなオブジェクトを 1 つ作成したいと思います。
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++; } }が表示される
Truckときにトラックのフィールドがまだ初期化されていないことを確認するために、コンストラクターの先頭に println ステートメントを意図的に配置しています。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。 -
まだコンストラクターの出番ではありません!変数の初期化が続行されます。 親クラスの非静的変数は 3 番目に初期化されます。 ご覧のとおり、継承によりオブジェクトの作成プロセスが大幅に複雑になりますが、それに関してできることは何もありません。プログラミングでいくつかのことを覚えておく必要があるだけです :)
実験として、クラス
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フィールドにすでに値が割り当てられていることを証明します。 -
いよいよコンストラクターの出番です! より正確には、基底クラスのコンストラクターの出番です。これは、オブジェクト作成プロセスの 4 番目のステップで呼び出されます。
これもかなり簡単に検証できます。2 行をコンソールに出力してみましょう。1 行目は
Vehicle基本クラスのコンストラクター内で、2 行目は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。インスタンス化されるクラス内のフィールドは、5 番目のステップまで初期化されません。驚くべきことですが、本当です :) もう一度、親クラスの場合と同じように簡単なチェックを行います。変数に初期値を設定し、コンストラクター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呼び出されます。そして最後に、この時点で初めて、インスタンス化しているクラスのコンストラクターが呼び出されます。
6 番目のステップでのみ、トラックに引数として渡す値がフィールドに割り当てられます。
ご覧のとおり、トラックの「構築」、つまりオブジェクトの作成プロセスは簡単ではありません。しかし、それを最小の部分に分割したようです:)
このプロセスをよく理解することがなぜそれほど重要なのでしょうか? 「内部で」何が起こっているのかを正確に知らなかった場合、普通のオブジェクトを作成した結果がどれほど予期せぬものになるかを想像してみてください:) さて、コースに戻っていくつかのタスクを完了する時が来ました。頑張って、また会いましょう! :)
GO TO FULL VERSION