1. 抽象クラスの構文
まず、Java における抽象クラスとは何か、そしてそれがなぜ必要なのかを確認しましょう。LEGO の組み立てキットを思い浮かべてください。共通の部品(たとえば「車輪」「ブロック」)があり、特定のモデル専用の部品もあります。抽象クラスは、具体的なモデルの組み立て手順ではなく、部品の扱い方に関する一般的な指針のようなものです。ブロックは突起と穴でつながり、車輪は軸に取り付け、部品は層状に積み上げられる、といった共通ルールと必須の手順を定義しますが、特定モデルの詳細な組み立ては書きません。具体的なモデルはサブクラスで、共通の指針を受け取り、足りない手順を補います。
Java で抽象クラスとは、直接インスタンス化できないクラス(new AbstractClass() はできない)ですが、そこから継承して「書き残された」メソッドを実装できます。
抽象クラスが必要になる場面:
- 一群のオブジェクトに共通の振る舞いまたは状態があるが、一部のロジックが異なるとき。
- 機能の一部は「デフォルト実装」し、一部はサブクラスに任せたいとき。
- このクラスのインスタンスを直接作成できないようにしたいとき(例:「動物」自体は存在せず、「猫」など具体的な種だけが存在する)。
抽象クラスの宣言方法
簡単です。class の前にキーワード abstract と書きます。
public abstract class Animal {
// フィールド(例: 動物の名前)
protected String name;
// コンストラクタ
public Animal(String name) {
this.name = name;
}
// 抽象メソッド — 宣言だけで、実装はない!
public abstract void makeSound();
// 通常の(実装済み)メソッド
public void sleep() {
System.out.println(name + " は眠っている: Zzz...");
}
}
特徴:
- 抽象クラスには、実装済みメソッドと抽象メソッドの両方を含められます。
- 抽象クラスはフィールド、コンストラクタ、さらには static メソッドも持てます。
- 抽象クラスのオブジェクトを直接生成することはできません:
-
Animal a = new Animal("だれか"); // エラー!
抽象メソッドの宣言方法
抽象メソッドとは本体のないメソッド、つまり波かっこと中身のコードがないものです。キーワード abstract で宣言し、必ずセミコロン ; で終わります。
public abstract void makeSound();
- 抽象メソッドは抽象クラスの内部でのみ宣言できます。
- 抽象クラスを継承するクラスは、すべての抽象メソッドを実装しなければなりません。実装しない場合、そのクラス自身も abstract になります。
2. 抽象クラスの継承: どのように動くか
具体例を見てみましょう。抽象クラス Animal が抽象メソッド makeSound() を持っているとします。これを実装するサブクラス Dog を作成します:
public class Dog extends Animal {
public Dog(String name) {
super(name); // 基底クラスのコンストラクタ呼び出し
}
@Override
public void makeSound() {
System.out.println(name + " は吠える: ワンワン!");
}
}
もうひとつのサブクラス:
public class Cat extends Animal {
public Cat(String name) {
super(name);
}
@Override
public void makeSound() {
System.out.println(name + " は鳴く: ニャー!");
}
}
これらのクラスを次のように使えます:
public class Main {
public static void main(String[] args) {
Animal dog = new Dog("Sharik");
Animal cat = new Cat("Murka");
dog.makeSound(); // Sharik は吠える: ワンワン!
cat.makeSound(); // Murka は鳴く: ニャー!
dog.sleep(); // Sharik は眠っている: Zzz...
cat.sleep(); // Murka は眠っている: Zzz...
}
}
注意:
型が Animal の変数は宣言できますが、生成できるのは具体(非抽象)のサブクラスのインスタンスだけです。
図: 構成イメージ
. Animal (abstract)
/ \
Dog Cat
(makeSound を実装) (makeSound を実装)
3. 抽象クラスとインターフェースはいつ使い分ける?
面接で非常によく聞かれる質問で、重要なポイントです。見ていきましょう。
- 抽象クラス — オブジェクト間で共通の状態(たとえばフィールド)や共通ロジック(実装付きメソッド)があり、振る舞いの「骨組み」を与えて拡張させたいとき。
- インターフェース — 実装や状態なしに、メソッドの集合(契約)だけを定めたいとき。Java 8 以降、インターフェースにも default/static メソッドが導入されましたが、それでもインターフェースは「オブジェクトが何をできるか」を表し、「どのように実現するか」ではありません。
現実世界の例:
「鳥」は抽象クラス:すべての鳥にくちばしと翼があり、飛べます(ただし飛び方はさまざま)。
「飛行可能」はインターフェース:飛べるのは鳥だけではなく、飛行機やスーパーヒーローも!飛び方はそれぞれ違いますが、重要なのは「飛べる」ということです。
4. 実用的な例
例 1: 部分的実装を持つ抽象クラス
たとえば、いろいろな乗り物が登場するゲームを作っているとします。どれも走れますが、その方法は異なります。ただし、すべてに速度、名前、標準的な停止の仕方があります。
public abstract class Transport {
protected String name;
protected int speed;
public Transport(String name, int speed) {
this.name = name;
this.speed = speed;
}
// 抽象メソッド — 実装はサブクラスで行う
public abstract void move();
// 実装済みメソッド
public void stop() {
System.out.println(name + " は停止した。");
}
}
public class Car extends Transport {
public Car(String name, int speed) {
super(name, speed);
}
@Override
public void move() {
System.out.println(name + " は道路を時速 " + speed + " km/h で走る。");
}
}
public class Bicycle extends Transport {
public Bicycle(String name, int speed) {
super(name, speed);
}
@Override
public void move() {
System.out.println(name + " はペダルをこいで時速 " + speed + " km/h で進む。");
}
}
使用例:
Transport car = new Car("Toyota", 120);
Transport bike = new Bicycle("Stels", 25);
car.move(); // Toyota は道路を時速 120 km/h で走る。
bike.move(); // Stels はペダルをこいで時速 25 km/h で進む。
car.stop(); // Toyota は停止した。
bike.stop(); // Stels は停止した。
例 2: パラメータを持つ抽象メソッド
public abstract class Shape {
public abstract double area();
}
public class Circle extends Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}
public class Rectangle extends Shape {
private double width, height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public double area() {
return width * height;
}
}
使用例:
Shape c = new Circle(3);
Shape r = new Rectangle(4, 5);
System.out.println("円の面積: " + c.area());
System.out.println("長方形の面積: " + r.area());
5. 抽象クラスの特性と注意点
- 抽象クラスは任意のアクセス修飾子を使えます: public、protected、private(たとえばフィールド)。
- 抽象メソッドがなくても抽象クラスを宣言できます。基底クラスのインスタンス化を禁止したいときに便利です。
- 抽象クラスを継承するクラスが、抽象メソッドをすべて実装しない場合、そのクラスも abstract として宣言しなければなりません。
- 抽象クラスにもコンストラクタを持てます。サブクラスのオブジェクト生成時に(super(...) 経由で)呼び出されます。
- 抽象メソッドは private にはできません(サブクラスで実装できなくなるため)。
- 抽象クラスは static メソッドやフィールドを含められます。
- 抽象クラスはインターフェースを実装できますが、そのメソッドを自ら実装する義務はありません — サブクラスで実装できます。
表: 抽象クラスとインターフェースの比較
| 抽象クラス | インターフェース | |
|---|---|---|
| キーワード | |
|
| フィールドを持てるか | はい(任意) | Java 8 以降 — static/final のみ |
| 実装付きメソッド | はい | Java 8 以降 — default/static |
| 抽象メソッド | はい | はい |
| 多重継承 | いいえ | はい(複数のインターフェースを実装できる) |
| コンストラクタ | はい | いいえ |
| インスタンスを生成できるか | いいえ | いいえ |
6. 抽象クラスとメソッドでよくあるミス
ミス №1: 抽象クラスのインスタンスを作ろうとする。
もし new Animal("だれか") と書くと、コンパイラはすぐに抽象クラスを直接生成できないことを指摘します。覚えておきましょう。抽象は「骨格」であって「生きた存在」ではありません。
ミス №2: サブクラスで抽象メソッドをすべて実装し忘れる。
基底の抽象クラスの抽象メソッドをひとつでも実装しなければ、そのクラス自身も abstract として宣言しなければならず、そうでなければコンパイルエラーになります。
ミス №3: 抽象クラス以外で抽象メソッドを宣言する。
Java では、通常(非抽象)のクラスに抽象メソッドを宣言することはできません — コンパイラが直ちにエラーにします。
ミス №4: 抽象メソッドを private や static にしようとする。
抽象メソッドは private にできません(オーバーライドできなくなるため)。また static にもできません(静的メソッドはオーバーライドされないため)。さらに final にもできません。final はオーバーライドを禁止するからです。
ミス №5: 抽象メソッドのアクセス修飾子を指定し忘れる。
明示的に修飾子を指定しないと、メソッドはパッケージ可視(package-private)になり、意図と異なる場合があります。
GO TO FULL VERSION