この記事では、Javaにおける高度な継承の概念、抽象クラスとインターフェースの使用方法と違いについて理解することができます。具体的なプログラム例を通じて、これらの概念の実践的な方法を学ぶことができます。
はじめに
この記事のコードをコピペしてEclipseで出力結果を確認してみよう!
高度な継承の基本
Javaの高度な継承では、抽象クラスとインターフェースが重要な役割を果たします。これらを使用することで、より柔軟で再利用性の高いコードを作成できます。
抽象クラス
抽象クラスは、完全に実装されていないメソッド(抽象メソッド)を含むクラスです。抽象クラスは直接インスタンス化できず、必ず継承されて使用されます。
インターフェース
インターフェースは、メソッドの仕様のみを定義し、実装はサブクラスに任せる仕組みです。複数のインターフェースを実装できるため、多重継承の一部を実現できます。
プログラム例1: 抽象クラスを使用した図形の面積計算
Shape.java
// 抽象クラス Shape
abstract class Shape {
// 抽象メソッド
public abstract double calculateArea();
// 通常のメソッド
public void printArea() {
System.out.println("面積: " + calculateArea());
}
}
Circle.java
// Shapeを継承した具体クラス Circle
class Circle extends Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
// 抽象メソッドの実装
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
}
Rectangle.java
// Shapeを継承した具体クラス Rectangle
class Rectangle extends Shape {
private double width;
private double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
// 抽象メソッドの実装
@Override
public double calculateArea() {
return width * height;
}
}
ShapeExample.java
// メインクラス
public class ShapeExample {
public static void main(String[] args) {
Shape circle = new Circle(5);
Shape rectangle = new Rectangle(4, 6);
circle.printArea();
rectangle.printArea();
}
}
出力結果:
面積: 78.53981633974483
面積: 24.0
Shape.javaの解説
抽象クラスの宣言
abstract class Shape {
// クラスの中身
}
abstract
キーワードを使用して、Shapeクラスを抽象クラスとして宣言しています。- 抽象クラスは、直接インスタンス化できないクラスです。
- 抽象クラスは、共通の振る舞いを定義し、サブクラスで具体的な実装を提供するためのテンプレートとして機能します。
抽象メソッドの宣言
public abstract double calculateArea();
abstract
キーワードを使用して、抽象メソッドを宣言しています。- 抽象メソッドは本体を持たず、セミコロンで終わります。
- このメソッドは、各具体的な形状(サブクラス)で必ず実装しなければなりません。
double
型の戻り値を持つことが指定されています。
通常のメソッドの定義
public void printArea() {
System.out.println("面積: " + calculateArea());
}
- これは通常のメソッド(具象メソッド)です。
- 抽象クラス内でも、通常のメソッドを定義できます。
- このメソッドは、
calculateArea()
メソッドを呼び出して面積を計算し、結果を出力します。 - サブクラスは、このメソッドをそのまま継承するか、オーバーライドすることができます。
このコードは、形状を表す抽象的な概念を定義しています。Shape
クラスを継承する具体的な形状クラス(例:円、四角形など)は、calculateArea()
メソッドを実装して、その形状特有の面積計算ロジックを提供する必要があります。これにより、多様な形状に対して統一的なインターフェースを提供しながら、各形状の特性に応じた実装の柔軟性を確保しています。
Circle.javaの解説
クラスの宣言と継承
class Circle extends Shape {
// クラスの中身
}
Circle
クラスを宣言し、extends
キーワードを使用してShape
クラスを継承しています。- これにより、
Circle
はShape
の具象サブクラスとなります。
フィールドの宣言
private double radius;
radius
というプライベートフィールドを宣言しています。- このフィールドは円の半径を表し、カプセル化されています(外部から直接アクセスできません)。
コンストラクタの定義
public Circle(double radius) {
this.radius = radius;
}
Circle
クラスのコンストラクタを定義しています。- コンストラクタは、オブジェクト生成時に呼び出され、初期化を行います。
this.radius = radius;
で、引数で受け取った値をフィールドに代入しています。
抽象メソッドの実装
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
Shape
クラスで宣言された抽象メソッドcalculateArea()
をオーバーライドして実装しています。@Override
アノテーションは、このメソッドが親クラスのメソッドをオーバーライドしていることを明示します。- 円の面積計算公式(π * r^2)を使用して、面積を計算し返却しています。
Math.PI
は円周率πの値を表す定数です。
このコードは、抽象クラス Shape
を継承した具体的な形状クラス Circle
を定義しています。Circle
クラスは以下の特徴を持ちます:
Shape
クラスの抽象メソッドを具体的に実装しています。- 円特有のプロパティ(半径)を持ち、カプセル化しています。
- コンストラクタを通じて初期化が可能です。
- 円の面積を計算する具体的なロジックを提供しています。
この実装により、Circle
オブジェクトは Shape
として扱うことができ、多態性を活用したプログラミングが可能になります。また、Shape
クラスで定義された printArea()
メソッドも継承されるため、円の面積を簡単に出力することができます。
Rectangle.javaの解説
クラスの宣言と継承
class Rectangle extends Shape {
// クラスの中身
}
Rectangle
クラスを宣言し、extends
キーワードを使用してShape
クラスを継承しています。- これにより、
Rectangle
はShape
の具象サブクラスとなります。
フィールドの宣言
private double width;
private double height;
width
とheight
という2つのプライベートフィールドを宣言しています。- これらのフィールドは長方形の幅と高さを表し、カプセル化されています(外部から直接アクセスできません)。
コンストラクタの定義
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
Rectangle
クラスのコンストラクタを定義しています。- コンストラクタは、オブジェクト生成時に呼び出され、初期化を行います。
this.width = width;
とthis.height = height;
で、引数で受け取った値をそれぞれのフィールドに代入しています。
抽象メソッドの実装
@Override
public double calculateArea() {
return width * height;
}
Shape
クラスで宣言された抽象メソッドcalculateArea()
をオーバーライドして実装しています。@Override
アノテーションは、このメソッドが親クラスのメソッドをオーバーライドしていることを明示します。- 長方形の面積計算公式(幅 * 高さ)を使用して、面積を計算し返却しています。
このコードは、抽象クラス Shape
を継承した具体的な形状クラス Rectangle
を定義しています。Rectangle
クラスは以下の特徴を持ちます:
Shape
クラスの抽象メソッドを具体的に実装しています。- 長方形特有のプロパティ(幅と高さ)を持ち、カプセル化しています。
- コンストラクタを通じて両方のプロパティを初期化できます。
- 長方形の面積を計算する具体的なロジックを提供しています。
この実装により、Rectangle
オブジェクトは Shape
として扱うことができ、多態性を活用したプログラミングが可能になります。また、Shape
クラスで定義された printArea()
メソッドも継承されるため、長方形の面積を簡単に出力することができます。
Circle
クラスと比較すると、Rectangle
クラスは2つのパラメータ(幅と高さ)を持つ点が異なりますが、Shape
の抽象概念を具体化するという点では同じ役割を果たしています。
ShapeExample.javaの解説
クラスの宣言
public class ShapeExample {
// クラスの中身
}
ShapeExample
という公開クラスを宣言しています。- このクラスは、プログラムのエントリーポイントとなる
main
メソッドを含みます。
mainメソッドの宣言
public static void main(String[] args) {
// メソッドの中身
}
main
メソッドを宣言しています。これは、プログラムの実行開始点です。public static void
キーワードは、このメソッドが公開された、静的な、戻り値のないメソッドであることを示します。
オブジェクトの生成
Shape circle = new Circle(5);
Shape rectangle = new Rectangle(4, 6);
Shape
型の変数に、Circle
とRectangle
のインスタンスを代入しています。- これは多態性の例です。具体的なサブクラスのオブジェクトを、抽象的な基底クラスの型で参照しています。
Circle
のコンストラクタには半径5
を、Rectangle
のコンストラクタには幅4
と高さ6
を渡しています。
メソッドの呼び出し
circle.printArea();
rectangle.printArea();
- 生成したオブジェクトの
printArea()
メソッドを呼び出しています。 - このメソッドは
Shape
クラスで定義されていますが、実際の面積計算は各サブクラスのcalculateArea()
メソッドで行われます。
このコードは、抽象クラス Shape
とそのサブクラス Circle
および Rectangle
を使用して、多態性を実演しています。重要なポイントは以下の通りです:
- 抽象クラス型の変数で具体的なサブクラスのオブジェクトを参照しています。
- 同じメソッド(
printArea()
)を呼び出していますが、各オブジェクトの具体的な実装に基づいて異なる結果が得られます。 - この設計により、新しい形状(例:三角形)を追加する際に、既存のコードを変更せずに拡張できます。
この例は、オブジェクト指向プログラミングの重要な概念である継承、多態性、抽象化を実践的に示しています。これにより、柔軟で拡張性の高いコードを作成できることがわかります。
プログラム例2: インターフェースを使用した動物の鳴き声
Animal.java
// 動物の行動を定義するインターフェース
interface Animal {
void makeSound(); // 抽象メソッド
}
Dog.java
// Animalインターフェースを実装する具体クラス Dog
class Dog implements Animal {
@Override
public void makeSound() {
System.out.println("ワンワン!");
}
}
Cat.java
// Animalインターフェースを実装する具体クラス Cat
class Cat implements Animal {
@Override
public void makeSound() {
System.out.println("ニャーニャー!");
}
}
AnimalExample.java
// メインクラス
public class AnimalExample {
public static void main(String[] args) {
Animal dog = new Dog();
Animal cat = new Cat();
dog.makeSound();
cat.makeSound();
}
}
出力結果:
ワンワン!
ニャーニャー!
Animal.javaの解説
インターフェースの宣言
interface Animal {
// インターフェースの中身
}
interface
キーワードを使用して、Animal
というインターフェースを宣言しています。- インターフェースは、抽象メソッドの集合を定義するための特殊な型です。
- インターフェースは、クラスが実装すべき契約を定義します。
抽象メソッドの宣言
void makeSound();
makeSound()
という抽象メソッドを宣言しています。- インターフェース内のメソッドは、デフォルトで public と abstract になるため、これらのキーワードは省略されています。
- このメソッドは本体を持たず、セミコロンで終わっています。
- 戻り値の型は
void
で、パラメータはありません。
このコードは、Animal
インターフェースを定義しています。このインターフェースの特徴と重要性は以下の通りです:
- 抽象化:
Animal
インターフェースは、動物の基本的な行動(この場合は音を出すこと)を抽象化しています。 - 契約: このインターフェースを実装するクラスは、
makeSound()
メソッドを必ず実装しなければなりません。 - 多態性: 異なる動物クラスが同じインターフェースを実装することで、多態的な振る舞いが可能になります。
- 拡張性: 新しい動物クラスを追加する際、このインターフェースを実装するだけで、既存のコードとの互換性が保たれます。
- 疎結合: インターフェースを使用することで、具体的な実装からコードを分離し、より柔軟なシステム設計が可能になります。
このインターフェースを使用することで、例えば Dog
クラスや Cat
クラスなど、様々な動物クラスを作成し、それぞれが独自の方法で makeSound()
メソッドを実装することができます。これにより、多様な動物の鳴き声を表現しつつ、統一的な方法でそれらを扱うことが可能になります。
Dog.javaの解説
クラスの宣言とインターフェースの実装
class Dog implements Animal {
// クラスの中身
}
Dog
クラスを宣言しています。implements
キーワードを使用して、Animal
インターフェースを実装していることを示しています。- これにより、
Dog
クラスはAnimal
インターフェースで定義されたすべてのメソッドを実装する義務を負います。
インターフェースメソッドの実装
@Override
public void makeSound() {
System.out.println("ワンワン!");
}
Animal
インターフェースで定義されたmakeSound()
メソッドを実装しています。@Override
アノテーションは、このメソッドがインターフェースのメソッドを実装(オーバーライド)していることを明示します。- メソッドは
public
として宣言されています。インターフェースのメソッドを実装する際は、必ず public にする必要があります。 - メソッド内で、犬の鳴き声(”ワンワン!”)を出力しています。
このコードは、Animal
インターフェースを実装した具体的な Dog
クラスを定義しています。Dog
クラスの特徴と重要性は以下の通りです:
- インターフェースの実装:
Animal
インターフェースを実装することで、Dog
クラスは動物としての契約を満たしています。 - 具体的な振る舞いの提供:
makeSound()
メソッドに具体的な実装を提供しています。これは犬特有の鳴き方を表現しています。 - 多態性の実現: このクラスのインスタンスは、
Animal
型の変数に代入できます。これにより、多態的なコードが書けるようになります。 - カプセル化: インターフェースを実装することで、
Dog
クラスの内部実装の詳細を隠蔽しつつ、必要な機能を公開しています。 - 拡張性: 将来的に
Dog
クラスに新しい機能を追加する際も、Animal
インターフェースとの互換性を保ったまま拡張できます。
このような実装により、例えば以下のようなコードが可能になります:
Animal myDog = new Dog();
myDog.makeSound(); // 出力: ワンワン!
これは、オブジェクト指向プログラミングの重要な概念であるポリモーフィズム(多態性)を示しています。Animal
型の変数で Dog
オブジェクトを参照し、インターフェースで定義されたメソッドを呼び出すことができます。
Cat.javaの解説
クラスの宣言とインターフェースの実装
class Cat implements Animal {
// クラスの中身
}
Cat
クラスを宣言しています。implements
キーワードを使用して、Animal
インターフェースを実装していることを示しています。- これにより、
Cat
クラスはAnimal
インターフェースで定義されたすべてのメソッドを実装する義務を負います。
インターフェースメソッドの実装
@Override
public void makeSound() {
System.out.println("ニャーニャー!");
}
Animal
インターフェースで定義されたmakeSound()
メソッドを具体的に実装しています。@Override
アノテーションは、このメソッドがインターフェースのメソッドを実装(オーバーライド)していることを明示します。- メソッドは
public
として宣言されています。インターフェースのメソッドを実装する際は、必ず public にする必要があります。 - メソッド内で、猫の鳴き声(”ニャーニャー!”)を出力しています。
このコードは、Animal
インターフェースを実装した具体的な Cat
クラスを定義しています。Cat
クラスの特徴と重要性は以下の通りです:
- インターフェースの実装:
Animal
インターフェースを実装することで、Cat
クラスは動物としての契約を満たしています。 - 具体的な振る舞いの提供:
makeSound()
メソッドに猫特有の実装を提供しています。これはDog
クラスとは異なる、猫の鳴き方を表現しています。 - 多態性の実現: このクラスのインスタンスも、
Animal
型の変数に代入できます。これにより、多態的なコードが書けるようになります。 - 一貫性と多様性:
Dog
クラスと同じインターフェースを実装しながら、異なる振る舞いを提供しています。これはインターフェースの強力さを示しています。 - 拡張性: 将来的に
Cat
クラスに新しい機能(例:毛づくろいなど)を追加する際も、Animal
インターフェースとの互換性を保ったまま拡張できます。
このような実装により、以下のようなコードが可能になります:
Animal myCat = new Cat();
myCat.makeSound(); // 出力: ニャーニャー!
これは、Dog
クラスの例と同様にポリモーフィズム(多態性)を示しています。Animal
型の変数で Cat
オブジェクトを参照し、インターフェースで定義されたメソッドを呼び出すことができます。
Dog
クラスと Cat
クラスの実装を比較すると、同じインターフェースを実装しながら、それぞれの動物に特有の振る舞いを提供していることがわかります。これにより、共通の型(Animal
)を使用しつつ、個々の動物の特性を表現することができます。
AnimalExample.javaの解説
クラスの宣言
public class AnimalExample {
// クラスの中身
}
AnimalExample
という公開クラスを宣言しています。- このクラスは、プログラムのエントリーポイントとなる
main
メソッドを含みます。
mainメソッドの宣言
public static void main(String[] args) {
// メソッドの中身
}
main
メソッドを宣言しています。これは、プログラムの実行開始点です。public static void
キーワードは、このメソッドが公開された、静的な、戻り値のないメソッドであることを示します。
オブジェクトの生成と変数の宣言
Animal dog = new Dog();
Animal cat = new Cat();
Animal
型の変数に、Dog
とCat
のインスタンスをそれぞれ代入しています。- これはポリモーフィズム(多態性)の例です。インターフェース型の変数で、そのインターフェースを実装したクラスのオブジェクトを参照しています。
メソッドの呼び出し
dog.makeSound();
cat.makeSound();
- 生成したオブジェクトの
makeSound()
メソッドを呼び出しています。 - このメソッドは
Animal
インターフェースで定義されていますが、実際の実装は各具体クラス(Dog
とCat
)で行われています。
このコードは、Animal
インターフェースとそれを実装した Dog
クラスと Cat
クラスを使用して、ポリモーフィズムを実演しています。重要なポイントは以下の通りです:
- インターフェース型の変数で具体的な実装クラスのオブジェクトを参照しています。
- 同じメソッド(
makeSound()
)を呼び出していますが、各オブジェクトの具体的な実装に基づいて異なる結果が得られます。 - この設計により、新しい動物クラス(例:
Bird
)を追加する際に、既存のコードを変更せずに拡張できます。
この例は、オブジェクト指向プログラミングの重要な概念であるインターフェース、ポリモーフィズム、抽象化を実践的に示しています。これにより:
- 柔軟性: 異なる動物クラスを同じ方法で扱えます。
- 拡張性: 新しい動物クラスの追加が容易です。
- 保守性: インターフェースを変更せずに実装を変更できます。
このような設計は、大規模なシステムや将来の拡張を考慮したプログラミングにおいて非常に有用です。
まとめ
- 抽象クラスは共通の振る舞いと状態を持つ関連クラスのための基本クラスとして使用されます。
- インターフェースはクラスが実装すべきメソッドの契約を定義します。
- 抽象クラスは単一継承のみ可能ですが、インターフェースは多重実装が可能です。
- 抽象クラスはprotectedメンバーを持つことができますが、インターフェースのメンバーはすべてpublicです。
- 抽象クラスは「is-a」関係を表現し、インターフェースは「can-do」関係を表現します。
重要ポイント: 抽象クラスは「何であるか」を、インターフェースは「何ができるか」を定義します。適切に使い分けることで、柔軟で拡張性の高い設計が可能になります。
- 意味: あるものが別のものの一種であることを表す
- 例: 「犬は動物である」
- 特徴:
- 共通の特性と振る舞いを定義
- 単一継承
- 階層構造を表現
- 意味: あるものが特定の能力や行動を持つことを表す
- 例: 「鳥は飛ぶことができる」
- 特徴:
- 特定の機能セットを定義
- 多重実装が可能
- クラス間の疎結合を促進
これらの概念を適切に使用することで、柔軟で拡張性の高いオブジェクト指向設計が可能になります。抽象クラスとインターフェースの特性を理解し、状況に応じて適切に選択することが重要です。
コメント