究極のJava抽象クラス解説!コード例で学ぶ抽象クラスの基本と実用テクニック
Javaの抽象クラスは、オブジェクト指向プログラミングの中で非常に重要な概念です。
これから我々は抽象クラスの基本的な使い方から、より高度な使い方までを、実際のコード例とその出力結果を交えて解説していきます。
抽象クラスは、同じようなオブジェクトが共通の動作や属性を持つ場合に、その共通部分を一つの「型」として定義するものです。
これにより、コードの再利用性が向上し、保守性も向上しますよ!
抽象クラスの基本
抽象クラスを利用することで、サブクラスに特定のメソッドの実装を強制することができます。
まずは、基本的な抽象クラスとその使用例を見ていきましょう。
ソースコード例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
abstract class Animal { abstract void sound(); } class Dog extends Animal { void sound() { System.out.println("Bow Bow"); } } public class Program { public static void main(String[] args) { Animal a = new Dog(); a.sound(); } } |
出力結果
Bow Bow
上記のコードでは、Animalという抽象クラスを定義し、その中でsound
という抽象メソッドを定義しています。
Dog
クラスはAnimal
クラスを継承しており、sound
メソッドをオーバーライド(再定義)しています。
KindleUnlimited会員であれば、全ての本をご覧頂けます。 StreamAPIを理解すれば、Javaの世界が変わる 第1版
抽象クラスとインスタンス
抽象クラスは、直接インスタンス化することはできません。
では、どうやって抽象クラスのメソッドを利用するのでしょうか?具体的なクラス(上記例ではDog
クラス)をインスタンス化し、そのインスタンスを通じて利用します。
そうですね、抽象クラス自体はインスタンス化できないので、具体的なサブクラスを作り、そこで抽象クラスで定義したメソッドを実装して利用します。
抽象クラスは、サブクラスに一貫したインターフェース(メソッド)を強制するためのものです。
抽象メソッドの特徴
抽象メソッドは、メソッドの宣言(シグネチャ)だけを指定し、実装(本体)は持ちません。
これにより、すべてのサブクラスが一貫したメソッドを持つことが強制され、そのメソッドがどのように実装されるかはサブクラス次第となります。
ソースコード例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
abstract class Vehicle { abstract void startEngine(); } class Car extends Vehicle { void startEngine() { System.out.println("Car engine started"); } } class Bike extends Vehicle { void startEngine() { System.out.println("Bike engine started"); } } public class Program { public static void main(String[] args) { Vehicle car = new Car(); car.startEngine(); Vehicle bike = new Bike(); bike.startEngine(); } } |
出力結果
Car engine started Bike engine started
上記コードでは、Vehicle
という抽象クラスにstartEngine
という抽象メソッドを定義しています。
Car
クラスとBike
クラスはこれをオーバーライドして、エンジンがスタートする様子をそれぞれ異なるメッセージで出力しています。
抽象クラスの進んだ使い方: メソッドチェーン
メソッドチェーンとは、メソッドの戻り値を再度メソッド呼び出しの対象とすることです。
これを抽象クラスと組み合わせることで、様々な形でメソッドチェーンをカスタマイズできます。
ソースコード例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
abstract class ShapeBuilder { abstract ShapeBuilder setColor(String color); abstract ShapeBuilder setDimension(int dimension); abstract void build(); } class CircleBuilder extends ShapeBuilder { private String color; private int dimension; CircleBuilder setColor(String color) { this.color = color; System.out.println("Color set to: " + color); return this; } CircleBuilder setDimension(int dimension) { this.dimension = dimension; System.out.println("Dimension set to: " + dimension); return this; } void build() { System.out.println("Building a " + color + " circle of " + dimension + "cm."); } } public class Program { public static void main(String[] args) { ShapeBuilder circleBuilder = new CircleBuilder(); circleBuilder.setColor("Red").setDimension(5).build(); } } |
出力結果
Color set to: Red Dimension set to: 5 Building a Red circle of 5cm.
上記のコードでは、ShapeBuilder
という抽象クラスを定義しており、そのサブクラスであるCircleBuilder
で具体的な実装を行なっています。
メソッドチェーンを用いることで、1行で複数のメソッドを呼び出し、設定とビルドを同時に行なっています。
抽象クラスのメリット
抽象クラスを使うメリットとして、異なるクラス間で共通のメソッドを強制できる点があります。
これにより、異なるオブジェクトでも共通のインターフェース(メソッド)を持つことが保証されます。
また、抽象クラスを使用することで、一部のメソッドのみをオーバーライド(再定義)することができ、それ以外の共通のロジックは抽象クラスに記述できるので、コードの重複を減らすことができます。
また、関連するクラス群を一つの「ファミリー」としてグルーピングすることもでき、コードの可読性や保守性も向上します。
抽象クラスのメリットを具体的なコードで確認しよう!
抽象クラスの一つの大きなメリットは、サブクラス(具体クラス)に一定の「枠」を提供しつつ、柔軟な拡張を可能にすることです。
具体的なソースコードを見ながら、どのように実装が簡略化できるのか見ていきましょう。
ソースコード例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
abstract class Animal { abstract void makeSound(); void sleep() { System.out.println("The animal is sleeping."); } } class Dog extends Animal { void makeSound() { System.out.println("The dog barks."); } } class Cat extends Animal { void makeSound() { System.out.println("The cat meows."); } } public class Program { public static void main(String[] args) { Animal myDog = new Dog(); myDog.makeSound(); myDog.sleep(); Animal myCat = new Cat(); myCat.makeSound(); myCat.sleep(); } } |
出力結果
The dog barks. The animal is sleeping. The cat meows. The animal is sleeping.
上記のコードには、Animal
という抽象クラスがあり、このクラスを継承したDog
とCat
クラスがあります。
抽象クラスAnimal
では、共通の動作であるsleep()
メソッドを実装し、動物固有の音を出すmakeSound()
メソッドを抽象メソッドとしています。
正解です、サルモリさん!抽象クラスを使用することで、共通のロジック(sleep()
メソッド)をサブクラスに再実装させずに済ませ、独自のロジック(makeSound()
メソッド)だけをサブクラスに実装させることができます。
これにより、コードの再利用性が向上し、保守も容易になります。
抽象クラスを利用する際の注意点
抽象クラスを利用する上で、いくつかの注意点があります。
これらを把握することで、より効果的に抽象クラスを活用し、後々のバグや設計の不具合を避ける手助けになります。
ポイント
抽象メソッドは具体クラスで必ず実装する
抽象クラスのサブクラスは、抽象クラスの抽象メソッドを全て実装しなければならない
抽象クラスにはコンストラクタを定義することができるが、直接呼び出すことはできない
抽象クラスとインターフェースは違う用途で使う
例えば、抽象クラスのサブクラスは、抽象クラスの抽象メソッドを全て実装しなければならない、というのは非常に重要なポイントです。
これを怠ると、コンパイルエラーとなり、アプリケーションが動かなくなります。
確かに、サルモリさん。しかし、これによって一貫した動作が担保され、バグの発生を抑制できるというメリットもあります。
これらのポイントを頭に入れながら抽象クラスを利用すると、より安全で保守性の高いコードを書く手助けになりますよ!
最後に上記の注意点を具体的なソースコードで何を言ってるか見てみましょう。
1. 抽象クラスはインスタンス化できない
以下のコードでは、抽象クラスのインスタンス化を試みていますが、エラーとなりコンパイルが通りません。
1 2 3 4 5 6 7 8 9 10 |
abstract class 抽象クラス { abstract void 抽象メソッド(); } public class メインクラス { public static void main(String[] args) { // これはコンパイルエラーとなります // 抽象クラス ac = new 抽象クラス(); // エラー: 抽象クラスはインスタンス化できません } } |
2. 抽象メソッドは具体クラスで必ず実装する
抽象クラスを継承した具体クラスでは、抽象メソッドを必ず実装しなければなりません。
1 2 3 4 5 6 7 8 9 10 |
abstract class 抽象クラス { abstract void 抽象メソッド(); } class 具体クラス extends 抽象クラス { // 抽象メソッドを必ず実装します void 抽象メソッド() { System.out.println("抽象メソッドを実装しました"); } } |
3. 抽象クラスのサブクラスは、抽象クラスの抽象メソッドを全て実装しなければならない
全ての抽象メソッドが実装されていなければ、コンパイルエラーとなります。
1 2 3 4 5 6 7 8 9 10 11 12 |
abstract class 抽象クラス { abstract void 抽象メソッド1(); abstract void 抽象メソッド2(); } // abstractMethod2が実装されていないのでコンパイルエラーが発生します class 具体クラス extends 抽象クラス { void 抽象メソッド1() { System.out.println("抽象メソッド1を実装しました"); } // エラー: 具体クラスは抽象クラス.抽象メソッド2()を実装する必要があります } |
4. 抽象クラスにはコンストラクタを定義することができるが、直接呼び出すことはできない
抽象クラスにコンストラクタを定義し、具体クラスから呼び出すことはできます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
abstract class 抽象クラス { 抽象クラス() { System.out.println("抽象クラスのコンストラクタが呼び出されました"); } abstract void 抽象メソッド(); } class 具体クラス extends 抽象クラス { 具体クラス() { // 具体クラスから抽象クラスのコンストラクタを呼び出します super(); } void 抽象メソッド() { System.out.println("抽象メソッドを実装しました"); } } |
5. 抽象クラスとインターフェースは違う用途で使う
抽象クラスは「is-a」関係を表現し、基本的な実装を共有するために使用します。一方、インターフェースは「can-do」関係を定義し、オブジェクトの機能を表現します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
interface 飛べる { void 飛ぶ(); } abstract class 鳥 { abstract void 歌う(); } // 具体クラスは複数のインターフェースを実装できますが、1つの抽象クラスしか継承できません class スズメ extends 鳥 implements 飛べる { void 歌う() { System.out.println("スズメが歌っています"); } public void 飛ぶ() { System.out.println("スズメが飛んでいます"); } } |
以上で、抽象クラスについての基本的なコーディング例をもとにした説明を終わります。
まとめ
今回は、「java抽象クラス」について、基本的な用語説明から、具体的なコードの実装、メリット、そして利用時の注意点について詳しく解説しました。
抽象クラスを理解し活用することで、コードの再利用性を向上させ、保守性を担保することができます。
最後まで読んで頂き、ありがとうございました。少しでもお役にたてたなら幸いです!