継承はねぇ~いいぞぉ~...
学校で僕がだいしゅきな継承が出てきたのでノリと勢いで継承について語りました。
ほとんどアドリブなのでabstractとかも出てきます。
で、継承ってなんぞや
継承っていうのは他のクラスを取り込むことで、他のクラスに書かれているメソッドとか変数をインスタンス生成あたかも自分のクラスに書かれているメソッドとか、変数のように扱うことができるようになる。
継承をするにはextends
キーワードを使う。
継承されたクラスのことを親クラス(super class)と呼び、継承をし拡張されたクラスのことを子クラス(Sub Class)という。
class SuperClass{
public int num = 10;
public void superClassMethod(){
System.out.println("親クラスだよ~");
}
}
class SubClass extends SuperClass{
//とりあえず何も書かない
}
class Test{
public static void main(String[] args){
SubClass instance = new SubClass();
instance.superClassMethod();
}
}
// >>>「親クラスだよ~」と表示される。
図で関係性を表してみるとこんな感じ。
子クラスは親クラスを含んでいるような感覚。
日本語で考えると子の中に親?みたいに思うかもしれないけど、頑張って慣れて...
子クラスは親クラスの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とかの動的型付け言語だとダックタイピングっつって、こういうたまたま同じ名前のメソッドがある親子関係のない状態でもこのような処理を行うことができる。
動的型付け言語は行き当たりばったりに一行ずつ実行していくので、その時に存在していればいいのだ。
さいごに
プログラム書くのメンドクセェェェェ!って思ったらそれを解決できる言語機能がないか、より短い構文はないか探してみると、いいプログラマって呼ばれる人種に近づけると僕は信じてるので、頑張って探していこう!!