抽象クラスとは何か?
抽象クラスとは、「設計図の設計図」のようなものである。通常のクラスが「家の設計図」だとすると、抽象クラスは「こういう要素は必ず入れてね」という大まかな設計方針を示すものだと考えるとよい。
例えば、「乗り物」という抽象クラスを考えてみよう。乗り物には必ず「走る」という機能が必要だが、その具体的な方法は乗り物によって異なる。
- 車は4輪で走る
- 自転車は2輪で走る
- 電車は線路の上を走る
このように、「走る」という機能は共通しているが、その実現方法は様々である。抽象クラスは、「走る機能を必ず実装してください」という約束事を定義する役割を持っている。
なぜ抽象クラスを使うの?
抽象クラスを使う主な理由は以下の3つである。
1. 共通のルールを強制できる
- 例えば、すべての乗り物には「走る」機能が必要であることを、プログラム上で強制できる。
2. 共通の機能をまとめられる
- 乗り物の「止まる」機能など、同じように動作する部分は1箇所にまとめて記述できる。
3. プログラムの設計が明確になる
- 「この種類のクラスには、これらの機能が必ず必要です」ということを、コードで表現できる。
実際のコードで見てみよう。
abstract class Vehicle {
// すべての乗り物に必要な「走る」機能
abstract void run();
// すべての乗り物で共通の「止まる」機能
void stop() {
System.out.println("徐々に速度を落として停止します");
}
}
// 車の場合
class Car extends Vehicle {
void run() {
System.out.println("4輪を回転させて走ります");
}
}
// 自転車の場合
class Bicycle extends Vehicle {
void run() {
System.out.println("2輪をペダルで回転させて走ります");
}
}
このように、抽象クラスを使うことで、プログラムの設計をより明確に、より管理しやすくすることができる。特に大規模なプログラムを作る際や、複数人で開発を行う際に、その真価を発揮する。
抽象クラスの基本
それでは、抽象クラスの基本的な特徴と、具体的にどのように使われるのかを見ていこう。特に、普通のクラスとの違いを理解することで、抽象クラスの役割がより明確になるはずだ。
抽象クラスの特徴
抽象クラスは、まず、「abstract」というキーワードを使って定義する。このキーワードは「抽象的な」という意味を持ち、Javaに対して「これは抽象クラスです」と伝える役割を持つ。
abstract class Animal {
String name;
abstract void makeSound(); // 抽象メソッド
}
ちなみに、「abstract」は形容詞で「抽象的な」という意味だ。これは具体的な実装をせず、一般的な概念や構造を定義することを表している。プログラミングでは、抽象クラスやメソッドが持つべき機能や振る舞いの枠組みを定義し、その具体的な実装はサブクラスに委ねるという意味を持つ。
具体的な例で理解する抽象クラス
抽象クラスの理解を深めるため、「料理人」を例に考え、下述のコード例のコメントアウトを参照してみてみよう。
abstract class Chef {
// すべての料理人に共通する動作
void washHands() {
System.out.println("手を洗います");
}
// 料理人によって異なる得意料理の作り方
abstract void cookSpecialty();
}
抽象クラスと普通のクラスの違い
抽象クラスと普通のクラス(具象クラスと呼ぶ)には、決定的な違いがある。
1. インスタンス化できない
- 抽象クラスは直接newできない
- 必ず子クラスを作って使う
2. 抽象メソッドを持てる
- 中身のない方法を定義できる
- 子クラスで必ず実装が必要
3. 継承を前提とした設計
- 必ず誰かに継承されることを想定している
Javaでは、クラス名の先頭は大文字にするという命名規則がある。これは、抽象クラスでも同じだ。Javaの標準的な命名規則では、クラス名はパスカルケース(各単語の先頭を大文字)で記述する。例えば「GameCharacter」や「Vehicle」のように記述する。
抽象クラスの作り方
では次に、実際に抽象クラスを作る方法を詳しく見ていこう。ここでは、抽象クラスを作る際の基本的なルールと、実際のコード例を使って説明していく。
基本的な書き方
抽象クラスを定義する際の基本的な構文は以下のようになる。
abstract class 抽象クラス名 {
// フィールド(変数)
// メソッド(具象・抽象)
}
具体例として、ゲームのキャラクターを作ってみよう。
abstract class GameCharacter {
// フィールド
protected String name; // 同じパッケージ内とサブクラスからアクセス可能
protected int hp; // 同じパッケージ内とサブクラスからアクセス可能
// コンストラクタ
public GameCharacter(String name) {
this.name = name;
this.hp = 100; // 初期HP
}
}
protectedは、同じパッケージ内と継承先のクラスからアクセスできる修飾子だ。抽象クラスでよく使用される。
抽象メソッドの定義方法
抽象メソッドは、中身を持たないメソッドだ。サブクラスで必ず実装しなければならない。
abstract class GameCharacter {
// 前述のコード...
// 抽象メソッド
abstract void attack(); // 攻撃方法は各キャラクターで異なる
abstract void specialMove(); // 必殺技も各キャラクターで異なる
}
抽象メソッドの宣言には、セミコロン(;)を使う。波括弧{}は使わない。これは「中身がない」ことを明示的に表している。
具象メソッドの実装方法
抽象クラスには、通常のメソッド(具象メソッド)も定義できる。これは、すべてのサブクラスで共通して使う処理を書くときに便利だ。
abstract class GameCharacter {
// 前述のコード...
// 具象メソッド
public void showStatus() {
System.out.println("名前: " + name);
System.out.println("HP: " + hp);
}
public void receiveDamage(int damage) {
this.hp -= damage;
if (this.hp < 0) {
this.hp = 0;
}
System.out.println(name + "は" + damage + "のダメージを受けた!");
}
}
具象メソッドは、サブクラスでオーバーライド(上書き)することもできる。その場合、@Overrideアノテーションを使うことで、誤ってメソッド名を間違えた場合にコンパイルエラーとして検出できる。
抽象クラスを使う方法
これまで学んだ抽象クラスの知識を活かして、実際の使い方を見ていこう。ここでは、先ほど作成したGameCharacterクラスを例に、具体的な実装方法を説明する。
抽象クラスの継承の仕方
抽象クラスを継承するには、extendsキーワードを使用する。抽象メソッドには適切なアクセス修飾子を指定する必要がある。publicを使用することで、どのクラスからでもアクセス可能となり、一般的なケースではこれが推奨される。
// 戦士クラス
class Warrior extends GameCharacter {
public Warrior(String name) {
super(name); // 親クラスのコンストラクタを呼び出す
}
@Override
public void attack() {
System.out.println(name + "は剣で攻撃した!");
}
@Override
public void specialMove() {
System.out.println(name + "は必殺剣を繰り出した!");
}
}
super()は親クラスのコンストラクタを呼び出すキーワードだ。コンストラクタの最初に書く必要がある。
オーバーライドの方法
抽象メソッドをオーバーライドする際は、以下の点に注意する必要がある。
// 魔法使いクラス
class Wizard extends GameCharacter {
private int mp; // 魔法使い独自の属性
public Wizard(String name) {
super(name);
this.mp = 50; // 初期MP
}
@Override
void attack() {
System.out.println(name + "は魔法で攻撃した!");
}
@Override
void specialMove() {
if (mp >= 20) {
System.out.println(name + "は大魔法を放った!");
mp -= 20;
} else {
System.out.println("MPが足りない!");
}
}
}
@Overrideアノテーションは必須ではないが、付けておくことで誤って別のメソッドを作ってしまうのを防げる。
実践的な例で学ぶ使い方
実際にキャラクターを作成して使ってみよう。
public class Game {
public static void main(String[] args) {
Warrior warrior = new Warrior("勇者");
Wizard wizard = new Wizard("賢者");
// 両方のキャラクターで同じメソッドが使える
warrior.attack(); // 勇者は剣で攻撃した!
wizard.attack(); // 賢者は魔法で攻撃した!
// 共通メソッドも使える
warrior.receiveDamage(30); // 勇者は30のダメージを受けた!
warrior.showStatus(); // 名前: 勇者
// HP: 70
}
}
このような設計は「ポリモーフィズム(多態性)」と呼ばれ、オブジェクト指向プログラミングの重要な概念の一つだ。異なるクラスのオブジェクトを同じように扱えるという特徴がある。
抽象クラスを使うことで、共通の機能を持ちながらも、それぞれのキャラクターの特徴を活かした実装ができただろう。
以上。