PHPとJavaのコンストラクタを比べてみた

PHP と Java のコンストラクタを比較してみたいと思います。

# 要約
- Java
  - 「暗黙的コンストラクタ呼び出し」が発生する場合がある
- PHP
  - 「暗黙的コンストラクタ呼び出し」は発生しない
  - サブクラスでコンストラクタが **定義されている** 場合
     - **「サブクラスのコンストラクタ」のみ** が呼び出される
      - 「スーパークラスのコンストラクタ」を呼び出す場合は `parent::__construct()` を呼び出す
  - サブクラスでコンストラクタが **定義されていない** 場合
     - **「スーパークラスのコンストラクタ」** が呼び出される

# 動作確認環境
- Java
  - OpenJDK 11.0.1
- PHP
  - 7.3.5
- Ruby
  - 2.6.3p62

# はじめに
Java 出身の人向けに、以下のように説明している記事を見かけます。

> コンストラクタを定義するとき、Java ではクラス名と同名のメソッドとして定義する。
> 一方、PHP では `__construct()` で定義する。
> 以上。

```java
class Sample
{
  public Sample() {
    System.out.println("Constructor of Sample Class");
  }
}
```

```php
class Sample
{
  public function __construct() {
    echo "Constructor of Sample Class\n");
  }
}
```

でも、PHP と Java の違いって、本当にそれだけですかね？[^1]

[^1]: PHP 4 ではじめてオブジェクト指向を取り入れたときは、PHP でも、コンストラクタはクラス名と同名のメソッドでした。その後、PHP 5 で `__construct()` と書くようになりましたが、PHP 4 形式で書いてもコンストラクタとして認識されました。しかし、PHP 7 では、PHP 4 形式で書いた場合に「E_DEPRECATED」エラーが出力されるようになりました。そして、今後リリースされる PHP 8 からは、コンストラクタとして認識しなくなる予定です。詳しくは [RFC](https://wiki.php.net/rfc/remove_php4_constructors) を参照のこと。


# PHPに「暗黙的コンストラクタ呼び出し」はない
PHP と Java で違いが生じるのは **継承** を使ったときです。

## Java
Java では、暗黙的に「スーパークラスのコンストラクタ」が呼び出されることがあります。[^2]

[^2]: Java だけでなく C++ でも同様だと思います。

```java
public class SuperClass {
    public SuperClass() {
        System.out.println("Constructor of Super Class");
    }
}

public class SubClass extends SuperClass {
    public SubClass() {
        System.out.println("Constructor of Sub Class");
    }
}

public class Main {
    public static void main(String[] args) {
        new SubClass();
    }
}
```

上記コードを実行すると、以下のように表示されます。

```
Constructor of Super Class
Constructor of Sub Class
```

「サブクラスのコンストラクタ」には、「スーパークラスのコンストラクタ」を明示的に呼び出す処理（ `super()` ）は書かれていません。

しかし、このような場合、Java は「 **引数を持たない** スーパークラスのコンストラクタ」を **暗黙的に** 呼び出すという [言語仕様](http://www.y-adagio.com/public/standards/tr_javalang2/classes.doc.html#78435) になっています。[^3] ここで **引数を持たない** と書いてあるのが重要です。

[^3]: 引用した言語仕様は、やや古い第 2 版ですが、最新の [言語仕様](https://docs.oracle.com/javase/specs/jls/se12/html/jls-8.html#jls-8.8.7) でもほぼ同じ記載になっています。

> コンストラクタ本体が明示的コンストラクタ呼出しで開始されず，宣言しているコンストラクタが根源的クラスであるクラスObjectの一部でなければ，コンストラクタ本体は，コンパイラにより暗黙に，上位クラスのコンストラクタ呼出し"super();"で開始すると仮定される。すなわち，その直接的上位クラスに存在する実引数を取らないコンストラクタ呼出しとする。

先程のコードの場合、「サブクラスのコンストラクタ」に「スーパークラスのコンストラクタ」を明示的に呼び出す処理が書かれていなかったため、**暗黙的に** 「 **引数を持たない** スーパークラスのコンストラクタ」が呼び出されました。

### コンパイルエラー
一方、「スーパークラスのコンストラクタ」に 「 **引数を持つ** コンストラクタ」のみが定義されている場合、「 **引数を持たない** コンストラクタ」を呼び出すことができないため、コンパイル時にエラーになります。

```java
public class SuperClass {
    // 引数を持つコンストラクタのみが定義されている
    public SuperClass(String name) {
        System.out.println("Constructor of Super Class");
    }
}

public class SubClass extends SuperClass {
    public SubClass() {
        System.out.println("Constructor of Sub Class");
    }
}

public class Main {
    public static void main(String[] args) {
        new SubClass();
    }
}
```

```
Error:(4, 23) java: クラス com.example.SuperClassのコンストラクタ SuperClassは指定された型に適用できません。
  期待値: java.lang.String
  検出値: 引数がありません
  理由: 実引数リストと仮引数リストの長さが異なります
```

この場合は、「サブクラスのコンストラクタ」で、**明示的に** 「スーパークラスのコンストラクタ」を呼び出す必要があります。

```java
public class SuperClass {
    // 引数を持つコンストラクタのみが定義されている
    public SuperClass(String name) {
        System.out.println("Constructor of Super Class");
    }
}

public class SubClass extends SuperClass {
    public SubClass() {
        // スーパークラスのコンストラクタを明示的に呼び出す
        super("hoge");
        System.out.println("Constructor of Sub Class");
    }
}

public class Main {
    public static void main(String[] args) {
        new SubClass();
    }
}
```

### デフォルトコンストラクタ
また、サブクラスにコンストラクタが定義されていない場合も、暗黙的に「 **引数を持たない** スーパークラスのコンストラクタ」が呼び出されることがあります。

```java
public class SuperClass {
    public SuperClass() {
        System.out.println("Constructor of Super Class");
    }
}

public class SubClass extends SuperClass {
    // サブクラスのコンストラクタが定義されていない
}

public class Main {
    public static void main(String[] args) {
        new SubClass();
    }
}
```

上記コードを実行すると、以下のように表示されます。

```
Constructor of Super Class
```

サブクラスにコンストラクタが定義されていない場合、Java は自動的に **デフォルトコンストラクタ** を作成します。
そして、デフォルトコンストラクタは「 **引数を持たない** スーパークラスのコンストラクタ」を呼び出す [言語仕様](http://www.y-adagio.com/public/standards/tr_javalang2/classes.doc.html#16823) になっています。

> クラスがコンストラクタ宣言を含んでいなければ，実引数を取らないデフォルトコンストラクタ(default constructor)が自動的に提供される。

> - 宣言しているクラスが，ルートクラスであるクラスObjectならば，デフォルトコンストラクタは，空の本体をもつ。
> - そうでなければ，デフォルトコンストラクタは，実引数を取らず，実引数をもたない上位クラスのコンストラクタを単に呼び出す。

> コンパイラがデフォルトコンストラクタを提供しているが，上位クラスが実引数をもたないコンストラクタをもっていなければ，コンパイル時エラーが発生する。

### 継承階層
なお、継承階層が深いコンストラクタから順番に呼び出されます。

```java
public class AncestorClass {
    public AncestorClass() {
        System.out.println("Constructor of Ancestor Class");
    }
}

public class SuperClass extends AncestorClass {
    public SuperClass() {
        System.out.println("Constructor of Super Class");
    }
}

public class SubClass extends SuperClass {
    public SubClass() {
        System.out.println("Constructor of Sub Class");
    }
}

public class Main {
    public static void main(String[] args) {
        new SubClass();
    }
}
```

上記コードを実行すると、以下のように継承階層が深い順に実行されます。

- `AncestorClass` のコンストラクタ
- `SuperClass` のコンストラクタ
- `SubClass` のコンストラクタ

```
Constructor of Ancestor Class
Constructor of Super Class
Constructor of Sub Class
```

## PHP
一方、PHP では暗黙的に「スーパークラスのコンストラクタ」が呼び出されることはありません。
以下は、[公式マニュアル](https://www.php.net/manual/ja/language.oop5.decon.php) からの引用です。

>  注意: 子クラスがコンストラクタを有している場合、親クラスのコンストラクタが 暗黙の内にコールされることはありません。 親クラスのコンストラクタを実行するには、子クラスのコンストラクタの 中で `parent::__construct()` をコールすることが 必要です。 子クラスでコンストラクタを定義していない場合は、親クラスのコンストラクタを継承します (ただし、private 宣言されている場合は除く)。 これは、通常のクラスメソッドと同様です。

PHP では、コンストラクタの継承は、通常のメソッドのオーバーライドと同様に考えることができます。
つまり、サブクラスでコンストラクタを定義した場合は、スーパークラスのコンストラクタをオーバーライドしたことになるのです。

したがって、PHP では、以下のように単純なルールにすることができます。

**ルール**

- サブクラスでコンストラクタが **定義されている** 場合
  - **「サブクラスのコンストラクタ」のみ** が呼び出される
    - 「スーパークラスのコンストラクタ」を呼び出す場合は `parent::__construct()` を呼び出す
- サブクラスでコンストラクタが **定義されていない** 場合
  - **「スーパークラスのコンストラクタ」** が呼び出される

### サブクラスでコンストラクタが定義されている場合
サブクラスでコンストラクタが **定義されている** 場合は、**「サブクラスのコンストラクタ」のみ** が呼び出されます。
**暗黙的に「スーパークラスのコンストラクタ」が呼び出されることはありません。**

```php
class SuperClass {
  public function __construct() {
    echo "Constructor of Super Class\n";
  }
}

class SubClass extends SuperClass {
  public function __construct() {
    echo "Constructor of Sub Class\n";
  }
}

new SubClass();
```

```
Constructor of Sub Class
```

また、通常のメソッドのオーバーライドと異なり、コンストラクタの場合はシグネチャが異なっていても例外的にオーバーライドと見なされます（[公式マニュアル](https://www.php.net/manual/ja/language.oop5.basic.php)）。

> メソッドをオーバーライドするときには、パラメータのシグネチャも同じでなければなりません。 もし違っていれば、PHP は E_STRICT レベルのエラーとなります。ただしコンストラクタは例外で、 異なるパラメータでオーバーライドすることができます。

要するに、 **サブクラスでコンストラクタが定義されている場合は、常に「サブクラスのコンストラクタ」のみが呼び出される** ということですね。

```php
class SuperClass {
  public function __construct() {
    echo "Constructor of Super Class\n";
  }
}

class SubClass2 extends SuperClass {
  // スーパークラスのコンストラクタにはない引数がある
  public function __construct(String $name) {
    echo "Constructor of Sub Class\n";
  }
}

new SubClass2('hoge');
```

```
Constructor of Sub Class
```

### サブクラスでコンストラクタが定義されていない場合
サブクラスでコンストラクタが **定義されていない** 場合は、**「スーパークラスのコンストラクタ」** が呼び出されます。

```php
class SuperClass {
  public function __construct() {
    echo "Constructor of Super Class\n";
  }
}

class SubClass3 extends SuperClass {}

new SubClass3();
```

```
Constructor of Super Class
```

### スーパークラスのコンストラクタを呼び出す
「サブクラスのコンストラクタ」から「スーパークラスのコンストラクタ」を呼び出す場合は、 `parent::__construct()` を呼び出します。

`parent::__construct()` は Java の `super()` に相当するものですが、いろいろと制約がある `super()` に比べると、はるかに制約のないものです。

```php
class SuperClass {
  public function __construct() {
    echo "Constructor of Super Class\n";
  }
}

class SubClass4 extends SuperClass {
	public function __construct() {
    // 先頭行でなくてもいい
    echo "Constructor of Sub Class Called\n";
    // 何度呼び出してもいい
		parent::__construct();
		parent::__construct();
	}
}

new SubClass4();
```

`parent::__construct()` を 2 回呼び出したので、 `"Constructor of Super Class"` が 2 行表示されていることに注意してください。

```
Constructor of Sub Class Called
Constructor of Super Class
Constructor of Super Class
```

なお、PHP では **暗黙的に** 「スーパークラスのコンストラクタ」を呼び出さないため、 **自動的に** 継承階層が深いコンストラクタから順番に呼び出すこともありません。
そうしたい場合は、それぞれのクラスで明示的に `parent::construct()` を呼び出す必要があります。

# おまけ: Ruby
「そんな変な仕様になってるのは PHP のオブジェクト指向が貧弱だからだろ？」とか言われると心外なので、生まれながらのオブジェクト指向言語である Ruby でも試してみました。

詳しくは書きませんが、PHP と同様の動作をしていることが分かると思います。

```ruby
# スーパークラス
class SuperClass
	def initialize
		puts 'initialize of Super Class'
	end
end

# サブクラス
# サブクラスのコンストラクタのみが呼び出される
class SubClass < SuperClass
	def initialize
		puts 'initialize of Sub Class'
	end
end

# サブクラス2
# サブクラスのコンストラクタのみが呼び出される
class SubClass2 < SuperClass
  # スーパークラスのコンストラクタにはない引数がある
	def initialize name
		puts 'initialize of Sub Class'
	end
end

# サブクラス3
# コンストラクタを定義していない場合
# スーパークラスのコンストラクタが呼び出される
class SubClass3 < SuperClass
end

# サブクラス4
# superメソッドを呼び出した場合
# スーパークラスの同名メソッド（initialize）が呼び出される
class SubClass4 < SuperClass
  def initialize
    super
  end
end

puts 'SubClass'
SubClass.new
puts 'SubClass2'
SubClass2.new('name')
puts 'SubClass3'
SubClass3.new
puts 'SubClass4'
SubClass4.new
```

```
SubClass
initialize of Sub Class
SubClass2
initialize of Sub Class
SubClass3
initialize of Super Class
SubClass4
initialize of Super Class
```

# 参考
## Java
- [Java言語規定 第2版](http://www.y-adagio.com/public/standards/tr_javalang2/jTOC.doc.html)
- [The Java Language Specification Java SE 12 Edition](https://docs.oracle.com/javase/specs/jls/se12/html/index.html)
- [基礎から始めるJavaのコンストラクタ 構文から上手な使い方まで](https://engineer-club.jp/java-constructor)
- [[Java]クラスの継承とコンストラクタ](https://qiita.com/katolisa/items/438a8d720ea5a60b55c4)

## PHP
- 公式マニュアル
  - [クラスの基礎](https://www.php.net/manual/ja/language.oop5.basic.php)
  - [コンストラクタとデストラクタ](https://www.php.net/manual/ja/language.oop5.decon.php)

## Ruby
- 公式リファレンスマニュアル
  - [instance method Object#initialize](https://docs.ruby-lang.org/ja/2.6.0/method/Object/i/initialize.html)
  - [メソッド呼び出し(super・ブロック付き・yield)](https://docs.ruby-lang.org/ja/latest/doc/spec=2fcall.html)

# 雑感
個人的好みだけで言うと、勝手に後ろで動かれるのは好きではないため、PHP や Ruby のほうが好みです。[^5]

ただ、 **継承** が [is-a 関係](https://ja.wikipedia.org/wiki/Is-a) であるべきことを考えると、「暗黙的コンストラクタ呼び出し」を行わない PHP や Ruby では、サブクラスが全く関係ないスーパークラスを継承できてしまうようにも思います。

そういう観点で考えると、「暗黙的コンストラクタ呼び出し」はむしろそうなるのが自然で、そうなって問題が起きるようなプログラムはそもそもオブジェクト指向的でないということになるのかなとも思います。

[^5]: 最初に触れたオブジェクト指向言語（？）が PHP だったからというのもあるかもしれません。というか、それに尽きるか。