1. 抽象クラスの定義と継承方法
まず、抽象クラスを定義してみましょう。以下は「人間」を表す抽象クラスHuman.javaの例です:
// abstractキーワードを使用して抽象クラスを定義
public abstract class Human {
// 実装済みのメソッド
public void eat(){
System.out.println("人間は食べる");
}
// public修飾子の空実装メソッド
public void run(){}
// デフォルトアクセス修飾子の空実装メソッド
void walk(){}
// protected修飾子のメソッド
protected void sleep(){}
// private修飾子のメソッド
private void read(){}
}
抽象クラスの特徴:
- abstractキーワードで修飾され、抽象メソッドを含めることができます
- すべてのメソッドは実装(空実装を含む)が必要です
- メソッドの修飾子にはpublic、protected、private、またはデフォルト(パッケージプライベート)が使用できます
次に、この抽象クラスを継承するInstructor.javaクラスを作成します:
public class Instructor extends Human {
@Override
public void run(){
System.out.println("講師は走る");
}
@Override
void walk(){
System.out.println("講師は歩く");
}
@Override
protected void sleep(){
System.out.println("講師は寝る");
}
// eatメソッドをオーバーライドする場合
@Override
public void eat(){
System.out.println("講師は食べる");
}
}
抽象クラスの重要な点:
- 抽象クラスは直接インスタンス化できません
- サブクラスは抽象クラスのメソッドを実装してもしなくても構いません
- 実装しない場合、呼び出し時には抽象クラスのデフォルト実装が使用されます
- 抽象クラスには具体的なメソッドやプロパティ(メンバ変数)を含めることができます
- 静的メンバや静的コードブロックも使用できます
2. インターフェースの定義と実装方法
インターフェースは、メソッドや動作の抽象化です。例えば、「教師」という能力を追加するインターフェースを定義してみましょう。
public interface Educator {
// public static finalは省略可能(暗黙的に適用)
int LEVEL = 5;
// 変数は暗黙的にpublic static final
String SUBJECT = "Mathematics";
// メソッドは暗黙的にpublic abstract
void teach();
void evaluate();
// Java 8以降ではdefaultメソッドも使用可能
default void prepare() {
System.out.println("講義の準備をする");
}
}
インターフェースの特徴:
- 変数は暗黙的にpublic static finalとなり、初期化が必要です
- メソッドは暗黙的にpublic abstractです
- private変数は定義できません
次に、このインターフェースを実装するクラスを作成します:
public class EducatorImplementation implements Educator {
@Override
public void teach() {
System.out.println("私は教育者で、教えます");
System.out.println("教育レベル: " + LEVEL);
}
@Override
public void evaluate() {
System.out.println("私は教育者で、評価します");
System.out.println("教科: " + SUBJECT);
}
// defaultメソッドはオーバーライドしなくても良い
}
テストクラスを作成して動作を確認します:
public class InterfaceTest {
public static void main(String[] args){
EducatorImplementation educatorImpl = new EducatorImplementation();
educatorImpl.evaluate();
educatorImpl.teach();
educatorImpl.prepare();
System.out.println("-----------------------------------------------------");
// インターフェース型の変数に実装クラスのインスタンスを代入
Educator educator = educatorImpl;
educator.evaluate();
educator.teach();
educator.prepare();
}
}
インターフェースの重要な点:
- インターフェースの変数はすべてfinalで不変です
- インターフェースを実装するクラスは、すべてのメソッドを実装する必要があります
- インターフェースは直接インスタンス化できませんが、実装クラスのオブジェクトを代入できます
3. 抽象クラスとインターフェースの比較
| 抽象クラス | インターフェース |
|---|---|
| abstractキーワードで修飾 | interfaceキーワードで定義 |
| extendsキーワードで継承 | implementsキーワードで実装 |
| メソッドの一部のみ実装可能 | すべてのメソッドを実装する必要あり |
| メソッドは実装(空実装含む)が必要 | メソッドは宣言のみ(Java 8以前) |
| 通常のメンバ変数や静的コードブロックを持てる | 変数はpublic static finalのみ |
| メソッドはpublic、protected、private、デフォルトアクセス可能 | メソッドはpublicのみ(デフォルトはpublic) |
| 新しいメソッドを追加しても既存コードに影響なし | 新しいメソッドを追加すると実装クラスすべてに影響 |
| 直接インスタンス化不可 | 直接インスタンス化不可 |
| mainメソッドやコンストラクタを持てる | mainメソッドやコンストラクタを持てない |
使用場面のガイドライン:
- Javaは単一継承のみサポートするため、インターフェースは多重継承の代替として設計されました
- 抽象クラスは「is-a(〜である)」関係を表し、基本的な性質と振る舞いを定義します
- インターフェースは「can-do(〜できる)」関係を表し、特定の機能や能力を定義します
- デフォルト実装が必要な場合や、基本機能が頻繁に変更される場合は抽象クラスを使用します
- 単に機能を追加したい場合や、多重継承のような機能を実現したい場合はインターフェースを使用します
- 複雑な階層構造では、抽象クラスとインターフェースを組み合わせて使用すると効果的です