LoginSignup
2
5

More than 3 years have passed since last update.

クラス継承とコンストラクタの記述の比較

Last updated at Posted at 2019-08-15

はじめに

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クラス {
}

コンストラクタ記述方法

アクセス修飾子 クラス名 ( あれば引数 ) {
}

サンプル

Parent.java
public class Parent {

    public Parent(){
        System.out.println("★Parentのコンストラクタ");
    }

    protected void parentMethod(String message){        
        System.out.println(message);
    }
}
Child.java
public class Child extends Parent{

    public Child(){
        System.out.println("☆Childのコンストラクタ");
    }

    public void childMethod(String message){       
        System.out.println(message);
        parentMethod("★Parentのメソッド呼べてるよ");
    }
}
Main.java
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__関数についてです。

サンプル

parent.py
class Parent(object):

    def __init__(self):
        print('★Parentのコンストラクタ')

    def parent_function(self, message):
        print(message)
child.py
from parent import Parent

class Child(Parent):

    def __init__(self):
        print('☆Childのコンストラクタ')

    def child_function(self, message):
        print(message)
        Parent.parent_function(self, '★Parentの関数呼べてるよ')
main.py
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__関数も実行したければ、下記のように明示的にする必要がある模様
child.py
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の関数呼べてるよ')

__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

サンプル

parent.rb
class Parent

    def initialize()
        puts "★Parentのコンストラクタ"
    end

    def parent_function(message)
        puts message
    end   
end
child.rb
require("./parent.rb")

class Child < Parent

    def initialize()
        puts "☆Childのコンストラクタ"
    end

    def child_function(message)
        puts message
        parent_function("★Parentの関数呼べてるよ")
    end
end
main.rb
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関数も実行
child.rb
require("./parent.rb")

class Child < Parent

    def initialize()
        super()  # ここを追加しました!!!!!!
        puts "☆Childのコンストラクタ"
    end

    def child_function(message)
        puts message
        parent_function("★Parentの関数呼べてるよ")
    end
end

PowerShell

会社の新人研修で、PowerShellスクリプトを組むお題があったので入れてみました。
PowerShellにクラスの継承はなかった(キリッ)、でオチにしようと思っていたら…ありました。
こちらはpaiza.IOになかったので、自分のPCで動作確認しました。
- OS:Windows10
- PowerShellバージョン:5.1.18362.145

クラスの継承記述方法

class 継承をするクラス : Superクラス {
}

コンストラクタ記述方法

クラス名 ( あれば引数 ) {
}

サンプル

parent.psm1
class Parent {

    Parent() {
        write-host("★Parentのコンストラクタ")
    }

    parent_function([String] $message) {

        write-host($message)
    }
}
child.ps1
using module ".\parent.psm1"

class Child : Parent {

    Child(){
         write-host("☆Childのコンストラクタ")       
    }

    child_function([String] $message) {

        write-host($message)
        ([Parent]$this).parent_function("★Parentの関数呼べてるよ")
    }
}
main.ps1
. .\child.ps1

$child = [Child]::new()
$child.child_function("☆Childの関数呼べてるよ")

実行結果(コンソール)

★Parentのコンストラクタ
☆Childのコンストラクタ
☆Childの関数呼べてるよ
★Parentの関数呼べてるよ

所感

  • クラスの継承については「コロンかぁ~」。コンストラクタはJavaっぽい
  • Javaと同じく、Parentクラスのコンストラクタも暗黙的に呼ばれている
  • こちらもJava同様、引数違いのコンストラクタを、複数実装可能
  • 「なーんだ、Javaっぽいんじゃん」で終わろうとしたがそうはいかず
    • ファイルを跨ぐクラス継承が、一筋縄でできない
      • Superクラスのファイルは、モジュールファイル(拡張子は.psm1)にして、using module で呼び出す
      • 拡張子まで変えなくちゃいけないって…なんで???
    • Superクラスの関数は、「継承をするクラスの $this を、Superクラスでキャスト」しないと呼べない
      • thisをキャスト…なんで???

さいごに

Java以外の、Python、Ruby、PowerShellは「書いたことがあるけどクラスを使ったことがない」のチョイスでした。
他、触ったことのないプログラミング言語を2~3こ試してみようと思ったのですが、
すみません、PowerShellで力尽きました。

paiza.IOはたくさん使えるプログラミング言語があるので、
元気が出たらその他でやってみた場合も追記したいと思います。
それにしてもpaiza.IO、本当に便利ですね。

書き方を比べてみて、
それぞれのプログラミング言語の特色、自分の理解のゆるさが分かって楽しかったです!
最後までお付き合いくださりありがとうございました。

2
5
8

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
5