【Java入門】実際にどう使う?abstractの意味とメリット
「Java」と言えば、最もメジャーなプログラミング言語のひとつです。
「Java」はオブジェクト指向と呼ばれている言語の一種で、習得難易度は高めですが、Androidアプリや大企業の大規模システムなどにも使われる人気のプログラミング言語です。
今回はJavaの中でも特に取り扱い方の理解が難しい「abstract」について、その意味や使用のメリットなどについて解説したいと思います。
「Javaでプログラムを作っているけれど、abstractの使い方があまりよくわかっていない」という方はぜひ参考にしてください。
Contents [hide]
【STEP1】「abstract」とは何か?
「abstract」は直訳すると「抽象的」という意味です。
「抽象的」とは、一体どのようなことなのでしょうか。
抽象的を辞書で調べると、以下の2つの意味が書かれています。
1・・・いくつかの事物に共通なものを抜き出して、それを一般化して考えるさま「例文:本質を抽象的に捉える」
2・・・頭の中だけで考えていて、具体性に欠けるさま。「例文:抽象的で、わかりにくい文章
コトバンク
Javaの「abstract=抽象的」は、1の意味に近く「共通なものを抜き出し、まとめる」事ができると考えてみてください。
この説明も具体性に欠ける為、抽象的な感じではありますが、具体的に「abstract」を解説していきたいと思います。
プログラミングで使用される「abstract」とは?
プログラミング言語における「abstract」は「メソッドの修飾子」として使われます。
※ 修飾子とは、メソッド、クラスなどの性質を決定付けするものです。
【※初心者向け】「メソッド」ってなに?
メソッドとはそもそも何でしょうか。
簡単に説明すると「メソッド=複数の処理をまとめたプログラム」となります。
プログラミング中級者であれば、メソッドを使い、効率的なプログラムを作成することができるはずです。
例えばJava初心者が初期に覚える条件分岐の制御文の代表「if文」。
このif文が同じ条件式の中で複数回出てくる場合、3回、4回と同じような処理を行うif文があると、無駄に長いプログラムになってしまいます。
「配列」や「繰り返し処理」を行うことで、プログラムの無駄を省くことができるのは皆さんもご存じの通りです。
if文は離れた箇所で使用することはできません。
その離れた箇所で使用する場合に使われるのが「メソッド」です。
何度も実行される処理をメソッドにしてひとまとめにしておくと、離れた場所からも何度でも呼び出すことのできる、効率的なプログラミングが可能になります。
「abstract」は未定義のメソッドを定義する言語
「abstract」を用いたソースコードの記述例を挙げてみましょう。
abstracに「void」を付けることで、戻り値を返さないメソッドとして定義できます。
abstract void method(); |
このソースコードは「このメソッドはabstractなメソッドです」という意味です。
わかりやすく言うと、「まだ処理内容が定義されていないメソッド=抽象メソッド」となります。
ここでのポイントは、「{}」。
定義するメソッドを「{}」ではなく、「;※セミコロン」を使用していることに注目してください。
通常はメソッド名()の後に処理を{}で囲って記述しますが、抽象メソッドでは記述内容を持たないため、メソッド名のすぐ後ろにセミコロンを記述します。
【STEP2】abstractクラスってなに?
「abstractクラス」は抽象クラスのこと。
「abstractメソッド」は、抽象メソッドを1つ以上もつクラスのことです。
abstract classクラス名{abstract 戻り値の型名 メソッド名(引数);} |
もし1つでも抽象メソッドが含まれている場合は、そのクラスは抽象クラスとなります。
そもそも「クラス」とは
プログラミングにおける「クラス」とはどういったことを表すのでしょうか?
クラスを表すソースコードは以下のようなプログラムになります。
class クラス名{
・・・ } |
この{}で囲まれた“・・・”の部分に、クラスの具体的な処理内容を入れます。
この処理内容は、「このプログラムに具体的に何をさせたいのか」ということです。
例えば、車であれば、「人を乗せて走る」ということが処理内容にあたりますし、携帯電話であれば、「電波を利用して離れた相手と通話をすること」になります。
クラスというのはそれら「具体的に何をさせたいのか」を示す「設計図」と言った方がわかりやすいかもしれません。
イメージとしては・・・
設計図(class) 車(クラス名){
人を乗せて走る } |
設計図(class) 携帯電話(クラス名){
電波を利用して離れた相手と通話をする } |
というソースコードになります。
【STEP3】abstractクラスのメリットとデメリット
「abstract」のクラスについて簡単に説明してきましたが、「abstract」はその特性を理解したうえで使いこなすことが重要です。
ここでは、具体的な「抽象クラス」のメリットとデメリットを解説していきます。
abstractクラスのメリット
abstractクラス=抽象クラスのメリットは「複数のクラスに散らばっている機能の、実装と管理が容易になる」ことです。
専門用語を使って説明すると、抽象クラスは「オーバーライドすることを前提に作成。処理とデータを持たないメソッド」のこと。
ちなみに抽象クラスと抽象メソッドは、「継承」されて初めて本来の役割を果たすことができます。
先ほどの「車」を例にしてみましょう。
設計図(class)車(クラス名){
abstract void 人を乗せて走る(); abstract void 同時に5名まで乗車(); abstract void 荷物を300Kg乗せて走る(); } |
というソースコードがあったと仮定します。
この車クラスには、「人を乗せて走る」、「同時に5名まで乗車」、「荷物を300Kg乗せて走る」というメソッドが継承される前提で「abstract」を使って事前にプログラムを作成することが可能です。
「abstract」のメリットとして、「開発者側にメソッドのオーバーライドを強制することができる」という点があります。
プログラミングを行うのはプログラマーにとって当たり前の仕事ですが、メソッドまで決めるのはクライアント(開発者側)の役割です。
クライアントが、どのようなメソッドを実装するのかという具体的な内容が決まっていない場合、その作業を飛ばしてプログラムを作成することが可能になります。
ソースコードで「abstract」を使ったプログラミングしておけば、後になって、開発者側から「こんなメソッドを入れてほしい」というときに手間が省けるのです。
そのメソッドを作成し、「継承」するだけでプログラミングが完了します。
【※初心者向け】「オーバーライド」ってなに?
プログラミング用語である「オーバーライド」
プログラミングにおける「オーバーライド」とはどのような意味合いなのでしょうか。それは「親クラスで定義されているメソッドを単純に引き継ぐだけではなく、子クラスで再定義すること」です。
似たような言葉に「オーバーロード」というものがありますが、全く関係のないものですので、混同しないよう注意が必要です。わかりやすく先ほどの「車」で説明していきます。
class Car{ int gas = 60; //ガソリン // gasを消費して走る public void drive(int gas){ this.gas -= gas; System.out.println(gas * 10 + “km走りました”); } // gasを補給する public void putGas(int gas){ this.gas += gas; System.out.println(“ガソリンを” + gas + “リットル補給しました”); } // 現在のgasの値を出力する public void checkGas(){ System.out.println(“ガソリンは残り” + this.gas + “リットルです”); } } |
コード引用:https://nobuo-create.net/java-beginner-26/
車クラスでメソッドは「ガソリンを消費して走る」、「ガソリンを補給して走る」、「ガソリンの残量を出力する」の3種類、フィールドは60リットルのガソリンです。
燃費は1リットル当たり10km走ると定義しましょう。
定義した内容を、まずプログラミングコードに変換し、実際に車を走らせてみます。
するとモニター上には、このメソッドの結果が表示されます。
200km走りました
300km走りました ガソリンを40リットル補給しました ガソリンは残り50リットルです |
60リットル満タンの状態から計500km走行したわけですから、残りのガソリンは、600km(満タン60リットルで走行可能な距離)-(500km÷10km※1リットル当たりの走行距離)=100km分あることになります。
ガソリンの量は100km÷10km※1リットル当たりの走行距離)=10リットルで、そのあとガソリンを40リットル補給したため、残りのガソリンは50リットルということになります。
この定義を「親クラス」として、今度は、今流行りの「エコカー」のクラス(子クラス)で燃費の定義を1リットルあたり「20km」にして再度プログラミングをしてみましょう。
子クラスの定義は、以下のように行います。
Class 子クラス名 extends 親クラス名 {
・・・ } |
この場合、子クラスでは親クラスのメソッドを継承。
継承をすることで、子クラスから親クラスのメンバ変数やメンバメソッドをそのまま使うことが可能になります。
class EconomyCar extends Car{
@Override public void drive(int gas){ this.gas -= gas; System.out.println(gas * 20 + “km走りました”); } } |
コード引用:https://nobuo-create.net/java-beginner-26/
この時、最初からプログラミングを行うのではなく、燃費の定義を「1リットルあたり20km」にしてオーバーライドさせると、どうなるのでしょうか。
オーバーライドさせることにより親クラスのインスタンスを内包した状態のままで、新しいクラスの「エコカークラス」のプログラムが作成できるようになるのです。
また、オーバーライドにはメリットがあります。
親クラスのメソッドを、子クラスのメソッドが覆い隠している状態になり、これにより無駄なソースコードの記述が不要になるのです。
abstractクラスのデメリット
次は、「抽象クラス」のデメリットについて解説してきます。
簡単な例として、車クラスで作ってみましょう。
車クラスの条件は以下です。
- ガソリンの許容量は60リットル
- 車にはそれぞれ役割があり、人間の運搬用、荷物の運搬用、レース用の3種類がある
- 燃費は全て1リットルあたり10km
プログラミング上の理論では
車クラス |
車の名前 |
ガソリンの許容量() 車の役割() 燃費() |
このような記述になります。
ガソリンの許容量と、燃費に関しては全車共通の処理のため問題ありませんが、車の役割に関しては、車の種類によって異なります。
この問題をどう解決すればいいのでしょうか。
以下の2つの方法があります。
- 「人間運搬用クラス」と「荷物運搬用クラス」と「レース用クラス」を個別に作成。
- 「車の役割()」に引数を持たせて、その引数の値でそれぞれの役割を判別し処理を分ける。または「車の役割()」の代わりとしてそれぞれの役割用にメソッドを作成して呼び分ける。
(例:運搬乗員人数メソッド、運搬可能な積載量メソッド、最高速メソッドなど)
しかし、この2つの解決方法にはメリット、デメリットが存在します。
順に見ていきましょう。
【1の解決方法のメリット】
他クラスの変化(仕様など)や追加(新しいクラスなど)に対して、他のクラスへの影響を抑えることができる。
【1の解決方法のデメリット】
それぞれのクラスに共通している処理(ガソリン許容量、燃費)もしっかり個別に記述する必要があります。
それにより、統一性(メソッドのシグニチャなど)がない可能性があるのです。
実際、記述ミスをしたとしても、コンパイル段階で判別はできません。
【2の解決方法のメリット】
それぞれの役割クラスを、役割クラス1つで呼び出すことが可能です。
つまり各クラスを使う側からすると、取り扱いが容易になります。
【2の解決方法のデメリット】
1つの修正がプログラム全体に及ぶ危険性が増す。
では、1か2、どちらを選択すればいいのでしょうか。
実は、このような時に抽象クラスを使うことにより、1と2のメリットを活かしつつ、デメリットを解消することができるのです。
「車の役割()」部分を「抽象メソッド」、車クラスを「抽象クラス」として構成をし直します。
「ガソリンの許容量」と「燃費」は抽象メソッドではなく、具象メソッドとして記述します。
車クラス |
車の名前 |
ガソリンの許容量() 車の役割{; 燃費() |
「車の役割()」を抽象メソッドにすることで、これを継承するクラスに「車の役割()」の実装を強制できます。
車の役割には、「人間運搬用」、「荷物運搬用」、「レース用」のクラスを作り、それぞれに役割()クラスに応じた処理を記述します。
この処理を行うと、以下3つのメリットの担保とデメリットの解消ができます。
- 役割クラスを個別に作成するが、継承することにより共通メソッドは記述せずに使用可能。1のメリットである他クラスへの影響を抑えることも可能。同時に二つの方法のデメリットである余分な記述が必要なくなる。
- 抽象メソッドである「車の役割()」の実装が保証され、1のデメリットである記述ミスが無くなる。
- 各役割のクラスは、親クラスである「役割クラス型」で統一した呼び出し方が可能。そのため2のメリットである取り扱いが、容易である部分が担保できる。
このような便利な方法で、クラスとメソッドをまとめることができました。
しかし、残念なことに、抽象クラスや抽象メソッドは、実装がないと抽象クラスや抽象メソッド単体では全く役に立ちません。
実装ありきなのです。
これが「抽象クラス」のデメリットになります。
【STEP4】実際にabstractクラスとメソッドを書いてみよう!
ここからは実際に抽象クラスと抽象メソッドを書き方を解説します。
初心者向けに簡単な内容の記述をソースコードに置き換えるので、初心者の方も参考にしてみてください。
abstractクラスとメソッドはどう書く?
まず抽象クラスと抽象メソッドのソースコードは「abstract」が必須です。
クラスでプログラミングする場合には、「abstract class」がクラス名の最初に記述され、その後に「任意のクラス名」を記述します。
メソッドで記述する場合には、メソッド名の先頭に「abstract」を入力します。
「abstract public void method();」のように最後部分のカッコの後に「;」セミコロンを使用します。
ソースコードを書いてみる【解説あり】
では実際にソースコードを書いてみましょう。
一般的な「抽象クラス」や「抽象メソッド」の記述は以下です。
abstract class AbstractClass {
abstract public void method(); } class ConcreteClass extends AbstractClass{ public void method(){ // 実装内容 } } |
引用元:https://techacademy.jp/magazine/17710
ここで注意したいのが、「抽象クラスの抽象メソッドは宣言のみが行われる」ということです。
処理の中身を記述しないため、ややこしい部分があると感じるかもしれません。
ですが、後から実装を強制するという前提があります。
この部分は空白にする必要があるのです。
簡単な抽象クラスを使った例を挙げてみましょう。
抽象クラス1つ、そしてそれを継承するために具象クラスを2つ作成します。
抽象クラスで実装したメソッドにより、動作が変化するようにします。
例題は「犬と猫の鳴き声」です。
public class AbstractDemo {
public static void main(String[] args) { // Catクラスをインスタンス化する Animal cat = new Cat(); cat.animalCry(); // Dogクラスをインスタンス化する Animal dog = new Dog(); dog.animalCry(); } } // 抽象クラス abstract class Animal { private String name; public Animal(String name) { this.name = name; } public void animalCry() { System.out.println(name + “の鳴き方: ” + cry()); } // 抽象メソッド // 具象クラスからのみアクセスするため、protected修飾とする abstract protected String cry(); } // 具象クラス 1つ目 class Cat extends Animal { public Cat() { super(“猫”); } // メソッドを実装する protected String cry() { return “ニャー”; } } //具象クラス 2つ目 class Dog extends Animal { public Dog() { super(“犬”); } // メソッドを実装する protected String cry() { return “ワン”; } } |
引用元:https://techacademy.jp/magazine/17710
実行結果
猫の鳴き方: ニャー
犬の鳴き方: ワン |
引用元:https://techacademy.jp/magazine/17710
最初に抽象クラスである「Animal」を作成し、「鳴く」という「cryメソッド」を抽象メソッドとしています。
具象クラスである「犬(Dog)クラス」、「猫(Cat)クラス」を作成します。
それぞれのクラス(犬クラス、猫クラス)のインスタンスを作成し、「animalCryメソッド」を呼び出します。
そして具象メソッドの実装に従ってインスタンス動作が変化していることを確認します。
注意したいのは、「Animalクラス」はインスタンス化ができないという点です。
あくまでメソッドである「Cry」が目的ですので、本来「犬、猫」と入力するところに「ワン」や「ニャー」と入力しても「犬」、「猫」という動作にはなりません。
abstractを使いこなして効率的なクラス設計を
設計された決まり事を、より正確に実現することに役立つのが「abstract」です。
一番重要なのは、「クラス設計」がきちんとできているかという所です。
クラス設計に問題がある場合、「abstract」は何の役にもたちません。
「abstract」を使いこなすためには、クラス設計を高いレベルでできることが最低条件になります。
高いレベルでクラス設計をするためには、「デザインパターン」についても学ぶ必要があるでしょう。
「abstract」は「クラス設計」を効率的に行うための「ツール」であることに留意する必要があります。
- 「abstract」=「抽象的な」の意味はいくつかの事物に共通なものを抜き出して、それを一般化して考えるさま
- メソッドとは「複数の処理をまとめたプログラム」のこと
- 「抽象クラス=abstractクラス」とは、抽象メソッド=abstractメソッドを1つ以上もつクラスのこと
- 「抽象クラス」のメリットは「複数のクラスに散らばっている機能の、実装と管理が容易になる」こと
- 「抽象クラス」のデメリットは「実装ありき」なこと
- 「abstract」はあくまで「ツール」であり、使いこなすためには、高いレベルの「クラス設計技術」が必要