こんにちは!侍エンジニア インストラクターの本多です。
みなさんは、abstractクラス(抽象クラス)というものをご存知でしょうか?
ルールがいくつかあって使いにくいところもありますが、大規模開発では欠かせないものです。この記事では、Javaのabstractクラスについて
- abstractクラスとは何か
- abstractクラスの使い方
という基本的な内容から、
- abstractクラスの注意点
- interfaceとの違い
といった応用的な内容に関しても解説していきます。今回はabstractクラスについて、わかりやすく解説していきます!
なお、Javaの記事については、こちらにまとめています。
abstractクラスの利点とは
abstractクラスとは、抽象メソッドを1つ以上持つクラスです。まず抽象メソッドは何か?というところから解説します。
抽象メソッドとは実装を持たない、シグネチャ(メソッド名、引数の型、引数の数)と戻り値の型のみを定義するメソッドです。
具体的には、以下の通りです。先頭にabstractとついているところ、引数のカッコのあとに{ } がないところに注目してください。
abstract 戻り値の型名 メソッド名(引数);
次に、抽象クラスです。抽象クラスとは、抽象メソッドを1つ以上持つクラスです。
抽象クラスは下記のように記述します。
abstract class クラス名 { abstract 戻り値の型名 メソッド名(引数); }
抽象クラスで抽象メソッドを持つ利点とは、開発者側にメソッドのオーバーライドを強制するということができることです。
抽象クラスを継承したサブクラスごとに抽象メソッドをオーバーライドして処理内容を記述する必要があります。
しかし、サブクラスごとに処理内容は別々にできます。サブクラスごとに処理内容はまちまちになる、でも必ず処理内容を記述しなければならない、という場合は抽象クラスで抽象メソッドを持つ利点を利用することになります。
抽象メソッドの使い方
抽象メソッドを使うには、抽象クラスを「extends」句で継承する必要があります。
下記のように記述します。
abstract class 抽象クラス名 { abstract 戻り値の型名 メソッド名(引数); } class 派生クラス名 extends 抽象クラス名 { 戻り値の型名 メソッド名(引数) { 処理内容; } }
では、実際に抽象メソッドの使い方を見てみましょう。
abstract class AbstractClass { abstract void abstractMethod(int num, String str); void nonAbstractMethod() { System.out.println("非抽象メソッドより出力"); } } public class Main extends AbstractClass { public static void main(String[] args) { Main main = new Main(); main.abstractMethod(3, "Test"); } public void abstractMethod(int num, String str) { System.out.println("引数int num = " + num + " / 引数String str = "+ str); } }
実行結果:
引数int num = 3 / 引数String str = Test
AbstractClassを継承したMainクラスに実装を記述しています。
抽象クラスの注意点
抽象クラスを使う上で、いくつかルールがあります。
- 直接インスタンス化できない。
- 抽象メソッドは全てオーバーライドしなくてはならない。
- サブクラスでコンストラクタを記述する必要がある
順に見ていきましょう。
直接インスタンス化できない
抽象クラスは、そのままでは使えません(つまり、インスタンス化できません)。
試しに、上記のAbstractClassをインスタンス化してみましょう。
abstract class AbstractClass { abstract void abstractMethod(int num, String str); void nonAbstractMethod() { System.out.println("非抽象メソッドより出力"); } } public class Main extends AbstractClass { public static void main(String[] args) { // 直接インスタンス化してみる。 AbstractClass ac = new AbstractClass(); ac.abstractMethod(3, "Test"); } public void abstractMethod(int num, String str) { System.out.println("引数int num = " + num + " / 引数String str = "+ str); } }
実行結果:
Main.java:13: error: AbstractClass is abstract; cannot be instantiated
このサンプルコードでは、エラーメッセージが表示されます。
AbstractClassは抽象クラスなのでインスタンス化できません。
抽象メソッドのオーバーライド
抽象クラスの抽象メソッドは、必ず全てオーバーライドする必要があります。
先の例のメソッド名を変えて、抽象メソッドをオーバーライドしないように変更します。
abstract class AbstractClass { abstract void abstractMethod(int num, String str); void nonAbstractMethod() { System.out.println("非抽象メソッドより出力"); } } public class Main extends AbstractClass { public static void main(String[] args) { Main main = new Main(); main.abstractMethod(3, "Test"); } // 抽象クラスに無いメソッド名に変更 public void Method(int num, String str) { System.out.println("引数int num = " + num + " / 引数String str = "+ str); } }
実行結果:
Main.java:9: error: Main is not abstract and does not override abstract method abstractMethod(int,String) in AbstractClass
abstractMethodをオーバーライドしていません、とエラーになりました。これが一体何の役に立つのでしょうか?
メリットは、開発者側にメソッドのオーバーライドを強制するということができます。クラスによってロジックはまちまちになる、でも必ず書かなくてはいけない、という場合はこの特性を利用できます。
コンストラクタを使う方法
abstractクラスのコンストラクタを使う方法について説明します。先ほどご説明したとおりabstractクラスは直接インスタンス化できません。
また、サブクラスではスーパークラスのコンストラクタを「super」句を使って継承する必要があります。
サブクラスはインスタンス化できるので、サブクラスをインスタンス化してスーパークラスから継承したコンストラクタを使用します。サンプルコードで確認しましょう。
// スーパークラス abstract class AbstractClass { public AbstractClass(int num, String str) { System.out.println("引数int num = " + num + " / 引数String str = "+ str); } abstract void abstractMethod(int num, String str); void nonAbstractMethod() { System.out.println("非抽象メソッドより出力"); } } // サブクラス class SubClass extends AbstractClass { // サブクラスからスーパークラスのコンストラクタを呼び出し public SubClass(int num, String str) { super(num, str); } public void abstractMethod(int num, String str) { System.out.println("引数int num = " + num + " / 引数String str = "+ str); } } public class Main { public static void main(String[] args) { SubClass sc = new SubClass(3, "Test"); } }
実行結果:
引数int num = 3 / 引数String str = Test
このサンプルコードでは、SubClassクラスは抽象クラスAbstractClassを継承しています。SubClassクラス内で抽象クラスAbstractClassのコンストラクタを「super」句を使って継承しています。
Mainクラス内ではSubClassクラスをインスタンス化して抽象クラスAbstractClassのコンストラクタを使用しています。
なお、継承したクラスのコンストラクタについては、以下の記事にまとめていますので、ぜひ参考にしてくださいね!
インターフェースとの違い
抽象クラスとインターフェースとの違い、使い分けを表一覧にしてみました。
抽象クラス | インターフェース | |
---|---|---|
抽象メソッド | サブクラスで必ず実装 | 抽象メソッドのみ記述 (自動でpublic abstract) |
実装を持つメソッド | 実装可能 | 実装不可(抽象メソッドのみ) (Java8以降、defaultを使って実装可) |
メンバ変数 | クラスのメンバ変数と同じ | 必ず定数(自動でpublic static final) |
多重継承 | 不可(単一継承のみ) | 可能 |
Java7以前は「インターフェースは直接インスタンス化できない」という違いがあったのですが、Java8以降はインターフェースも直接インスタンス化できてしまいます。
サンプルコードで確認しましょう
interface AbstractInterface { default void defaultMethod(){ System.out.println("defaultMethodより出力"); } void abstractMethod(); } public class Main { public static void main(String[] args) { AbstractInterface ac = new AbstractInterface() { public void abstractMethod(){ System.out.println("オーバーライドしたabstractMethodより出力"); } }; ac.abstractMethod(); ac.defaultMethod(); } }
実行結果:
オーバーライドしたabstractMethodより出力 defaultMethodより出力
では、抽象メソッドとインターフェースの違いは何かというと、多重継承ができるかどうかです。
インターフェースは下記のように複数のインターフェースを実装することができます。
class クラス名 implements インターフェース名1, インターフェース名2 { // 処理 }
よってインターフェースは、より柔軟な実装ができます。
ただし、多重継承を使いすぎると複雑になりすぎるというデメリットがあるので、十分注意する必要があります。
クラスについてもっと詳しく知りたい方へ
クラスのさまざまな使い方については、以下の記事にまとめていますので、ぜひ参考にしてくださいね!
interface(インターフェース)の使い方総まとめ
interface(インタフェース)のいろいろな使い方を次の記事にまとめているので、ぜひ確認してください!
まとめ
この記事では、abstractクラスを説明しました。
取り扱いのルールが難しいabstractですが、注意しながら使えば問題ありません。
使い方を忘れた場合、本記事を参考にしてくださいね!