はじめに
Pythonでクラスの継承をしこうとしたことがきっかけでした。
これまでJavaを扱っていた自分からみて、
「何だこれっ!!!!!!!」と思ったので、いくつかのプログラミング言語の
クラスの継承の記述方法と、コンストラクタの記述方法&動きを比べてみました。
今回のスタンス
Javaを軸に、書いて、比べて、眺めて、所感を述べます。
※今回「コンストラクタ」と呼んでいる部分については、プログラミング言語によって、
考え方?ポジション?使い方?が異なり、単純比較はできないと思いますが、
とりあえず並べて所感を述べます。
動作確認にはpaiza.IOを使わせていただきました。便利。
https://paiza.io/ja
サンプルの仕様
- Parentクラス(Superクラス)
- デフォルトコンストラクタを用意。「★Parentのコンストラクタ」の文字列をコンソール出力する
- 関数/メソッドを1つ用意
- 引数で渡された文字列をコンソール出力する
- Childクラス(継承をするクラス)
- Parentクラスを継承する
- デフォルトコンストラクタを用意。「☆Childのコンストラクタ」の文字列をコンソール出力する
- 関数/メソッドを1つ用意
- 引数で渡された文字列をコンソール出力する
- 「★Parentの関数/メソッド呼べてるよ」の文字列を引数に、Superクラスの関数/メソッドを呼び出す
- Mainクラス/mainスクリプト(実行元)
- paiza.IO的に、Javaの実行元(mainメソッドを含む)クラスの名前がMainである必要があってこの名前
- Childクラスをインスタンス化する
- 「☆Childの関数/メソッド呼べてるよ」の文字列を引数に、Childクラスの関数/メソッドを呼び出す
Java
個人的には一番見慣れている言語です。バージョンはJava12です。
クラスの継承記述方法
アクセス修飾子 class 継承をするクラス extends Superクラス {
}
コンストラクタ記述方法
アクセス修飾子 クラス名 ( あれば引数 ) {
}
サンプル
public class Parent {
public Parent(){
System.out.println("★Parentのコンストラクタ");
}
protected void parentMethod(String message){
System.out.println(message);
}
}
public class Child extends Parent{
public Child(){
System.out.println("☆Childのコンストラクタ");
}
public void childMethod(String message){
System.out.println(message);
parentMethod("★Parentのメソッド呼べてるよ");
}
}
public class Main {
public static void main(String[] args) {
Child child = new Child();
child.childMethod("☆Childのメソッド呼べてるよ");
}
}
実行結果(コンソール)
★Parentのコンストラクタ
☆Childのコンストラクタ
☆Childのメソッド呼べてるよ
★Parentのメソッド呼べてるよ
所感
- 個人的に一番しっくりくるクラス継承とコンストラクタの記述方法
- 親クラスのデフォルトコンストラクタも暗黙的に呼ばれている
- 引数違いのコンストラクタを、複数実装可能
Python
つづいて、今回の記事を書くきっかけ。バージョンはPython3.6.8です。
クラスの継承記述方法
class 継承をするクラス ( Superクラス ):
コンストラクタ記述方法
def __init__(self , あれば引数 ):
インスタンスselfに初期値設定処理
def __new__(cls ,あれば引数 ):
return super().__new__(cls ,あれば引数 )
※以降のサンプル、実行結果(コンソール)、所感は__init__関数についてです。
サンプル
class Parent(object):
def __init__(self):
print('★Parentのコンストラクタ')
def parent_function(self, message):
print(message)
from parent import Parent
class Child(Parent):
def __init__(self):
print('☆Childのコンストラクタ')
def child_function(self, message):
print(message)
Parent.parent_function(self, '★Parentの関数呼べてるよ')
from child import Child
def main():
ch = Child()
ch.child_function('☆Childの関数呼べてるよ')
if __name__ == '__main__':
main()
実行結果(コンソール)
☆Childのコンストラクタ
☆Childの関数呼べてるよ
★Parentの関数呼べてるよ
所感
- 「え、クラスの継承で括弧つかうの?引数と間違っちゃわない?」と動揺
- Pythonはすべてがオブジェクトの扱いだから?
- Parentクラスの__init__関数は、Childクラスの__init__関数で上書きされちゃう
- Childクラスの__init__関数を消して実行すると、「★Parentのコンストラクタ」が出力された
- Parentクラスの__init__関数も実行したければ、下記のように明示的にする必要がある模様
from parent import Parent
class Child(Parent):
def __init__(self):
Parent.__init__(self) # ここを追加しました!!!!!!
print('☆Childのコンストラクタ')
def child_function(self, message):
print(message)
Parent.parent_function(self, '★Parentの関数呼べてるよ')
- __init__関数は1クラスに1個しか用意できない
- Javaライクにはできない
- 複数コンストラクタが欲しい場合、デフォルト引数を使う方法が良い模様
__new__関数について※(2019/8/16)追記
当初、__init()__関数についてのみ記載していましたが、
コメントをいただいたので、__new__関数についても調べました。
※公式だと「メソッド」表記なのですが、、、今回の記事の表記に合わせて「関数」で統一しています
- __new__関数は、インスタンス生成時に動くので、__init__関数よりも先に動く。
- __init__関数は、インスタンスが生成された後に動く
以下Python3.7.4ドキュメントからです。
__new__関数を実装する際、return super().__new__(cls[, ...])
が必要なんですね。
https://docs.python.org/ja/3/reference/datamodel.html#object.__new__
典型的な実装では、クラスの新たなインスタンスを生成するときには super().__new__(cls[, ...]) に適切な引数を指定してスーパクラスの __new__() メソッドを呼び出し、新たに生成されたインスタンスに必要な変更を加えてから返します。
色々ちゃんと書いてありました(´・ω・`)
https://docs.python.org/ja/3/reference/datamodel.html#object.__init__
インスタンスが (__new__() によって) 生成された後、それが呼び出し元に返される前に呼び出されます。引数はクラスのコンストラクタ式に渡したものです。基底クラスとその派生クラスがともに __init__() メソッドを持つ場合、派生クラスの __init__() メソッドは基底クラスの __init__() メソッドを明示的に呼び出して、インスタンスの基底クラス部分が適切に初期化されること保証しなければなりません。例えば、 super().__init__([args...]) 。
Javaのコンストラクタと並べるのであれば、__new__関数の方が良かったような。。
__init__関数で実装したサンプルを、そのまま__new__関数に差し替えたところ、
実行結果(コンソール)は下記のようになりました。
☆Childのコンストラクタ
★Parentのコンストラクタ
☆Childの関数呼べてるよ
★Parentの関数呼べてるよ
Ruby
PythonときたらRuby、という謎論理により。バージョンはRuby2.6.3です。
クラスの継承記述方法
class 継承をするクラス < Superクラス
end
コンストラクタ記述方法
def initialize( あれば引数 )
end
サンプル
class Parent
def initialize()
puts "★Parentのコンストラクタ"
end
def parent_function(message)
puts message
end
end
require("./parent.rb")
class Child < Parent
def initialize()
puts "☆Childのコンストラクタ"
end
def child_function(message)
puts message
parent_function("★Parentの関数呼べてるよ")
end
end
require("./child.rb")
child = Child.new
child.child_function("☆Childの関数呼べてるよ")
実行結果(コンソール)
☆Childのコンストラクタ
☆Childの関数呼べてるよ
★Parentの関数呼べてるよ
所感
- クラスの継承している感が伝わる記述のように思える。コンストラクタが決められた名前の関数であるのはPythonっぽい
- こちらもPython同様、Parentクラスのinitialize関数は、Childクラスのinitialize関数で上書きされちゃう
- Childクラスのinitialize関数を消して実行すると、「★Parentのコンストラクタ」が出力された
-
super
で、Parentのinitialize関数も実行
require("./parent.rb")
class Child < Parent
def initialize()
super() # ここを追加しました!!!!!!
puts "☆Childのコンストラクタ"
end
def child_function(message)
puts message
parent_function("★Parentの関数呼べてるよ")
end
end
- initialize関数は1クラスに1個しか用意できない
- こちらもPythonと似ている
- 同じく複数コンストラクタが欲しい場合、デフォルト引数を使うと良い模様
PowerShell
会社の新人研修で、PowerShellスクリプトを組むお題があったので入れてみました。
PowerShellにクラスの継承はなかった(キリッ)、でオチにしようと思っていたら…ありました。
こちらはpaiza.IOになかったので、自分のPCで動作確認しました。
- OS:Windows10
- PowerShellバージョン:5.1.18362.145
クラスの継承記述方法
class 継承をするクラス : Superクラス {
}
コンストラクタ記述方法
クラス名 ( あれば引数 ) {
}
サンプル
class Parent {
Parent() {
write-host("★Parentのコンストラクタ")
}
parent_function([String] $message) {
write-host($message)
}
}
using module ".\parent.psm1"
class Child : Parent {
Child(){
write-host("☆Childのコンストラクタ")
}
child_function([String] $message) {
write-host($message)
([Parent]$this).parent_function("★Parentの関数呼べてるよ")
}
}
. .\child.ps1
$child = [Child]::new()
$child.child_function("☆Childの関数呼べてるよ")
実行結果(コンソール)
★Parentのコンストラクタ
☆Childのコンストラクタ
☆Childの関数呼べてるよ
★Parentの関数呼べてるよ
所感
- クラスの継承については「コロンかぁ~」。コンストラクタはJavaっぽい
- Javaと同じく、Parentクラスのコンストラクタも暗黙的に呼ばれている
- こちらもJava同様、引数違いのコンストラクタを、複数実装可能
- 「なーんだ、Javaっぽいんじゃん」で終わろうとしたがそうはいかず
-
ファイルを跨ぐクラス継承が、一筋縄でできない
- Superクラスのファイルは、モジュールファイル(拡張子は.psm1)にして、
using module
で呼び出す - 拡張子まで変えなくちゃいけないって…なんで???
- Superクラスのファイルは、モジュールファイル(拡張子は.psm1)にして、
-
Superクラスの関数は、「継承をするクラスの
$this
を、Superクラスでキャスト」しないと呼べない- thisをキャスト…なんで???
-
ファイルを跨ぐクラス継承が、一筋縄でできない
さいごに
Java以外の、Python、Ruby、PowerShellは「書いたことがあるけどクラスを使ったことがない」のチョイスでした。
他、触ったことのないプログラミング言語を2~3こ試してみようと思ったのですが、
すみません、PowerShellで力尽きました。
paiza.IOはたくさん使えるプログラミング言語があるので、
元気が出たらその他でやってみた場合も追記したいと思います。
それにしてもpaiza.IO、本当に便利ですね。
書き方を比べてみて、
それぞれのプログラミング言語の特色、自分の理解のゆるさが分かって楽しかったです!
最後までお付き合いくださりありがとうございました。