抽象クラス
これまで書いてきたプログラムは具体的な処理を持っているため「具象クラス」といわれています。
一方で、Javaには処理は書かずに記載内容の指定を行うようなクラスがあります。これを「抽象クラス(abstract class)」と呼びます。
抽象クラスとは、文字通り抽象的存在のクラスであり、具体的な処理は抽象クラスを継承したクラスに記述します。抽象クラスの存在意義は複数のクラスに対して強制的に共通機能(共通処理)を持たせることです。
抽象クラスはオブジェクト化できない特殊なクラスの為、継承して利用する事が大前提になります。
抽象クラスの書式
// 抽象クラスの書式
public abstract class 抽象クラス名
{
コンストラクタ
各メンバ(フィールド変数、メソッド)
abstract 戻り値の型 抽象メソッド名(引数);
}
抽象クラスの凡例
「com.cmps」直下に「abstractClass」パッケージを作成、
「Abstract」「SampleA」「FunSampleA」クラスを作成し、以下のソースを記述しましょう。
・Abstractクラス
public class Abstract {
public static void main(String[] args) {
// コメントアウトしてエラー内容を見てください。
// 抽象クラスはインスタンス化が出来ない
// Sample sample = new Sample();
// Sampleをもとに設計した
// FunSampleAをインスタンス化してみる
SampleA sampleA = new FunSampleA();
FunSampleA sampleB = new FunSampleA();
System.out.println(sampleA.getClass());
System.out.println(sampleB.getClass());
}
}
・SampleAクラス
public abstract class SampleA {
public abstract void fun();
public abstract void funCustom();
}
このクラスが抽象クラスです。
記載を見ると「fun()」「funCustom()」で処理を終えています。
この状態では、「継承してこのSampleAを使うときは、クラスに必ずfun()メソッドを追加してください。」
という意味になります。
・FunSampleAクラス
public class FunSampleA extends SampleA {
@Override
public void fun() {
System.out.println("設計図");
}
// abstractで指定したメソッドを記載しないとエラーになる
// 中身は適当で良いのでfunCustom()メソッドを作成する
@Override
public void funCustom() {
System.out.println("test");
}
}
SampleAクラスを継承しているFunSampleAクラスです。
このクラスでは、SampleAクラスで指定したメソッドが無いと(オーバーライドしないと)エラーとなってしまいます。
※fun()メソッドもしくはfunCustom()メソッドのどちらかをコメントアウトすればエラーが出ます
抽象クラスの定義は「abstract修飾子」を利用する事以外は、通常のクラスを定義する方法と同じです。
「public abstract class」や「abstract void」といったようにクラス、メソッドに対して「abstract」を記載することで宣言できます。
「abstract」の利便性、使いどころは、クラスに実装する処理を指定できるポイントにあります。
例えば、「funSample」というプログラムをA~Zまで26個作るとして、その開発を複数人で実施することになったとします。
各個人のプログラムの書き方の個性によって作成される処理は多岐にわたって作成されることになります。
そして、いざプログラムをまとめて実行しようとしたときに、
funというメソッドを作っていない人がいたために処理が出来なかった、といった状況が発生することがあります。
そのため、事前に「abstractクラスとなるSampleAでこのメソッドは絶対入れてください」と指定することで、
他のプログラマーがみてもfunメソッドを必ず入れた処理にするため、プログラムのミスが少なくできます。
抽象クラスを継承した新しいクラスを利用するプログラム
抽象クラスを継承して新しいクラスを作成し、継承元(スーパークラス)の機能も自身のクラスの機能も正しく利用できる事を確認します。
先程作ったクラスに追記していきましょう(元の記述はコメントアウトしましょう)
・SampleAクラス ※親クラス(抽象クラス)
public abstract class SampleA {
// 消費電力
private int epower;
// コンストラクタ
public SampleA() {
this.epower = 0;
}
// アクセサメソッド(共通処理)
public void setEpower(int epower) {
this.epower = epower;
System.out.println("消費電力を" + epower + "Wに設定しました。");
}
public int getEpower() {
return epower;
}
// 抽象メソッド
public abstract void show();
}
抽象クラスでも基本的なメンバ(フィールド変数、コンストラクタ、メソッド)を宣言できるのは、通常のクラスと変わりません。
機械の共通機能として消費電力を扱う為の定義を行っています。
show()メソッドは定義はしても、処理の中身までは定義しません。
SampleAクラスを継承した子クラスに必ず実装してもらうために定義しています。
・FunSampleAクラス ※抽象クラスを継承した子クラス
public class FunSampleA extends SampleA {
private String os;
private int memory;
// コンストラクタ(引数あり)
public FunSampleA(String os, int memory) {
this.os = os;
this.memory = memory;
System.out.println("OS「" + os + "」メモリサイズ「" + memory
+ "MByte」のパソコンを作成しました。");
}
// 抽象メソッドをオーバーライド
@Override
public void show() {
System.out.println("パソコンのOSは「" + this.os + "」です。");
System.out.println("メモリサイズは「" + this.memory + "MByte」です。");
}
}
SampleAクラスの抽象メソッドであるshow()メソッドをオーバーライドしています。
抽象メソッドを必ずオーバーライドして定義しなければならない事を除けば、
通常のクラスを継承するのと変わりありません。
・Abstractクラス
public class Abstract {
public static void main(String[] args) {
// FunSampleAクラスをオブジェクト化
System.out.println("[FunSampleAオブジェクトの作成]");
FunSampleA com = new FunSampleA("Windows7", 3072);
com.setEpower(350);
System.out.println("---------------------------------------");
// FunSampleAとオブジェクトの情報を表示
System.out.println("■FunSampleA情報を表示");
com.show();
}
}
以下の箇所は、FunSampleAクラスのオブジェクト化を引数ありのコンストラクタにて行っています。
FunSampleA com = new FunSampleA("Windows7", 3072);
次の行では、親クラスから継承したsetEpower()メソッドを利用し、消費電力の値である350を設定しています。
最後に親クラスの抽象メソッドであるshow()メソッドをオーバーライドした
show()メソッドを呼び出して、オブジェクトの情報を表示しています。
実行結果
[FunSampleAオブジェクトの作成]
OS「Windows7」メモリサイズ「3072MByte」のパソコンを作成しました。
消費電力を350Wに設定しました。
---------------------------------------
■FunSampleAの情報を表示
パソコンのOSは「Windows7」です。
メモリサイズは「3072MByte」です。
インターフェース
abstractの他にも、「interface」という仕組みがあります。
先ほどのabstractと考え方は同じですが、「abstract」は継承元として1つしか選べないのに対して、
「interface」は継承元を2つ以上指定できる(多重継承)という特徴があります。
その他の特徴としては
・継承と併用が出来る
・別のインターフェースを継承することが出来る
などが挙げられます。
インターフェースを使用することで
・同一のインタフェースを実装した全てのクラスは、同じ振る舞いをさせる事が可能になる。(標準化)
・通常のクラスや抽象クラスの継承と異なり、インタフェースは多重継承(実装)が可能になる。
・インタフェースは機能の概要だけで本体の処理を持たない為、提供する機能の目的を理解し易くなる。(抽象化)
といったメリットが期待されます。
インターフェースの書式
インタフェースも抽象クラス同様にオブジェクト化できない為、継承して利用する事が大前提になります。
public interface インターフェース名
{
型 フィールド変数名 = 式(値);
戻り値の型 抽象メソッド名(引数);
}
インターフェースを定義する際のルールを以下に示します。
- インターフェースの定義について
・interfaceにはpublicやabstract修飾子が使用可能。
・publicが省略された場合、同じパッケージ内からしかアクセスできない。 - インターフェースのメンバ変数について
・すべての変数は「public static final」が付いている定義と同じになる。
・「public」「static」「final」修飾子のいずれか、もしくは全部を省略する事も可能。
・定数となるため、初期値の設定が必要になる。 - インタフェースのメソッドについて
・全てのメソッドは「public abstract」が付いている定義と同じになる。
・「public」「abstract」修飾子のいずれかもしくは全部を省略が可能。
・static修飾子をつけることはできない。
・メソッドが本体(処理)を持つことができない。
インターフェースの実装
クラスにインタフェースを実装する場合は継承とは異なり「implements」で実装します。
抽象メソッドと同様に抽象メソッドが定義されている場合は、必ずオーバーライドする必要があります。
implementsを使って継承する場合は継承するクラスを「,(カンマ)」区切りで指定することが出来ます。
インタフェースを実装した新しいクラスを作成し、そのクラスの機能を利用できることを確認してみましょう。
「com.cmps.abstractClass」パッケージ内に「Machine」「Computer」を作成しましょう。
・Machineインターフェース
public interface Machine {
// 製造国
String Country = "Japan";
// 抽象メソッド
public void show();
}
このMachineインターフェースは「interfaceキーワード」で定義することでインターフェースになっています。
クラス内には、製造国の情報を保持するフィールド変数Countryと
抽象メソッドであるshow()メソッドの定義をしています。
・Computerクラス ※インターフェースを実装したクラス
// インターフェース(Machine)を実装
public class Computer implements Machine {
private String os;
private int memory;
// コンストラクタ(引数あり)
public Computer(String os, int memory) {
this.os = os;
this.memory = memory;
System.out.println("OS「" + os + "」メモリサイズ「" + memory
+ "MByte」のパソコンを作成しました。");
}
// 抽象メソッドをオーバーライド
public void show() {
System.out.println("パソコンのOSは" + this.os + "です。");
System.out.println("メモリサイズは" + this.memory + "MByteです。");
System.out.println("製造国は" + Country + "です。");
}
}
以下の記述でインターフェースであるMachineを実装しています。
public class Computer implements Machine
クラス内では抽象メソッドであるshow()メソッドをオーバーライドしています。
※抽象クラスは必ずオーバーライドする必要があります。
また、インターフェースで定義されたCountryを参照し、製造国情報を取得しています。
インターフェースを実装することで、フィールド変数は継承して利用できます。
ちなみにコンストラクタやshow()メソッド内で、osやmemoryの前についている「this.」ですが、
this.の後ろに続く変数がフィールド変数であることを明確にするために使用しています。
・Abstractクラス(Mainメソッドを持つクラス) ※これまで記載した内容はコメントアウトしましょう
public class Abstract {
public static void main(String[] args) {
// Computerクラスをオブジェクト化
System.out.println("[Computerオブジェクトの作成]");
Computer com = new Computer("Windows7", 3072);
System.out.println("---------------------------------------");
// Computerオブジェクトの情報を表示
System.out.println("■Computer情報を表示");
com.show();
}
}
以下の箇所にて、Computerクラスのオブジェクト化を引数ありのコンストラクタで行っています
Computer com = new Computer("Windows7", 3072);
また、インターフェース(Machine)の抽象メソッドを
オーバーライドしたshow()メソッドを呼び出し、オブジェクトの情報を表示しています。
今回のプログラムではインタフェースを実装したComputerクラスも、必ず抽象メソッドをオーバーライドして利用しています。
これによりインタフェースも抽象クラスと同じような働きをもつものだと分かります。
ただし、インタフェースのフィールドは全て定数で、メソッドは全て抽象メソッドとなっています。
抽象クラス(SampleA)のように、値を変更できるフィールド変数や、
処理が定義されているメソッドのようなメンバは持たせることができない点に注意しましょう。
抽象クラスとインターフェースの使い分け
これまでの学習でどちらもメソッドの実装を「インタフェースを実装したクラス」や、
「抽象クラスを継承したクラス」に強制的に行わせる機能をもっているため似たような仕組みに見えます。
インタフェースと抽象クラスの機能を再確認してみると、
インタフェースはimplementsキーワードによりクラスに実装されます。
その際クラスはインタフェースで宣言されたメソッド(抽象メソッド)は全て実装し、
フィールド変数(定数)しか継承することできません。
インタフェースはクラスでもないためオブジェクト化は出来ません。
抽象クラスを継承したサブクラスは、抽象クラス内でabstract宣言されたメソッド(抽象メソッド)を全て実装しなければなりません。
また抽象クラス内で実装されている機能(フィールド変数、メソッド)も継承することができます。
抽象クラスはクラスですが、インタフェース同様にオブジェクト化出来ませんが、子クラスに機能を継承させることができます。
こうして見ると抽象クラスは、子クラスにメソッドの実装を強制できる上に、
共通処理となる機能を、子クラスに提供できるので、インタフェースより便利にみえます。
インタフェースと抽象クラスは似た機能を持っていますが、そもそも抽象クラスは根本的に継承の一種であり、
インタフェースは必要機能の宣言と捉えて考えると性質が異なってみえませんか。
これまでの内容を踏まえて、抽象クラスとインタフェースの使い分けを簡単に考えると以下になります。
・ 抽象クラスは複数の派生先クラスで、一部の処理の実装が異なる場合に使用する。
・ インタフェースは特定のクラスを共通の方法で取りまとめたい場合に使用する。
両者の仕組みを理解するにはやや難しいですが、似たような機能を持っていても異なる仕組みだと理解して下さい。
クラスの階層
継承を行う事で親クラスや子クラスといった親子関係が発生し、クラスに階層が出来る事は学習しました。
抽象クラスやインタフェースを利用するとより高度な階層構造を作ることもできるようになります。
多重継承の仕組み
プログラムを作成していく中で、2つ以上のクラスを同時に継承させて子クラスを使いたいという場合が出てくるかもしれません。
このような場合の継承を多重継承(multiple inheritance)と言います。
ただし、Javaでは「クラスを使った多重継承はできない」事になっています。
クラスを使った多重継承は行えませんが、
インターフェースを使用することで多重継承の仕組みを一部実現可能です。
2つ以上のインターフェースを実装する方法
クラスには2つ以上のインタフェースを実装することが可能であると説明しました。
実際に定義する基本構文を以下に示します。
public class クラス名 implements インターフェース名1 , インターフェース名2 …
{
型 フィールド変数 = 式(値);
オーバーライドした抽象メソッド定義;
}
インターフェースを複数実装したい場合、「,」で区切って記述します。
それぞれのインターフェースで定義されている抽象メソッドを必ず実装しなければいけないのは同じです。
実際に試してみましょう。
「Product」クラスを作成しましょう。また「Computer」クラス、「Abstract」クラスに追記しましょう。
・Productクラス ※インターフェース
public interface Product {
// 抽象メソッド
void productShow();
}
このインターフェースでは、抽象メソッドであるproductShow()メソッドを定義しています。
・Computerクラス ※インタフェース(Product、Computer)2つ実装したクラス
// インターフェース(Machine,Product)を実装
public class Computer implements Machine, Product { // Productを追記
private String os;
private int memory;
// コンストラクタ(引数あり)
public Computer(String os, int memory) {
this.os = os;
this.memory = memory;
System.out.println("OS「" + os + "」メモリサイズ「" + memory
+ "MByte」のパソコンを作成しました。");
}
// 抽象メソッドをオーバーライド
public void show() {
System.out.println("パソコンのOSは" + os + "です。");
System.out.println("メモリサイズは" + memory + "MByteです。");
System.out.println("製造国は" + Country + "です。");
}
// 以下を追記
// Productの抽象メソッドをオーバーライド
public void productShow() {
System.out.println("Productクラスの抽象メソッドです。");
}
}
以下の箇所で新たにProductを追記し、インターフェースを実装しています。
「public class Computer implements Machine, Product」
また、Productの抽象メソッド、productShow()メソッドをオーバーライドしています。
・Abstractクラス ※mainメソッドを持つ実行クラス
public class Abstract {
public static void main(String[] args) {
// Computerクラスをオブジェクト化
System.out.println("[Computerオブジェクトの作成]");
Computer com = new Computer("Windows7", 3072);
System.out.println("---------------------------------------");
// Computerオブジェクトの情報を表示
System.out.println("■Computer情報を表示");
com.show();
// 追記
com.productShow();
}
}
実行結果
[Computerオブジェクトの作成]
OS「Windows7」メモリサイズ「3072MByte」のパソコンを作成しました。
---------------------------------------
■Computer情報を表示
パソコンのOSはWindows7です。
メモリサイズは3072MByteです。
製造国はJapanです。
Productクラスの抽象メソッドです。
インタフェースMachineとProductで定義されている抽象メソッドを、
オーバーライドしたshow()メソッドとproductShow()メソッドを呼び出して表示しています。
今回のプログラムでComputerクラスは、2つのインタフェースを実装しています。
そのため、Computerクラスでは両方のインタフェースの抽象メソッドの処理内容を定義しなければなりません。Javaではクラスの多重継承を認めていませんが、
2つ以上のインタフェースを実装することで、メソッド定義(名前、戻り値の型、引数の形式)を多重継承することができます!
練習問題
「com.cmps.abstractClass」パッケージ内に「abstractQuestion」パッケージを作成し、
「AbstractQuestion」クラスを作成し、mainメソッドを配置してください。
動物を表す抽象クラス「Animal」と各動物たちに共通するインターフェースを用いて、
以下のルールを満たすプログラムを記述しましょう。
ルール
1.抽象クラスである「Animal」には以下を含むこと
- フィールド変数:String name
- コンストラクタで名前を受け取る
- 抽象メソッド:void move()
2.インターフェース「Soundable」を作成、以下を含めること
- メソッド:void speak()
3.以下のクラスを定義し、DogとBirdは「Animal」と「Soundable」を継承・実装、Fishは「Animal」を継承すること
- Dog
- Fish
- Bird
4.mainメソッドでは、以下の操作を行うこと
- List<Animal>に、Dog,Fish,Birdのインスタンスを追加
- 拡張for文を使い、すべての動物に「move()」と「speak()」を実行させる
- Soundableを実装しているかどうかは、if文とinstanceof演算子を使用して判定させる
最終的に以下のような出力結果となるように、記述しましょう。
犬が走っています
ワンワン
魚が泳いでいます
鳥が飛んでいます
ホーホケキョ
instanceof演算子
instanceof演算子は、対象のクラスもしくはインターフェースが「指定したクラスのインスタンス」もしくは「指定したインターフェースを実装したクラスのインスタンスである」を判定します。戻り値は、true/falseです。
書き方は以下の通りです。変数名 instanceof クラス名
変数名 instanceof インターフェース名
