[雑談] 継承とポリモーフィズム、プロパティ

継承とポリモーフィズム、プロパティ (setter/getter) について一度まとめようかと思います。

まず、簡単なクラスを1個作ります。

class A {
  String name;
  String value;

  A() {

    name = "Class A";

  }

  String getName() {

    return name;

  }

  void setValue(String value) {

    this.value = value;

  }

  String getValue() {

    return value;

  }

}

内容は見た通りで、コンストラクタで初期化され、getterで読み出すことができるnameフィールドとsetter/getterで読み書きできるvalueフィールドを用意しています。

setter/getterを使用する場合、フィールドはprivateにするのが普通ですが、今回は値を調べたいと思うのでアクセス修飾子はデフォルト (パッケージプライベート) としています。

このクラスからコンストラクタだけを定義したクラスを派生させます。

class A2 extends A {
  A2() {
    name = "Class A2";

  }

}

これらのクラスを使用したmainプログラムを作成します。

public class Main {
  public static void main(String[] args) {
    A a = new A2();

    a.setValue("Value A2");

    System.out.println("a.getName(): " + a.getName());

    System.out.println("a.getValue(): " + a.getValue());

    System.out.println("a.name: " + a.name);

    System.out.println("a.value: " + a.value);

    A2 a2 = (A2) a;

    System.out.println("a2.getName(): " + a2.getName());

    System.out.println("a2.getValue(): " + a2.getValue());

    System.out.println("a2.name: " + a2.name);

    System.out.println("a2.value: " + a2.value);

  }

}

それでは実行してみましょう。

a.getName(): Class A2
a.getValue(): Value A2
a.name: Class A2

a.value: Value A2

a2.getName(): Class A2

a2.getValue(): Value A2

a2.name: Class A2

a2.value: Value A2

A2クラスにはフィールドname, value、メソッドgetName, setValue, getValueのいずれも定義されていませんが、Aクラスのフィールド・メソッドが継承されるので、定義されているような見え方をします。

また、nameフィールドの値はAクラスのコンストラクタでいったん"Class A"に設定されますが、A2クラスのコンストラクタが"Class A2”に書き換えるので"Class A2"となっています。

※ A2クラスのコンストラクタによるnameフィールドへの値の設定は初期化ではなく書き換えになります。Aクラスのnameフィールドにfinalが指定されているとA2クラスのコンストラクタによる書き換えができないのでコンパイルエラーになります。

次にフィールドだけを定義したA3クラスを作ってみます。

class A3 extends A {
  String name;
  String value;

  A3() {

    name = "Class A3";

  }

}

これで先と同じようなプログラムを実行してみると以下のようになります。

a.getName(): Class A
a.getValue(): Value A3
a.name: Class A

a.value: Value A3

a3.getName(): Class A

a3.getValue(): Value A3

a3.name: Class A3

a3.value: null

A3クラスで定義したname, valueフィールドがAクラスのフィールドを隠してしまうため、A3クラスのコンストラクタはA3クラスのnameフィールドに対する書き込みを行います。

このため、AクラスのnameフィールドはAクラスのコンストラクタで初期化した"Class A"がそのまま残り、getNameメソッドはAクラスで定義されているので、A3クラスのオブジェクトから呼び出してもAクラスのnameフィールドを参照して"Class A"を出力します。

setValue/getValueメソッドも同様にAクラスのvalueフィールドにしかアクセスしないので、A3クラスのvalueフィールドはnullのままとなっています。

このようにフィールドは定義したクラスのメソッドからしかアクセスできないことにりゅいが必要です。

今度はメソッドのみを定義したクラスを作ってみます。

class A4 extends A {
  A4() {
    name = "Class A4";

  }

  String getName() {

    return name;

  }

  void setValue(String value) {

    this.value = value;

  }

  String getValue() {

    return value;

  }

}

これをこれまでと同様に実行した結果は以下のようになりました。

a.getName(): Class A4
a.getValue(): Value A4
a.name: Class A4

a.value: Value A4

a4.getName(): Class A4

a4.getValue(): Value A4

a4.name: Class A4

a4.value: Value A4

Aクラスを通して操作しようが、A4クラスを通して操作しようが、値を記録するフィールドはAクラスで定義したものが継承の仕組みを通して両方のクラスで共通に使われるため、同じ結果が得られます。

最後にフィールドとメソッドの両方を再定義した場合の動作を見てみます。

class A5 extends A {
  String name;
  String value;

  A5() {

    name = "Class A5";

  }

  String getName() {

    return name;

  }

  void setValue(String value) {

    this.value = value;

  }

  String getValue() {

    return value;

  }

}

これをこれまでと同様に実行すると以下のような結果になります。

a.getName(): Class A5
a.getValue(): Value A5
a.name: Class A

a.value: null

a5.getName(): Class A5

a5.getValue(): Value A5

a5.name: Class A5

a5.value: Value A5

フィールドを再定義しているのでA3と同様に各メソッドは属するクラスのフィールドにアクセスします。

しかし、メソッドが再定義されているのでポリモーフィズムによりAクラスを通してメソッドを呼び出してもA5クラスのメソッドが呼ばれるため、A5クラスを通して呼び出した結果と同じものになります。

これはAクラスの各フィールドが更新されていないことからも確認できると思います。

以上を簡単にまとめると、再定義が行われていない場合には継承によりフィールド、メソッドともに派生クラスを通してアクセスしてもスーパークラスのフィールド、メソッドが使用されます。

フィールドの再定義が派生クラスで行われた場合、メソッドは各クラスから見えるフィールド、そのクラスで定義されたフィールド、もしくは直近のスーパークラスで定義されたフィールドのみにアクセスするため、メソッドが派生クラスで再定義されていなければ派生クラスで定義されたフィールドにはアクセスすることはできません。

メソッドの再定義が派生クラスで行われた場合にはスーパークラスからのメソッド呼び出しであっても派生クラスで再定義されたメソッドが実行されるため、派生クラスで定義されたフィールドにのみアクセスが可能で、スーパークラスで定義したフィールドにはアクセスできなくなります。

以上が継承とポリモーフィズムについて調べた結果となります。

ところで、Javaでソフトウエアを構築する際にはフィールドのアクセス修飾子をprivateとして、フィールドに値を設定するメソッド (setter) とフィールドから値を取得するメソッド (getter) を用意するプロパティと呼ばれる実装方法がよく行われています。

プロパティには様々な利点が挙げられていますが、今回見てきたようにフィールドでは対応できないポリモーフィズムを実現できる点も一つの特徴だと思います。



0コメント

  • 1000 / 1000