1. 概要

このチュートリアルでは、Javaがコンストラクターをどのように処理するかを学習し、Java言語仕様からそれらに関連するいくつかのルールを確認します。

2. コンストラクター宣言

Javaでは、すべてのクラスにコンストラクターが必要です。 その構造はメソッドに似ていますが、目的が異なります。

コンストラクターの仕様を見てみましょう。

<Constructor Modifiers> <Constructor Declarator> [Throws Clause] <Constructor Body>

それぞれの作品を別々に見てみましょう。

2.1. コンストラクター修飾子

コンストラクター宣言はアクセス修飾子で始まります。他のアクセス修飾子に基づいて、 public private protected 、またはパッケージアクセスにすることができます。

コンパイルエラーを防ぐために、コンストラクター宣言には、複数の private protected 、またはpublicアクセス修飾子を含めることはできません。

メソッドとは異なり、コンストラクターを abstract static final 、native、またはsynchronizedにすることはできません。

  • コンストラクターfinalはクラスメンバーではなく、継承しないため、宣言する必要はありません。
  • コンストラクターを実装する必要があるため、抽象化は不要です。
  • 各コンストラクターはオブジェクトで呼び出されるため、静的コンストラクターは必要ありません。
  • 構築中のオブジェクトは、構築中にオブジェクトをロックするため、同期しないでください。これは通常、すべてのコンストラクターが作業を完了するまで他のスレッドで使用できません。
  • Javaにはnative コンストラクターはありません。これは、オブジェクトの作成中にスーパークラスコンストラクターが常に呼び出されるようにすることを目的とした言語設計上の決定だからです。

2.2. コンストラクター宣言子

コンストラクター宣言子の構文を調べてみましょう。

Constrcutor Name (Parameter List)

宣言子のコンストラクター名とコンストラクター宣言を含むクラスの名前が一致している必要があります。一致していないと、コンパイル時エラーが発生します。

2.3. 節を投げる

メソッドとコンストラクターのthrows句の構造と動作はどちらも同じです。

2.4. コンストラクター本体

コンストラクター本体の構文は次のとおりです。

Constructor Body: { [Explicit Constructor Invocation] [Block Statements] }

コンストラクター本体の最初のコマンドと同じクラスまたは直接スーパークラスの別のコンストラクターを明示的に呼び出すことができます。 同じコンストラクターを直接または間接的に呼び出すことはできません。

3. 明示的なコンストラクターの呼び出し

コンストラクターの呼び出しは、次の2つのタイプに分けることができます。

  • 代替コンストラクターの呼び出しは、キーワードthisで始まります。 同じクラスの代替コンストラクターを呼び出すために使用されます。
  • スーパークラスコンストラクターの呼び出しは、キーワードsuper。で始まります。

thisおよびsuperキーワードを使用して別のコンストラクターを呼び出す方法の例を見てみましょう。

class Person {
    String name;

    public Person() {
        this("Arash");   //ExplicitConstructorInvocation
    }

    public Person(String name){
        this.name = name;
    }
}

ここで、 Employee の最初のコンストラクターは、そのスーパークラス Person のコンストラクターを呼び出し、IDを渡します。

class Person {
    int id;
    public Person(int id) {
        this.id = id;
    }
}

class Employee extends Person {
    String name;
    public Employee(int id) {
        super(id);
    }
    public Employee(int id, String name) {
        super(id);
        this.name = name;
    }
}

4. コンストラクター呼び出しのルール

4.1. thisまたはsuperはコンストラクターの最初のステートメントである必要があります

コンストラクターを呼び出すときは常に、基本クラスのコンストラクターを呼び出す必要があります。 さらに、クラス内で別のコンストラクターを呼び出すことができます。 Javaは、コンストラクターの最初の呼び出しをthisまたはsuperに対して行うことにより、このルールを適用します。

例を見てみましょう:

class Person {
    Person() {
        //
    }
}
class Employee extends Person {
    Employee() {
        // 
    }
}

コンストラクターのコンパイルの例を次に示します。

.class Employee
.super Person
; A constructor taking no arguments
.method <init>()V
aload_0
invokespecial Person/<init>()V
return
.end method

コンストラクターのコンパイルは、生成されたメソッドに名前が付いていることを除いて、他のメソッドのコンパイルと似ています。 検証するための要件の1つメソッドでは、スーパークラスコンストラクター(または現在のクラスの他のコンストラクター)の呼び出しは、メソッドの最初のステップである必要があります。

上で見ることができるように、クラスはそのスーパークラスコンストラクターを呼び出す必要があります。 java.lang.Object。

クラスがスーパークラスコンストラクターを呼び出さなければならない場合、適切な初期化なしでクラスが使用されないようにします。 一部のメソッドはクラスが初期化されるまで機能しないため、JVMのセキュリティはこれに依存します。

4.2. コンストラクターでthissuperの両方を使用しないでください

コンストラクター本体でthissuperを一緒に使用できると想像してみてください。

例を通して何が起こるか見てみましょう:

class Person {
    String name;
    public Person() {
        this("Arash");
    }

    public Person(String name) {
        this.name = name;
    }
}

class Employee extends Person {
    int id;
    public Employee() {
        super();
    }

    public Employee(String name) {
        super(name);
    }

    public Employee(int id) {
        this();
        super("John"); // syntax error
        this.id = id;
    }

    public static void main(String[] args) {
        new Employee(100);
    }
}

コンパイル時エラーが表示されるため、上記のコードを実行できません。 もちろん、Javaコンパイラには論理的な説明があります。

コンストラクターの呼び出しシーケンスを見てみましょう。

初期化が不明確なため、Javaコンパイラはこのプログラムのコンパイルを許可しません。

4.3. 再帰的なコンストラクターの呼び出し

コンストラクターがそれ自体を呼び出すと、コンパイラーはエラーをスローします。 たとえば、次のJavaコードでは、コンストラクター内で同じコンストラクターを呼び出そうとしているため、コンパイラーはエラーをスローします。

public class RecursiveConstructorInvocation {
    public RecursiveConstructorInvocation() {
        this();
    }
}

Javaコンパイラの制限にもかかわらず、コードを少し変更することでプログラムをコンパイルできますが、次のようにスタックオーバーフローが発生します。

public class RecursiveConstructorInvocation {
    public RecursiveConstructorInvocation() {
        RecursiveConstructorInvocation rci = new RecursiveConstructorInvocation();
    }

    public static void main(String[] args) {
        new RecursiveConstructorInvocation();
    }
}

コンストラクターを呼び出すことによって初期化されるRecursiveConstructorInvocationオブジェクトを作成しました。 次に、コンストラクターは別の RecursiveConstructorInvocation オブジェクトを作成します。このオブジェクトは、スタックがオーバーフローするまでコンストラクターを再度呼び出すことによって初期化されます。

それでは、出力を見てみましょう。

Exception in thread "main" java.lang.StackOverflowError
	at org.example.RecursiveConstructorInvocation.<init>(RecursiveConstructorInvocation.java:29)
	at org.example.RecursiveConstructorInvocation.<init>(RecursiveConstructorInvocation.java:29)
	at org.example.RecursiveConstructorInvocation.<init>(RecursiveConstructorInvocation.java:29)
//...

5. 結論

このチュートリアルでは、Javaでのコンストラクターの仕様について説明し、クラスおよびスーパークラスでのコンストラクターの呼び出しを理解するためのいくつかのルールを確認しました。

いつものように、コードサンプルはGitHubにあります。