1. 変数の初期化

すでにご存知のとおり、クラス内で複数の変数を宣言でき、単に宣言するだけでなく、初期値で即座に初期化することもできます。

これらの同じ変数はコンストラクターでも初期化できます。これは、理論的には、これらの変数に 2 回値を割り当てることができることを意味します。例

コード ノート
class Cat
{
   public String name;
   public int age = -1;

   public Cat(String name, int age)
   {
     this.name = name;
     this.age = age;
   }

   public Cat()
   {
     this.name = "Nameless";
   }
}



変数ageには初期値が割り当てられます。




初期値は上書きされます。


age 変数には初期値が格納されます。
 Cat cat = new Cat("Whiskers", 2);
これは許可されます。最初のコンストラクターが呼び出されます。
 Cat cat = new Cat();
これは許可されます。2 番目のコンストラクターが呼び出されます。

が実行されると、次のことが起こりますCat cat = new Cat("Whiskers", 2);

  • オブジェクトCatが作成されます
  • すべてのインスタンス変数は初期値で初期化されます
  • コンストラクターが呼び出され、そのコードが実行されます。

つまり、変数は最初に初期値を取得し、それから初めてコンストラクターのコードが実行されます。


2. クラス内の変数の初期化の順序

変数は、コンストラクターの実行前に単に初期化されるのではなく、明確に定義された順序、つまりクラス内で宣言された順序で初期化されます。

いくつかの興味深いコードを見てみましょう。

コード ノート
public class Solution
{
   public int a = b + c + 1;
   public int b = a + c + 2;
   public int c = a + b + 3;
}

変数が作成された時点では変数 と変数がまだa存在しないため、このコードはコンパイルされません 。ただし、次のようにコードを記述することもできます。このコードはコンパイルされ、問題なく実行されます。bc

コード ノート
public class Solution
{
   public int a;
   public int b = a + 2;
   public int c = a + b + 3;
}


0
0+2
0+2+3

ただし、コードは他の開発者に対して透過的でなければならないことに注意してください。コードの可読性が損なわれるため、このような手法は使用しないほうがよいでしょう。

ここで、変数に値が割り当てられる前に、変数にはデフォルト値があることを覚えておく必要があります。タイプの場合int、これはゼロです。

JVM がa変数を初期化するとき、単に int 型のデフォルト値 0 を割り当てます。

に達するとb、変数はすでに認識されており、値を持っているため、JVM はそれに値 2 を割り当てます。

変数に到達するとc、変数ab変数はすでに初期化されているため、JVM はc0+2+3 の初期値を簡単に計算します。

メソッド内に変数を作成する場合、事前に値を割り当てていない限り、その変数を使用することはできません。しかし、これはクラスの変数には当てはまりません。クラスの変数に初期値が割り当てられていない場合は、デフォルト値が割り当てられます。


3. 定数

オブジェクトがどのように作成されるかを分析しているときに、定数、つまり修飾子を使用した変数の初期化についても触れておく価値がありますfinal

変数にfinal修飾子がある場合は、初期値を割り当てる必要があります。これについてはすでにご存知であり、何も驚くべきことではありません。

しかし、コンストラクターで初期値を割り当てる場合、すぐに初期値を割り当てる必要はないということはわかりません。これは最終変数ではうまく機能します。唯一の要件は、複数のコンストラクターがある場合、各コンストラクターで最終変数に値を割り当てる必要があることです。

例:

public class Cat
{
   public final int maxAge = 25;
   public final int maxWeight;

   public Cat (int weight)
   {
     this.maxWeight = weight; // Assign an initial value to the constant
   }
}


4. コンストラクター内のコード

コンストラクターに関する重要な注意事項がいくつかあります。その後、Java の学習を続けると、継承、シリアル化、例外などに遭遇するでしょう。これらはすべて、さまざまな程度でコンストラクターの動作に影響を与えます。今、これらの話題を深く掘り下げるのは意味がありませんが、少なくとも触れておく義務があります。

たとえば、コンストラクターに関する重要な注意事項を次に示します。理論的には、コンストラクターでは任意の複雑なコードを作成できます。しかし、これはやめてください。例:

class FilePrinter
{
   public String content;

   public FilePrinter(String filename) throws Exception
   {
      FileInputStream input = new FileInputStream(filename);
      byte[] buffer = input.readAllBytes();
      this.content = new String(buffer);
   }

   public void printFile()
   {
      System.out.println(content);
   }
}






ファイル読み取りストリームを開く
ファイルをバイト配列に読み取る
バイト配列を文字列として保存する




ファイルの内容を画面に表示する

FilePrinter クラスのコンストラクターでは、ファイル上のバイト ストリームをすぐに開き、その内容を読み取りました。これは複雑な動作であるため、エラーが発生する可能性があります。

そのようなファイルがなかったらどうなるでしょうか? ファイルの読み取りに問題があった場合はどうすればよいでしょうか? 大きすぎるとどうなるでしょうか?

複雑なロジックはエラーの可能性が高いことを意味し、コードが例外を正しく処理する必要があることを意味します。

例 1 — シリアル化

標準的な Java プログラムでは、自分がクラスのオブジェクトを作成しない状況がたくさんあります。たとえば、ネットワーク経由でオブジェクトを送信することにしたとします。この場合、Java マシン自体がオブジェクトをバイトのセットに変換して送信し、そのバイトのセットからオブジェクトを再作成します。

ただし、ファイルが他のコンピュータに存在しないとします。コンストラクターでエラーが発生し、誰もそれを処理しません。そして、それはプログラムを終了させる可能性があります。

例 2 — クラスのフィールドの初期化

クラス コンストラクターがチェック例外をスローできる場合、つまり throws キーワードがマークされている場合は、オブジェクトを作成するメソッドで示された例外をキャッチする必要があります。

しかし、そのような方法がない場合はどうなるでしょうか? 例:

コード  ノート
class Solution
{
   public FilePrinter reader = new FilePrinter("c:\\readme.txt");
}
このコードはコンパイルされません。

クラスコンストラクターはチェック例外をFilePrinterスローする可能性があります。これは、オブジェクトを try-catch ブロックでラップしないとオブジェクトを作成できないことを意味します。また、try-catch ブロックはメソッド内でのみ記述できます。FilePrinter



5. 基本クラスのコンストラクター

前回のレッスンでは、継承について少し説明しました。残念ながら、継承と OOP に関する完全な説明は OOP 専用のレベルに限定されており、コンストラクターの継承はすでに私たちに関連しています。

クラスが別のクラスを継承する場合、親クラスのオブジェクトはクラスのオブジェクト内に埋め込まれます。さらに、親クラスには独自の変数と独自のコンストラクターがあります。

つまり、クラスに親クラスがあり、その変数とメソッドを継承する場合、変数がどのように初期化され、コンストラクターが呼び出されるのかを知って理解することが非常に重要です。

クラス

変数が初期化され、コンストラクターが呼び出される順序をどのようにして知ることができるのでしょうか? まずは 2 つのクラスのコードを書いてみましょう。一方が他方を継承します。

コード ノート
class ParentClass
{
   public String a;
   public String b;

   public ParentClass()
   {
   }
}

class ChildClass extends ParentClass
{
   public String c;
   public String d;

   public ChildClass()
   {
   }
}










クラスはクラスChildClass を継承しますParentClass

変数が初期化され、コンストラクターが呼び出される順序を決定する必要があります。ログ記録はこれを行うのに役立ちます。

ロギング

ロギングは、プログラムの実行中に実行されるアクションをコンソールまたはファイルに書き込むことによって記録するプロセスです。

コンストラクターが呼び出されたことを判断するのは非常に簡単です。コンストラクターの本体で、コンソールにメッセージを書き込みます。しかし、変数が初期化されたかどうかはどうやって判断できるのでしょうか?

実際、これもそれほど難しいことではありません。変数の初期化に使用される値を返す特別なメソッドを作成し、初期化をログに記録します。コードは次のようになります。

最終コード

public class Main
{
   public static void main(String[] args)
   {
      ChildClass obj = new ChildClass();
   }

   public static String print(String text)
   {
      System.out.println(text);
      return text;
   }
}

class ParentClass
{
   public String a = Main.print("ParentClass.a");
   public String b = Main.print("ParentClass.b");

   public ParentClass()
   {
      Main.print("ParentClass.constructor");
   }
}

class ChildClass extends ParentClass
{
   public String c = Main.print("ChildClass.c");
   public String d = Main.print("ChildClass.d");

   public ChildClass()
   {
      Main.print("ChildClass.constructor");
   }
}




ChildClassオブジェクト の作成


このメソッドは、渡されたテキストをコンソールに書き込み、それを返します。Display textクラス





を宣言し、それを使用して変数を初期化します。コンストラクターが呼び出されたというメッセージを書き込みます。戻り値は無視してください。 Display textクラスを宣言し、それを使用して変数を初期化します。 コンストラクターが呼び出されたというメッセージを書き込みます。戻り値は無視してください。 ParentClass









ChildClass






このコードを実行すると、次のように画面にテキストが表示されます。

メソッドのコンソール出力Main.print()
ParentClass.a
ParentClass.b
ParentClass.constructor
ChildClass.c
ChildClass.d
ChildClass.constructor

したがって、コンストラクターが呼び出される前に、クラスの変数が初期化されていることを常に個人的に確認できます。基本クラスは、継承されたクラスの初期化前に完全に初期化されます。