3
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

各言語で、継承の挙動はかなり違うという話

Last updated at Posted at 2020-01-26

はじめに

この記事は、私が色んな言語でひたすら似たようにクラス継承を書いてみて、実際にどんな値が出力されるのかを調査した結果をまとめたものです。時には既知の言語でも「こんな文法あったんだ」と思いながら、時にはHello Worldから頑張りました。

まとめるのが大変だった割に誰得?という内容ですが、同じことが気になった人のために置いておきます。
いやでも新しい発見があるかもしれないのでとりあえず読んでみてください。
意外と面白い結果になったかもしれません。

調べた言語

  • 静的型付け

    • Java (Corretto 1.8.0_232)
    • C# (3.4.0)
    • C++ (11.0.0)
    • Scala (2.13.1)
    • Kotlin (1.3.61)
    • Swift (5.1.3)
  • 動的型付け

    • Python (3.7.1)
    • Ruby (2.6.5)
    • PHP (7.1.32)
    • JavaScript (node v12.14.1)
  • オプショナルな静的型付け

    • TypeScript (3.7.2)
    • Dart (2.7.0)

調査で使うコード

ある親クラスを子クラスが継承します。
その親クラスと子クラスには、同じ名前のインスタンス変数があります。(クラス変数ではありません)
親クラスにはメソッドがあり、そのメソッドを使うとインスタンス変数をコンソールに出力できます。

実行時には、子クラスのインスタンスで、継承した親のメソッドを呼びます。
さぁ親と子どっちのインスタンス変数が出力されるでしょうか……というストーリーです。

多分読める人が多いであろうJS(ES6以降)のコードで書くと、こういうコードです。

class SuperClass {
  constructor() {
    // ※JSではインスタンス変数は実行時に定義されるので、コンストラクタに書く
    this.instanceVariable = "SuperClass" // 同じ名前で違う値
  }

  superClassMethod() {
    console.log(this.instanceVariable)
  }
}

class SubClass extends SuperClass {
  constructor() {
    super()
    this.instanceVariable = "SubClass" // 同じ名前で違う値
  }
}

const sub = new SubClass() // 子クラスをインタンス化
sub.superClassMethod() // 継承した親クラスのメソッドを呼ぶ

自分が普段使っている言語でどういう値が出力されるかぜひ想像してみてください。
(良し悪しはともかくとして)たまにある書き方なので、自分の得意な言語のものは知っているかもしれません。
でも他の言語でどう動くのかを知っていたら、いつか別の会社や別の案件に入った時、もしくは興味本位で他言語に入門した時に役に立つかもしれません。

今回は一応、インスタンス変数のみならず、メソッド(クラスメソッドではないインスタンスのメソッド)でreturn "SuperClass"をした時にどうなるのかも見たりしています。
この場合は、親クラスと子クラスに同じ名前のメソッドがあり、親クラスのメソッドでそれを呼ぶ……というシナリオです。

どんなコードを書いて調べたかは、

コードを見る(hoge言語)
class Hoge {}

こんな風に詳細折りたたみ要素が置いてあるので、クリックして開いてもらえると中身のコードを読むことができます。
何をしているか見やすくするために原則としてコピペを採用したかなりWETなコードですが、それはわざとなのでマサカリを投げないでください。

今回のポイント

ご存知の通り、プログラミング言語によって存在する文法は異なりますし、評価の仕方も違います。
今回の結果が変わるポイントは、下記であると思います。

  • アクセス修飾子が存在するか。また、どんな種類のものがあるか
  • 親クラスと子クラスで同名のインスタンス変数をそもそも定義可能か
  • インスタンス変数のオーバーライド、メソッドのオーバーライドがそれぞれ存在するか。
  • 継承そのものを、各言語がどう処理しているか

結果発表

Java

エンタープライズシステムの覇者、Java。
Javaにはアクセス修飾子として、privateprotectedがあります。
privateは、現在のクラスからしか見ることはできません。protectedは、現在のクラスと子クラスからアクセスできます。

Javaではインスタンス変数のオーバーライドは存在しません。
なので、メソッドでオーバーライドをした時にどうなるのかも見てみます。

条件 結果
インスタンス変数がprivate "SuperClass"
インスタンス変数がprotected "SuperClass"
インスタンス変数の代わりにprivateなメソッド "SuperClass"
インスタンス変数の代わりにprotectedなメソッド(override) "SubClass"

オーバーライドは文字通り「上書き」してしまいます。

今回は関係ありませんでしたが、Javaは、子クラス側のメソッドで親クラスのインスタンス変数にsuper.instanceVariableのようにしてアクセスすることができます。オーバーライドしないゆえにできる芸当ですね。

コードを見る(Java)
public class JavaSample {
    public static void main(String[] args) {
        System.out.println("---- SubClass ----");
        SubClass sub = new SubClass();
        sub.superClassMethod();

        System.out.println("---- SubClassProtected ----");
        SubClassProtected subp = new SubClassProtected();
        subp.superClassMethod();

        System.out.println("---- SubClassGetter ----");
        SubClassGetter subg = new SubClassGetter();
        subg.superClassMethod();

        System.out.println("---- SubClassGetterProtected ----");
        SubClassGetterProtected subgp = new SubClassGetterProtected();
        subgp.superClassMethod();
    }
}

class SuperClass {
    // privateのインスタンス変数はサブクラスから参照できない
    private String instanceVariable = "SuperClass";

    public void superClassMethod() {
        System.out.println(instanceVariable);
    }
}

class SubClass extends SuperClass {
    private String instanceVariable = "SubClass";
}

// -------------------------------------------

class SuperClassProtected {
    protected String instanceVariable = "SuperClass";

    public void superClassMethod() {
        System.out.println(instanceVariable);
    }
}

class SubClassProtected extends SuperClassProtected {
    protected String instanceVariable = "SubClass";

    // public void subClassMethod() {
        // System.out.println(instanceVariable); と書くと、
        // System.out.println(this.instanceVariable); と同じ。"SubClass"が出る。
        // superをつけると、"SuperClass"と表示させることもできる
        // System.out.println(super.instanceVariable);
    // }
}

// -------------------------------------------

class SuperClassGetter {
    private String instanceVariable() {
        return "SuperClass";
    }

    public void superClassMethod() {
        System.out.println(instanceVariable());
    }
}

class SubClassGetter extends SuperClassGetter {
    private String instanceVariable() {
        return "SubClass";
    }
}

// -------------------------------------------

class SuperClassGetterProtected {
    protected String instanceVariable() {
        return "SuperClass";
    }

    public void superClassMethod() {
        System.out.println(instanceVariable());
    }
}

class SubClassGetterProtected extends SuperClassGetterProtected {
    protected String instanceVariable() {
        return "SubClass";
    }
}

C#

Javaの次はやっぱりC#。歴史的経緯からかなりJavaに近い文法を持ちます。その結果もまたJavaや、後述するC++と似ていますが、Javaには無いものもあります。
ちなみにC#にはプロパティがあるので、メソッドの代わりにプロパティで書きました。

C#にはvirtualoverrideがあります。virtualは、「このメソッドはオーバーライドされる可能性があるぞ」と教える修飾子で、overrideは、ここでメソッドはオーバーライドしているぞと教える修飾子です。

そこまではいいのですが、C#には更に、newという変わった修飾子があります。このnewは、インスタンス生成のときのnew Human()のnewとは別モノです。overrideの代わりにnewをつけることで、子クラスからの呼び出しであっても、親クラスのメソッドを親クラスの文脈のままで評価させることができます。なぜかというと、親クラスのメソッドをオーバーライド(上書き)しているわけではなく、ただ隠しているだけだからです。ややこしいですね。

条件 結果
インスタンス変数がprivate "SuperClass"
インスタンス変数がprotected "SuperClass"
プロパティがprivate "SuperClass"
プロパティがprotected(override) "SubClass"
プロパティがprotected(new) "SuperClass"
コードを見る(C#)
using System;

public class CSharpSample
{
    public static void Main(string[] args)
    {
        Console.WriteLine("---- SubClass ----");
        var sub = new SubClass();
        sub.SuperClassMethod();

        Console.WriteLine("---- SubClassProtected ----");
        var subp = new SubClassProtected();
        subp.SuperClassMethod();

        Console.WriteLine("---- SubClassGetter ----");
        var subg = new SubClassGetter();
        subg.SuperClassMethod();

        Console.WriteLine("---- SubClassGetterProtectedOverride ----");
        var subgpo = new SubClassGetterProtectedOverride();
        subgpo.SuperClassMethod();

        Console.WriteLine("---- SubClassGetterProtectedNew ----");
        var subgpn = new SubClassGetterProtectedNew();
        subgpn.SuperClassMethod();
    }
}

class SuperClass
{
    private string instanceVariable = "SuperClass";

    public void SuperClassMethod()
    {
        Console.WriteLine(instanceVariable);
    }
}

class SubClass : SuperClass
{
    // warning CS0414: The field 'SubClass.instanceVariable' is assigned but its value is never used
    private string instanceVariable = "SubClass";
}

// ----------------------------

class SuperClassProtected
{
    protected string instanceVariable = "SuperClass";

    public void SuperClassMethod()
    {
        Console.WriteLine(instanceVariable);
    }
}

class SubClassProtected : SuperClassProtected
{
    // newはオーバーライドではなく、継承元のインスタンス変数隠しているよと明示している
    new protected string instanceVariable = "SubClass";
}

// ----------------------------

class SuperClassGetter
{
    private string instanceVariable
    { 
        get { 
            return "SuperClass";
        }
    }

    public void SuperClassMethod()
    {
        Console.WriteLine(instanceVariable);
    }
}

class SubClassGetter : SuperClassGetter
{
    private string instanceVariable {
        get {
            return "SubClass";
        }
    }
}

// ----------------------------

class SuperClassGetterProtected
{
    protected virtual string instanceVariable
    { 
        get { 
            return "SuperClass";
        }
    }

    public void SuperClassMethod()
    {
        Console.WriteLine(instanceVariable);
    }
}

class SubClassGetterProtectedOverride : SuperClassGetterProtected
{
    protected override string instanceVariable {
        get {
            return "SubClass";
        }
    }
}

class SubClassGetterProtectedNew : SuperClassGetterProtected
{
    protected new string instanceVariable {
        get {
            return "SubClass";
        }
    }
}

C++

C++は組み込みの現場などで使われることが多いらしいですが私は何も知りません。
C++ナニモワカラナイ……。

Javaとともに、C#の元となった言語なだけあって、C#に挙動がとても似ています。
インスタンス変数の代わりにメソッドを使った場合で、かつoverrideしないというのは、C#でいうところのnewと同じ挙動をします。

条件 結果
インスタンス変数がprivate "SuperClass"
インスタンス変数がprotected "SuperClass"
インスタンス変数の代わりにprivateなメソッド "SuperClass"
インスタンス変数の代わりにprotectedなメソッド(override) "SubClass"
インスタンス変数の代わりにprotectedなメソッド(overrideしない) "SuperClass"
コードを見る(C++)
#include <iostream>

class SuperClass {
// デフォルトだとprivate(クラス外からアクセス不可になる)
    std::string instanceVariable = "SuperClass";
public:
    void superClassMethod() {
        std::cout << instanceVariable << std::endl;
    }
};

class SubClass : public SuperClass {
    std::string instanceVariable = "SubClass";
};

// -------------------------------

class SuperClassProtected {
protected:
    std::string instanceVariable = "SuperClass";
public:
    void superClassMethod() {
        std::cout << instanceVariable << std::endl;
    }
};

class SubClassProtected : public SuperClassProtected {
protected:
    std::string instanceVariable = "SubClass";
};

// -------------------------------

class SuperClassGetter {
    std::string instanceVariable() {
        return "SuperClass";
    }
public:
    void superClassMethod() {
        std::cout << instanceVariable() << std::endl;
    }
};

class SubClassGetter : public SuperClassGetter {
    std::string instanceVariable() {
        return "SubClass";
    }
};

// -------------------------------

class SuperClassProtectedGetter {
protected:
    std::string instanceVariable() {
        return "SuperClass";
    }
public:
    void superClassMethod() {
        std::cout << instanceVariable() << std::endl;
    }
};

class SubClassProtectedGetter : public SuperClassProtectedGetter {
protected:
    std::string instanceVariable() {
        return "SubClass";
    }
};

// -------------------------------

class SuperClassProtectedGetterOverride {
protected:
    virtual std::string instanceVariable() {
        return "SuperClass";
    }
public:
    void superClassMethod() {
        std::cout << instanceVariable() << std::endl;
    }
};

class SubClassProtectedGetterOverride : public SuperClassProtectedGetterOverride {
protected:
    std::string instanceVariable() override {
        return "SubClass";
    }
};

int main()
{
    std::cout << "---- SubClass ----" << std::endl;
    SubClass sub;
    sub.superClassMethod();

    std::cout << "---- SubClassProtected ----" << std::endl;
    SubClassProtected subp;
    subp.superClassMethod();

    std::cout << "---- SubClassGetter ----" << std::endl;
    SubClassGetter subg;
    subg.superClassMethod();

    std::cout << "---- SubClassProtectedGetter ----" << std::endl;
    SubClassProtectedGetter subpg;
    subpg.superClassMethod();

    std::cout << "---- SubClassProtectedGetterOverride ----" << std::endl;
    SubClassProtectedGetterOverride subpgo;
    subpgo.superClassMethod();

    return 0;
}

Scala

関数型の風を取り込んだAltJavaです。
JavaやC#と大きく違うところは、インスタンス変数のオーバーライドができることです。新機能来ました!

条件 結果
インスタンス変数がprivate "SuperClass"
インスタンス変数がprotected(override) "SubClass"
コードを見る(Scala)
object ScalaSample {
    def main(args: Array[String]): Unit = {
        println("---- SubClass ----")
        val sub = new SubClass
        sub.superClassMethod()
    
        println("---- SubClassProtected ----")
        val subp = new SubClassProtected
        subp.superClassMethod()
    }
}

class SuperClass {
    private val instanceVariable = "SuperClass";

    def superClassMethod(): Unit = {
        println(instanceVariable);
    }
}

class SubClass extends SuperClass {
    private val instanceVariable = "SubClass";
}

// ----------------------------

class SuperClassProtected {
    protected val instanceVariable = "SuperClass";

    def superClassMethod(): Unit = {
        println(instanceVariable);
    }
}

class SubClassProtected extends SuperClassProtected {
    override protected val instanceVariable = "SubClass";
}

Kotlin

Scalaに大きな影響を受けたAltJavaです。Androidアプリの開発で多く用いられています。
オーバーライドする予定のインスタンス変数にはopenをつけなくてはならないこと以外、Scalaと同じです。
つまりopenはC++やC#のvirtualみたいなものですね。

条件 結果
インスタンス変数がprivate "SuperClass"
インスタンス変数がprotected(override) "SubClass"
コードを見る(Kotlin)
fun main(args: Array<String>) {
    println("---- SubClass ----");
    val sub = SubClass();
    sub.superClassMethod();

    println("---- SubClassOverride ----");
    val subo = SubClassOverride();
    subo.superClassMethod();
}

open class SuperClass {
    private val instanceVariable = "SuperClass";

    fun superClassMethod() {
        println(instanceVariable);
    }
}

class SubClass : SuperClass() {
    private val instanceVariable = "SubClass";
}

// -----------------------------------

open class SuperClassOverride {
    open val instanceVariable = "SuperClass";

    fun superClassMethod() {
        println(instanceVariable);
    }
}

class SubClassOverride : SuperClassOverride() {
    override val instanceVariable = "SubClass";
}

Swift

アプリと言ったらAndroidだけではありません。iOSを忘れて貰っては困ります。
Swiftのprivateはバージョンによって挙動が変わったりするようですが、今回はSwift 5で検証したので、Javaと同じく、クラス内のみ有効で、継承されません。

Swiftのletは再代入を禁止した変数宣言です。JSでいうとconstです。
そしてSwiftのvarは再代入可能な普通の変数宣言です。JSでいうletです。ややこしや。

Swiftでは、インスタンス変数(Swiftではプロパティという)を、継承元と同じものを定義することはできません。
代わりにComputed Property(C#でいうプロパティのようなもの)を使うと、オーバーライドすることができます。

privateのようなアクセス修飾子をつけないと、internalというスコープになります。internalはJavaでいうpublicみたいなものです(雑)。

条件 結果
インスタンス変数がprivate "SuperClass"
インスタンス変数がinternal 定義不可
Computed Propertyがprivate "SuperClass"
Computed Propertyがinternal(override) "SubClass"
コードを見る(Swift)
class SuperClass {
  private let instanceVariable = "SuperClass";

  func SuperClassMethod() {
    print(instanceVariable);
  }
}

class SubClass: SuperClass {
  private let instanceVariable = "SubClass";
}

// --------------------------------

class SuperClassInternal {
  let instanceVariable = "Error";

  func SuperClassMethod() {
    print(instanceVariable);
  }
}

class SubClassInternal: SuperClassInternal {
  // error: cannot override with a stored property 'instanceVariable'
  // let instanceVariable = "SubClass";
}

// --------------------------------

// Computed Propertyにしてgetterを生やす。こうしたら関数扱いなのでoverrideができるようになる。
class SuperClassGetter {
  // letだと、error: 'let' declarations cannot be computed properties
  private var instanceVariable: String { get { return "SuperClass" } }

  func SuperClassMethod() {
    print(instanceVariable);
  }
}

class SubClassGetter: SuperClassGetter {
  private var instanceVariable: String { get { return "SubClass" } };
}

// --------------------------------

// Computed Propertyにしてgetterを生やす。こうしたら関数扱いなのでoverrideができるようになる。
class SuperClassInternalGetter {
  // letだと、error: 'let' declarations cannot be computed properties
  var instanceVariable: String { get { return "SuperClass" } }

  func SuperClassMethod() {
    print(instanceVariable);
  }
}

class SubClassInternalGetter: SuperClassInternalGetter {
  override var instanceVariable: String { get { return "SubClass" } };
}

print("---- SubClass ----");
let sub = SubClass();
sub.SuperClassMethod();

print("---- SubClassInternal ----");
let subi = SubClassInternal();
subi.SuperClassMethod();

print("---- SubClassGetter ----");
let subg = SubClassGetter();
subg.SuperClassMethod();

print("---- SubClassInternalGetter ----");
let subig = SubClassInternalGetter();
subig.SuperClassMethod();

Python

ここからは動的型付け言語!
Qiitaのタグ投稿ランキングで1位を取り続けるなど、その人気を盤石なものにしたPythonです。

Pythonのクラスで特徴的なのは、メソッドの第一引数にインスタンス自身がやってくるので宣言時には第1引数にselfと書くところです。別にselfじゃなくても良いのですが、みんなselfと書きます。

あとは動的型付け言語らしく、インスタンス変数を動的に生み出すので、クラス宣言直下ではなくコンストラクタの中でインスタンス変数を定義します。ここはRubyもJavaScriptも同じですね。

Pythonにはメンバのアクセス制限がありません。修飾子privateはありません。
関数名の先頭にアンダースコアを1つつけて、アクセスしないで欲しいことを示す文化です。

しかしPythonでは、子クラスと名前が衝突した場合に備えた機能があります。アンダースコアを2つつけると、内部的なメンバ名が変わり(ネームマングリング)、元の変数名に簡単にアクセスできなくすることができます。この機能を使うことで、子クラスで同名のメンバがあった場合でも、実行時に元の親クラスの文脈で結果を評価することができるようになります。
pep8-ja - 命名規約 - メソッド名とインスタンス変数
結果を見てみましょう。

条件 結果
インスタンス変数 "SubClass"
インスタンス変数の代わりにメソッド "SubClass"
インスタンス変数(アンスコ2つ付き) "SuperClass"
インスタンス変数の代わりにメソッド(アンスコ2つ付き) "SuperClass"
コードを見る(Python)
class SuperClass:
  def __init__(self):
    self.instance_variable = "SuperClass"

  def super_class_method(self):
    print(self.instance_variable)

class SubClass(SuperClass):
  def __init__(self):
    super().__init__()
    self.instance_variable = "SubClass"

# ------------------------------

class SuperClassNameMangling:
  def __init__(self):
    self.__instance_variable = "SuperClass"

  def super_class_method(self):
    print(self.__instance_variable)

class SubClassNameMangling(SuperClassNameMangling):
  def __init__(self):
    super().__init__()
    self.__instance_variable = "SubClass"

# ------------------------------

class SuperClassGetter:
  def instance_variable(self):
    return "SuperClass"

  def super_class_method(self):
    print(self.instance_variable())

class SubClassGetter(SuperClassGetter):
  def instance_variable(self):
    return "SubClass"

# ------------------------------

class SuperClassNameManglingGetter:
  def __instance_variable(self):
    return "SuperClass"

  def super_class_method(self):
    print(self.__instance_variable())

class SubClassNameManglingGetter(SuperClassNameManglingGetter):
  def __instance_variable(self):
    return "SubClass"

print('---- SubClass ----')
sub = SubClass()
sub.super_class_method()

print('---- SubClassNameMangling ----')
subp = SubClassNameMangling()
subp.super_class_method()

print('---- SubClassGetter ----')
subg = SubClassGetter()
subg.super_class_method()

print('---- SubClassNameManglingGetter ----')
subpg = SubClassNameManglingGetter()
subpg.super_class_method()

Ruby

「オブジェクト指向の動的型付け言語といったら?」
という連想クイズをされたプログラマは、真っ先にこの言語を思い浮かべる方も多いのではないでしょうか。

Rubyにはメソッドに対して使えるprivateがありますが、Javaのような言語のprivateとは挙動が違います。詳しくはレシーバとかを説明しなきゃいけなくなるので書きませんが、とりあえず言えることはprivateがあっても継承先にはそのメソッドが見えるということです。なのでJavaでいうとprotectedの方が近いかもしれません。
[Ruby] privateメソッドの本質とそれを理解するメリット

ちなみにRubyは変数とメソッドの名前空間が分かれており、メソッドをカッコなしで書いても呼び出せます。
それからメソッドはreturnを書かなくても、最後に評価した式の結果を返します。
@変数名がインスタンス変数です。Rubyでは変数名の1文字目で変数の種別(スコープ)がわかります。

条件 結果
インスタンス変数 "SubClass"
インスタンス変数の代わりにメソッド "SubClass"
インスタンス変数の代わりにprivateなメソッド "SubClass"
コードを見る(Ruby)
class SuperClass
  def initialize()
    @instance_variable = "SuperClass"
  end

  def super_class_method
    p @instance_variable
  end
end

class SubClass < SuperClass
  def initialize()
    super()
    @instance_variable = "SubClass"
  end
end

# --------------------------------

class SuperClassGetter
  def instance_variable()
    "SuperClass"
  end

  def super_class_method
    p instance_variable
  end
end

class SubClassGetter < SuperClassGetter
  def instance_variable()
    "SubClass"
  end
end

# --------------------------------

class SuperClassGetterPrivate
  def super_class_method
    p instance_variable
  end

  private

  def instance_variable()
    "SuperClass"
  end
end

class SubClassGetterPrivate < SuperClassGetterPrivate
  private
  
  def instance_variable()
    "SubClass"
  end
end

p '---- SubClass ----'
subc = SubClass.new
subc.super_class_method

p '---- SubClassGetter ----'
subg = SubClassGetter.new
subg.super_class_method

p '---- SubClassGetterPrivate ----'
subgp = SubClassGetterPrivate.new
subgp.super_class_method

PHP

PHPにはJavaのようなprivateprotectedpublicがあります。
PythonやRubyやJavaScriptには基本的に無いのに! 羨ましい!
でもJavaとは違って、protectedなインスタンス変数はサブクラスのものを読みに行きます。
そこは他の動的型付け言語と足並みを揃えた動きですね。

条件 結果
インスタンス変数がprivate "SuperClass"
インスタンス変数がprotected "SubClass"
インスタンス変数の代わりにprivateなメソッド "SuperClass"
インスタンス変数の代わりにprotectedなメソッド "SubClass"
コードを見る(PHP)
<?php
function println($message)
{
    echo $message.PHP_EOL;
}

// ----------------------------------------

class SuperClass
{
    private $instanceVariable = "SuperClass";

    public function superClassMethod()
    {
        println($this->instanceVariable);
    }
}

class SubClass extends SuperClass
{
    private $instanceVariable = "SubClass";
}

// ----------------------------------------

class SuperClassProtected
{
    protected $instanceVariable = "SuperClass";

    public function superClassMethod()
    {
        println($this->instanceVariable);
    }
}

class SubClassProtected extends SuperClassProtected
{
    protected $instanceVariable = "SubClass";
}

// ----------------------------------------

class SuperClassGetter
{
    private function instanceVariable()
    {
        return "SuperClass";
    }
    
    public function superClassMethod()
    {
        println($this->instanceVariable());
    }
}

class SubClassGetter extends SuperClassGetter
{
    private function instanceVariable()
    {
        return "SubClass";
    }
}

// ----------------------------------------

class SuperClassGetterProtected
{
    protected function instanceVariable()
    {
        return "SuperClass";
    }
    
    public function superClassMethod()
    {
        println($this->instanceVariable());
    }
}

class SubClassGetterProtected extends SuperClassGetterProtected
{
    protected function instanceVariable()
    {
        return "SubClass";
    }
}

// ----------------------------------------

println("---- SubClass ----");
$sub = new SubClass();
$sub->superClassMethod();

println("---- SubClassProtected ----");
$subp = new SubClassProtected();
$subp->superClassMethod();

println("---- SubClassGetter ----");
$subg = new SubClassGetter();
$subg->superClassMethod();

println("---- SubClassGetterProtected ----");
$subgp = new SubClassGetterProtected();
$subgp->superClassMethod();

JavaScript

フロントエンドの覇者です。
プロトタイプベースオブジェクト指向言語という、プログラミング言語Selfから影響を受けた言語です。
ES6以降のJavaScriptではクラスベースの書き方も取り入れているため、extendsして継承もできます。

条件 結果
インスタンス変数 "SubClass"
古い書き方でインスタンス変数 "SubClass"
インスタンス変数で、呼び出しメソッドがアロー関数 "SubClass"
インスタンス変数の代わりにアロー関数 "SubClass"
コードを見る(JavaScript)
class SuperClass {
  constructor() {
    this.instanceVariable = "SuperClass"
  }

  superClassMethod() {
    console.log(this.instanceVariable)
  }
}

class SubClass extends SuperClass {
  constructor() {
    super()
    this.instanceVariable = "SubClass"
  }
}

// ------------------------

function LegacySuperClass() {
  this.instanceVariable = "SuperClass";
};
LegacySuperClass.prototype.superClassMethod = function() {
  console.log(this.instanceVariable)
}; 

function LegacySubClass() {
  this.instanceVariable = "SubClass";
};
LegacySubClass.prototype = new LegacySuperClass();

// ------------------------

class SuperClassArrow {
  constructor() {
    this.instanceVariable = "SuperClass"
  }

  superClassMethod = () => {
    console.log(this.instanceVariable)
  }
}

class SubClassArrow extends SuperClassArrow {
  constructor() {
    super()
    this.instanceVariable = "SubClass"
  }
}

// ------------------------

class SuperClassGetterArrow {
  instanceVariable = () => {
    return "SuperClass"
  }

  superClassMethod = () => {
    console.log(this.instanceVariable())
  }
}

class SubClassGetterArrow extends SuperClassGetterArrow {
  instanceVariable = () => {
    return "SubClass"
  }
}

// ------------------------

console.log('---- SubClass ----')
const sub = new SubClass()
sub.superClassMethod()

console.log('---- LegacySubClass ----')
var lsub = new LegacySubClass()
lsub.superClassMethod()

console.log('---- SubClassArrow ----')
const suba = new SubClassArrow()
suba.superClassMethod()

console.log('---- SubClassGetterArrow ----')
const subga = new SubClassGetterArrow()
subga.superClassMethod()

TypeScript

静的に型をつけられる、イケてるJavaScriptですね。
union型という、高度な型システムを持つHaskellに見られるような型に近いものがあったり、Conditional TypesやらMapped Typesやら表現力のある型があるのが特徴です。

TypeScriptではprivateな同名のメンバを親クラス子クラスで定義するとコンパイルエラーになります。protectedであれば問題ありません。Swiftとは真逆ですね。

条件 結果
インスタンス変数がprivate 定義不可
インスタンス変数がprotected "SubClass"
インスタンス変数の代わりにprivateなメソッド 定義不可
インスタンス変数がprotectedで、呼び出しメソッドがアロー関数 "SubClass"
インスタンス変数の代わりにprotectedなアロー関数 "SubClass"
コードを見る(TypeScript)
class SuperClass {
  private readonly instanceVariable: string

  constructor() {
    this.instanceVariable = "Error"
  }

  public superClassMethod() {
    console.log(this.instanceVariable)
  }
}

class SubClass extends SuperClass {
  // instanceVariableで定義しようとすると、
  // TS2415: Class 'SubClass' incorrectly extends base class 'SuperClass'.
  // private readonly instanceVariable: string

  // constructor() {
  //   super()
  //   this.instanceVariable = "SubClass"
  // }
}

// ------------------------

class SuperClassGetter {
  private instanceVariable() {
    return "Error"
  }

  public superClassMethod() {
    console.log(this.instanceVariable())
  }
}

class SubClassGetter extends SuperClassGetter {
  // instanceVariableで定義しようとすると、
  // TS2415: Class 'SubClassGetter' incorrectly extends base class 'SuperClassGetter'.
  // Types have separate declarations of a private property 'instanceVariable'.
  // private instanceVariable() {
  //   return "SubClass"
  // }
}

// ------------------------

class SuperClassProtected {
  protected readonly instanceVariable: string

  constructor() {
    this.instanceVariable = "SuperClass"
  }

  public superClassMethod() {
    console.log(this.instanceVariable)
  }
}

class SubClassProtected extends SuperClassProtected {
  protected readonly instanceVariable: string
  
  constructor() {
    super()
    this.instanceVariable = "SubClass"
  }
}

// ------------------------

class SuperClassProtectedGetterArrow {
  protected instanceVariable = () => {
    return "SuperClass"
  }

  public superClassMethod = () => {
    console.log(this.instanceVariable())
  }
}

class SubClassProtectedGetterArrow extends SuperClassProtectedGetterArrow {
  protected instanceVariable = () => {
    return "SubClass"
  }
}

// ------------------------

// アロー関数版
class SuperClassProtectedArrow {
  protected readonly instanceVariable: string

  constructor() {
    this.instanceVariable = "SuperClass"
  }

  public superClassMethod = () => {
    console.log(this.instanceVariable)
  }
}

class SubClassProtectedArrow extends SuperClassProtectedArrow {
  protected readonly instanceVariable: string
  
  constructor() {
    super()
    this.instanceVariable = "SubClass"
  }
}

console.log('---- SubClass ----')
const sub = new SubClass()
sub.superClassMethod()

console.log('---- SubClassGetter ----')
const subg = new SubClassGetter()
subg.superClassMethod()

console.log('---- SubClassProtected ----')
const subp = new SubClassProtected()
subp.superClassMethod()

console.log('---- SubClassProtectedArrow ----')
const subpa = new SubClassProtectedArrow()
subpa.superClassMethod()

console.log('---- SubClassProtectedGetterArrow ----')
const subpga = new SubClassProtectedGetterArrow()
subpga.superClassMethod()

Dart

Flutterが流行りだしてから急に存在感が出てきた印象な言語です。
Dartではprivatepublicを書くことはありません。メンバの名前にアンダースコアをつけることでprivateにすることはできますが、ライブラリ外から見えなくするというだけなので、Javaなどのprivateとは違います。

条件 結果
インスタンス変数(アンスコ付き) "SubClass"
インスタンス変数 "SubClass"
インスタンス変数の代わりにメソッド "SubClass"
コードを見る(Dart)
void main() {
  print("---- SubClassPrivate ----");
  final subp = new SubClassPrivate();
  subp.superClassMethod();

  print("---- SubClass ----");
  final sub = new SubClass();
  sub.superClassMethod();

  print("---- SubClassGetter ----");
  final subg = new SubClassGetter();
  subg.superClassMethod();
}

class SuperClassPrivate {
  // アンダースコアをつけるとPrivateになるが、
  // privateは同一ライブラリに対して可視のため、ここではサブクラスから普通に見えちゃう
  final String _instanceVariable = "SuperClass";

  void superClassMethod() => print(_instanceVariable);
}

class SubClassPrivate extends SuperClassPrivate {
  final String _instanceVariable = "SubClass";
}

// ------------------------------------

class SuperClass {
  final String instanceVariable = "SuperClass";

  void superClassMethod() => print(instanceVariable);
}

class SubClass extends SuperClass {
  final String instanceVariable = "SubClass";
}

// ------------------------------------

class SuperClassGetter {
  String instanceVariable() => "SuperClass";

  void superClassMethod() => print(instanceVariable());
}

class SubClassGetter extends SuperClassGetter {
  String instanceVariable() => "SubClass";
}

まとめ

ある程度使い慣れた言語でも知らないことがあったりして、意外と楽しかったです。
挙げた例の中には今回初めて書いた言語もあるので、こんな書き方あるよ! とか、間違ってるよ! とかあったらコメントください。

全体的にまとめると、動的型付け言語だとスーパークラスのメソッドを呼んだ時でもサブクラスの文脈でまずは評価させようとするような印象でした。一方の静的型付け言語では、「スーパークラスのメソッドなんだから基本はスーパークラスで評価する。ただしオーバーライドしたらその限りじゃない」という感じでした。

個人的に印象深かったのは、下記の5つでした。

  • C#にはnewというオーバーライドしない用の修飾子がある
  • ScalaとKotlinは静的型付け言語なのにインスタンス変数のオーバーライドができる
  • Pythonのアンダースコア2つのメンバ名はただの名前じゃなく効果もあった
  • PHPには動的型付け言語なのにJavaみたいなprivateがある
  • TypeScriptは重複するprivateな同名メンバをコンパイル時にエラーにして予期せぬ動作になることを防いでくれる

更新履歴

  • Pythonの命名規約の説明について誤りがあったため、修正を行いました。 @shiracamus さんありがとうございました。
  • Pythonではprivateという言葉は使わないと指摘があったため、修正を行いました。 @Luice さんありがとうございました。
3
8
7

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
3
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?