LoginSignup
4

More than 5 years have passed since last update.

継承とオーバーライドで怠惰しよう

Last updated at Posted at 2018-11-12

継承はねぇ~いいぞぉ~...

学校で僕がだいしゅきな継承が出てきたのでノリと勢いで継承について語りました。
ほとんどアドリブなのでabstractとかも出てきます。

で、継承ってなんぞや

継承っていうのは他のクラスを取り込むことで、他のクラスに書かれているメソッドとか変数をインスタンス生成あたかも自分のクラスに書かれているメソッドとか、変数のように扱うことができるようになる。

継承をするにはextendsキーワードを使う。
継承されたクラスのことを親クラス(super class)と呼び、継承をし拡張されたクラスのことを子クラス(Sub Class)という。

親クラス
class SuperClass{
  public int num = 10;

  public void superClassMethod(){
    System.out.println("親クラスだよ~");
  }
}
子クラス
class SubClass extends SuperClass{
  //とりあえず何も書かない
}
mainメソッドを持つクラス
class Test{
  public static void main(String[] args){
    SubClass instance = new SubClass();
    instance.superClassMethod();
  }
}

// >>>「親クラスだよ~」と表示される。

図で関係性を表してみるとこんな感じ。
名称未設定1.png
子クラスは親クラスを含んでいるような感覚。
日本語で考えると子の中に親?みたいに思うかもしれないけど、頑張って慣れて...

子クラスは親クラスのprivateではない変数もメソッドも使うことができる。
親クラスのprivateな変数などを使いたい場合はGetter・Setterメソッドを用意してやればいい。

子クラス
class SubClass extends SuperClass{
  void test(){
    //親クラスの変数を参照する
    System.out.println(num);  //10

    //親クラスのメソッドを呼んでみる
    superClassMethod();
  }
}

こんな感じで継承すると、他のクラスを自分のクラスに取り込んでしまったような感じにすることができた。
けど、いろいろな場所で同じメソッドを使えるというだけならクラスメソッドとかにすればいいじゃーんってね。

そうじゃねぇんだ。

継承するとオーバーライドができるようになる。やってみよう。

オーバーライドしてみよう

Override(オーバーライド)をするとスーパークラスのメソッドを上乗せ定義することができる。
なんでそんなことが必要なの?っていうのは後述するからちょっと待っててね。

親クラス
class SuperClass{
  public void printMethod(){
    System.out.println("親クラスだよ~");
  }
}
子クラス
class SubClass extends SuperClass{
  @Override
  public void printMethod(){
    System.out.println("オーバーライドしてやったぜ!");
  }
}

このように親クラスのメソッドと同じ名前、同じ引数を持つメソッドを子クラスで定義することでメソッドをオーバーライドすることができる。

@Overrideアノテーションというものをメソッドの前に書いている。
これはJavaのコンパイラに対して「このメソッドはオーバーライドしてるんやで」と示すためのもので、プログラムの動作には関係ない。
これがないとオーバーライドしてるかどうかわかんねぇのと、親クラスのメソッドと名前が一致してないとエラーを出してくれるので付けておくのが定石。

詳しいこと知りたくば次のとこを読むとよし。
別にいいや~って人は別に読まなくてよし。

@Overrideがないとどうなるの?

なんで動作に関係ないものが必要なのか。
例えば、例ではprintMethod()というメソッドを上乗せ定義しているが、ちょっと誤字っちゃってpriiiiiiiiiintMethod()というメソッド名になってしまったとする。

子クラス
class SubClass extends SuperClass{
  public void priiiiiiiiiintMethod(){
    System.out.println("オーバーライドしてやったぜ!");
  }
}

これに@Overrideがついていないと、オーバーライドをしようとしてないメソッドとして普通にコンパイルできてしまう。
このメソッドを呼び出す側でもう一度正確に誤字ってしまう(?)と普通に使えるので、目的の動作をしていないことに気づくのに時間がかかってしまう。

そんなヒューマンエラーを防止するために@Overrideアノテーションを書いた方がよい。
IDEで作業するときなんかは、入力補完機能もよく働いてくれるし。

なんでこんな面倒くさい事するんだ?

「別に継承したいんだったらコピペなりなんなりしてもう一個クラス作っちゃえばいいじゃん」って、僕も思ったんだけど、継承は僕らが思っているよりもかなりインテリジェンスだ。

コピペコードを削減できるから

まず、似たクラスを用意する際にコピペせずに済むので、コード量の削減につながる。
「別に長くてもいいじゃん」って?1文字多く打つのもめんどくせぇと思う怠惰になろう!!

プログラム的に生き物を作る例で話を進めていこう。

継承せずに書いてみる

お犬さん
class Wanko{
  public void eat(String foodName){
    //犬の食べ方をする処理が書かれていると思って

    //エサ皿に...
    //Direct...
    //Attack!!(犬食い)

    System.out.println(foodName+"食った");

    //消化する
    syouka(foodName);
  }

  public void syouka(String foodName){
    System.out.println(foodName+"消化した");
  }
}
おサルさん
class Monkey{
  public void eat(String foodName){
    //サルの食べ方をする処理が書かれていると思って

    //手で拾って...
    //おクチに...
    //運んで食べてほしい。

    System.out.println(foodName+"食った");

    //消化する
    syouka(foodName);
  }

  public void syouka(String foodName){
    System.out.println(foodName+"消化した");
  }
}

さて、これら2つのメソッドに似てる箇所がいくつかあるだろう。
これを継承によってコード量を削減していこう。

継承して書いてみる

まず、下地となる生き物クラスを作る。
僕は生物学者ではないのでわかんないけど、おサルもお犬も消化する手順は同じものとする。

生き物
class Animal{
  public void eat(String foodName){
    System.out.println(foodName+"食った");

    //消化処理
    syouka(foodName);
  }
  public void syouka(String foodName){
    System.out.println(foodName+"消化した");
  }
}

これを継承してお犬さんクラスとおサルさんクラスを書こう。

お犬さん
class Wanko extends Animal{
  @Override
  public void eat(String foodName){
    //犬の食べ方をする処理が書かれていると思って
    //エサ皿に...
    //Direct...
    //Attack!!(犬食い)

    //super()を使うと親クラスのeatメソッドを呼べる。
    super(foodName);
  }
}
おサルさん
class Monkey extends Animal{
  @Override
  public void eat(String foodName){
    //サルの食べ方をする処理が書かれていると思って

    //手で拾って...
    //おクチに...
    //運んで食べてほしい。

    //super()を使うと親クラスのeatメソッドを呼べる。
    super(foodName);
  }
}

突然super();という書き方を出したが、なんてことはない。親クラスのメソッドを呼んでいるだけだ。
親クラスのeat()メソッドでお犬とおサルで共通の処理である何を食ったか表示する処理と消化処理をしている。

ちなみに...(super()について)

super()はオーバーライドしたメソッドの中、子クラスのコンストラクタ内で使えるキーワードで、親クラスのメソッド、コンストラクタを呼び出すことができる。
super()も普通のメソッドと同じく引数を渡したり、オーバーロードもできる。

abstractキーワード

前述のAnimalクラスは継承を前提としたクラスだった。
このままであればAnimalクラスは継承をせずにインスタンスを生成し使うこともできるが、そのために作ったクラスじゃないっすよね。

そんなときはabstractキーワード。abstractはクラスとメソッドにも付けられる。

abstract抽象無形という意味なんですって。
その名の通り抽象的なクラス、メソッドなので、具体的にしてから使ってくれやってこと。

クラスにabstractを付けた場合はnewキーワードによるインスタンス生成を禁止し、継承をしないと使えないようになる。
abstractを付けたクラスのことを抽象クラスなんて呼ぶ。

メソッドにabstractを付けた場合は処理を記述できず、子クラスにオーバーライドを強制させる。
子クラスからオーバーライドしないとコンパイルが通らなくなる。
また、当然のごとくsuper()は使えない。

生き物
class abstract Animal{
  public void eat(String foodName){
    System.out.println(foodName+"食った");

    //消化処理
    syouka(foodName);
  }
  public void syouka(String foodName){
    System.out.println(foodName+"消化した");
  }

  public abstract void printName();
}
おサルさん
class Monkey extends Animal{
  @Override
  public void eat(String foodName){
    //サルの食べ方をする処理が書かれていると思って

    //手で拾って...
    //おクチに...
    //運んで食べてほしい。

    //super()を使うと親クラスのeatメソッドを呼べる。
    super(foodName);
  }

  //printName()メソッドを必ずオーバーライドする必要がある。
  @Override
  public void printName(){
    System.out.println("ワイはおサルさんやで");
  }
}

finalキーワード

finalを付けるとそれ以上に変更することを禁止できる。。

クラスに付けると継承を禁止する。
メソッドに付けるとオーバーライドを禁止する。
変数に付けると変数の宣言時以外の値の再代入を禁止する。

親クラス型として扱えるから

親クラス型にアップキャストできるのが本当に便利。

前述の動物たちをまた持ってくる。

生き物
class Animal{
  public void eat(String foodName){
    System.out.println(foodName+"食った");

    //消化処理
    syouka(foodName);
  }
  public void syouka(String foodName){
    System.out.println(foodName+"消化した");
  }
}
お犬さん
class Wanko extends Animal{
  @Override
  public void eat(String foodName){
    //犬の食べ方をする処理が書かれていると思って
    //エサ皿に...
    //Direct...
    //Attack!!(犬食い)

    //super()を使うと親クラスのeatメソッドを呼べる。
    super(foodName);
  }
}
おサルさん
class Monkey extends Animal{
  @Override
  public void eat(String foodName){
    //サルの食べ方をする処理が書かれていると思って

    //手で拾って...
    //おクチに...
    //運んで食べてほしい。

    //super()を使うと親クラスのeatメソッドを呼べる。
    super(foodName);
  }
}

Animalとお犬さん、おサルさんは親子関係にある。
親子関係にあると型変換ができるんですねぇ...!

まず、型変換せずにつらつらと書いてみますか。

class MeshiKue{
  public static void main(String[] args){
    Wanko dog = new Wanko();
    Monkey monkey = new Monkey();

    Gohan(dog);
    Gohan(monkey);
  }

  void Gohan(Wanko wanko){
    wanko.eat("りんご");
  }

  void Gohan(Monkey monkey){
    monkey.eat("りんご");
  }
}   

まぁ、こうなりますわな。
2種類だからいいけど、のちに仲間が増えてサーバルキャットとかアライさんとかフェネックやめるのだぁ~とかが増えたとしたらGohan()メソッドを種類の分だけいちいち書かなきゃいけなくなるよね。

それが全部同じ処理だったら目もあてられないので、型変換を使って1つで済むようにしよう。

class MeshiKue{
  public static void main(String[] args){
    Wanko dog = new Wanko();
    Monkey monkey = new Monkey();

    //お犬とおサルをAnimal型へアップキャストする
    Gohan((Animal)dog);
    Gohan((Animal)monkey);
  }

  //ひとつで済んだ!
  void Gohan(Animal animal){
    animal.eat("りんご");
  }
}   

子クラス型を親クラス型へキャストすることをアップキャスト、親クラス型を子クラス型へキャストすることをダウンキャストという。
ダウンキャストは1度アップキャストした変数にのみ行うことができる。

親子関係があると、親クラスに含まれているメソッド、変数を子クラスが必ず持っていることになるので、親クラスへキャストすることができる。

親子関係がない2のクラス間で同じ名前・仮引数のメソッドがあっても、それはたまたま名前と仮引数がかぶったメソッドが存在しているだけに過ぎないので、静的型付け言語では型変換が行えない。

ちなみに

Pythonとかの動的型付け言語だとダックタイピングっつって、こういうたまたま同じ名前のメソッドがある親子関係のない状態でもこのような処理を行うことができる。
動的型付け言語は行き当たりばったりに一行ずつ実行していくので、その時に存在していればいいのだ。

さいごに

プログラム書くのメンドクセェェェェ!って思ったらそれを解決できる言語機能がないか、より短い構文はないか探してみると、いいプログラマって呼ばれる人種に近づけると僕は信じてるので、頑張って探していこう!!

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
4