Java
 Computer >> コンピューター >  >> プログラミング >> Java

Javaの抽象クラスとは?abstractクラスと抽象メソッドの基本を実例付きで解説

はじめに:Javaにおける「抽象化」とは

抽象化(Abstraction)は、カプセル化や継承と並ぶ、オブジェクト指向プログラミングの中核となる3大原則のひとつです。

抽象化を活用することで、プログラムの複雑さを抑え、コードを読みやすくすることができます。Javaにおける抽象化では、次の2つのことが可能になります。

  1. クラスの必要な詳細情報だけを外部に見せる
  2. それ以外の詳細な実装内容を隠す

この抽象化は、Javaではクラスメソッドの両方に適用できます。

本記事では、Javaにおける抽象化の基礎知識から、抽象クラス・抽象メソッドの具体的な使い方まで、サンプルコードを交えながら丁寧に解説していきます。

Javaにおける抽象化の基本

オブジェクト指向プログラミングとは、データや処理を「オブジェクト」と「クラス」で表現するプログラミング手法です。オブジェクトとクラスを活用することで、コードの重複を減らし、プログラム全体の効率を高められます。

Javaはオブジェクト指向言語であり、オブジェクトやクラスを扱うためのさまざまな機能が備わっています。そのひとつが抽象化です。抽象化を使うことで、特定のクラスに対して必要な情報だけを公開し、それ以外を隠蔽できます。

Javaで抽象化が使われる場面は、主に「クラス」と「メソッド」の2つです。

Javaの抽象クラス(abstract class)とは

Javaにおいて、抽象クラスとはインスタンス(オブジェクト)を生成できないクラスのことです。つまり、抽象クラスは直接インスタンス化できません。

抽象クラスを作成するには、abstractキーワードを使用します。以下が抽象クラスの基本的な例です。

abstract class BankAccount {
	// ここにコードを記述
}

これは抽象クラスなので、このクラスからオブジェクトを生成しようとすると、プログラムはエラーを返します。

例として、BankAccountというクラスのインスタンスを作成してみましょう。顧客であるLucyの銀行情報を格納するために、lucyAccountというインスタンスを生成したいとします。次のようなコードを書いてみます。

BankAccount lucyAccount = new BankAccount();

しかし、このコードを実行すると、プログラムは次のエラーを返します。

BankAccount is abstract; cannot be instantiated

このように、抽象クラスは直接オブジェクトに割り当てることができません。

ただし、抽象クラスからサブクラス(子クラス)を作ることは可能です。次のセクションでは、抽象クラスからサブクラスを作成する方法を見ていきましょう。

抽象クラスの継承

前述のとおり、抽象クラスにはオブジェクトを割り当てられません。したがって、抽象クラスの内容——つまり、そのクラスが持つ属性やメソッド——にアクセスするには、クラスを継承(inherit)する必要があります。

ここで、銀行の口座情報を管理するプログラムを作成するとします。このプログラムには、BankAccountとCheckingAccountという2つのクラスが含まれます。BankAccountは親クラスとして銀行口座を操作するための属性やメソッドを保持し、CheckingAccountは子クラスとしてそれらを継承します。

この例では、BankAccountを抽象クラスとして定義します。BankAccount自体は直接使わず、当座預金口座(CheckingAccount)の作成だけに利用するイメージです。

BankAccountクラスとCheckingAccountクラスを扱うコードは、次のように書けます。

abstract class BankAccount {
	public void viewAccountNumber() {
		System.out.println("Account number: #1932042");
	}
}

class CheckingAccount extends BankAccount {
}

class Main {
	public static void main(String[] args) {
		CheckingAccount lucyAccount = new CheckingAccount();
		lucyAccount.viewAccountNumber();
	}
}

このコードを実行すると、次の出力が得られます。

Account number: #1932042

この例では、3つのクラスを作成しました。

  1. BankAccount: 抽象クラスです。viewAccountNumber()というメソッドを持っています。抽象クラスであるため、このクラスのオブジェクトを直接生成することはできません。
  2. CheckingAccount: 子クラスです。親クラスであるBankAccountの属性とメソッドを継承しています。
  3. Main: メインプログラムのコードを含むクラスです。

メインプログラム内では、CheckingAccountクラスのインスタンスを生成し、lucyAccountという名前を付けています。lucyAccountはオブジェクトです。3つのクラスを定義した後、lucyAccountオブジェクトに対してviewAccountNumber()メソッドを呼び出しました。

この例の構成要素を整理すると、次の表のようになります。

名前種類説明
BankAccount抽象クラス/親クラス銀行口座を扱うための属性とメソッドを保持する。
CheckingAccount子クラス親クラスのメソッドを継承する。
Mainクラスメインプログラムのコードを含む。
lucyAccountオブジェクトプログラムの実際の利用場面を表す。

Javaの継承についてさらに詳しく学びたい方は、継承に関する専用ガイドもあわせて参照してください。

Javaの抽象メソッド(abstract method)とは

抽象化できるのはクラスだけではありません。メソッドも抽象的にできます。

抽象メソッドを宣言する際にも、先ほどと同じくabstractキーワードを使用します。抽象メソッドには処理内容(本体)を記述せず、宣言できるのは抽象クラスの中だけという点に注意してください。

以下は、Javaにおける抽象メソッドの例です。

abstract void checkBalance();

この例では、checkBalance()という抽象メソッドを作成しました。このメソッドには本体がありません。つまり、メソッドに関連付けられたコードが存在しない状態です。

先ほどの銀行口座の例に戻りましょう。あのプログラムには、lucyAccount.viewAccountNumber();というメソッド呼び出しが含まれていました。

この仕組みにより、プログラムは顧客の口座番号を表示できるようになります。前のセクションの例では、親クラスであるBankAccountが、いわば子クラスのための「テンプレート」のような役割を果たしていました。

この場合の子クラスは、さまざまな種類の銀行口座です。顧客は複数種類の口座を持つ可能性があり、それぞれの口座に対して同じ機能を実行したいことがあるためです。先ほど作成した子クラスはCheckingAccountでした。

もう一度、例に出てきた顧客Lucyについて考えてみましょう。Lucyは当座預金口座を持っていますが、普通預金口座や証券口座も持っているかもしれません。Lucyのすべての口座番号を表示できるようにしたい場合、追加の2つの口座タイプに対応する子クラスを作ればよいのです。たとえば、SavingsAccountやBrokerageAccountといった名前になります。

親クラスBankAccountを一度作ってしまえば、3種類すべての口座タイプの口座番号表示を、コードの重複を最小限に抑えながら効率的に実現できます。

次のコードでは、abstractキーワードを2回使用しています。まず抽象クラス(BankAccount)を宣言し、続いて後ほど顧客の口座番号を表示するために使う抽象メソッドを宣言します。その抽象メソッドの宣言が abstract void viewAccountNumber(); です。その後、CheckingAccount子クラス側でviewAccountNumber()メソッドを実装します。コードは以下のとおりです。

abstract class BankAccount {
	abstract void viewAccountNumber();
}

class CheckingAccount extends BankAccount {
public void viewAccountNumber() {
		System.out.println("Checking account number: #1932042");
	}
}

class Main {
	public static void main(String[] args) {
		CheckingAccount lucyAccount = new CheckingAccount();
		lucyAccount.viewAccountNumber();
	}
}

このコードを実行すると、次の出力が得られます。

Checking account number: #1932042

出力結果は先ほどの例と同じですが、コード上にはいくつか重要な違いがあります。

  1. 抽象メソッドの宣言: この例では、BankAccountという抽象クラスを宣言し、その中にviewAccountNumber()という抽象メソッドを含めています。
  2. 抽象メソッドの実装(拡張): CheckingAccountクラスによってBankAccountクラスを継承し、CheckingAccountクラスがBankAccountの内容を受け継いでいます。

CheckingAccountクラス内では、CheckingAccountオブジェクトからのみアクセスできるviewAccountNumber()メソッドを定義しています。そして、顧客の口座番号を確認するために、CheckingAccountオブジェクトを生成しているわけです。

Javaの抽象化 実践例

ここまで、Javaの抽象クラスと抽象メソッドの両方について解説してきました。最後に、これらを組み合わせた完全な抽象化の例を見てみましょう。これまでの銀行口座の例で説明した内容をすべて統合します。

ある銀行が、当座預金口座と普通預金口座の2種類の口座を提供しているとします。そこで、口座保有者が自分の口座番号を確認できるプログラムを作成することにしました。

この銀行では、当座預金口座の場合は口座番号が顧客IDで始まり555で終わり、普通預金口座の場合は777で終わるというルールになっています。

このプログラムは、次のようなコードで実現できます。

abstract class BankAccount {
	abstract void viewAccountNumber();
}

class CheckingAccount extends BankAccount {
	public void viewAccountNumber() {
		System.out.println("Checking account number: #1932042555");
	}
}

class SavingsAccount extends BankAccount {
	public void viewAccountNumber() {
		System.out.println("Savings account number: #1932042777");
	}
}

class Main {
	public static void main(String[] args) {
		CheckingAccount ellieCheckingAccount = new CheckingAccount();
		ellieCheckingAccount.viewAccountNumber();

		SavingsAccount ellieSavingsAccount = new SavingsAccount();
		ellieSavingsAccount.viewAccountNumber();
	}
}

このコードを実行すると、次の出力が得られます。

Checking account number: #1932042555
Savings account number: #1932042777

この例では、BankAccountというクラスを作成し、その中にviewAccountNumber()という抽象メソッドを1つ定義しました。

口座番号の採番ルールは口座の種類ごとに異なるため、viewAccountNumber()メソッドをBankAccountクラス内で実装することはできません。代わりに、BankAccountクラスの各サブクラスが、それぞれviewAccountNumber()メソッドを実装します。

この例では、CheckingAccountクラスは「顧客IDで始まり555で終わる番号」を口座番号として採番し、SavingsAccountクラスは「顧客IDで始まり777で終わる番号」を採番しています。

コード内では、Ellieという顧客のためにCheckingAccountクラスのインスタンスを生成し、ellieCheckingAccountという名前を付けています。そしてviewAccountNumber()メソッドを呼び出して口座番号を確認すると、次の出力が返されます。

Checking account number: #1932042555

同様に、SavingsAccountクラスのインスタンスとしてellieSavingsAccountを生成し、viewAccountNumber()メソッドを呼び出すと、次の出力が返されます。

Savings account number: #1932042777

まとめ

抽象化は、オブジェクト指向プログラミングにおける重要な機能です。クラスやメソッドの一部の詳細を隠し、本質的に必要な情報だけを公開することを可能にします。Javaで抽象クラスや抽象メソッドを作成するには、abstractキーワードを使用します。

本記事では、具体例を交えながら、抽象化の仕組み、抽象化が有用である理由、そしてJavaで抽象クラス・抽象メソッドを作成する方法を解説しました。これであなたも、プロの開発者のようにJavaの抽象クラスや抽象メソッドを活用できるはずです!

  1. Javaのsuperキーワードとは?使い方とサンプルコードを徹底解説

    JavaのsuperキーワードとはJavaにおけるsuperキーワードは、サブクラスから直接の親クラス(スーパークラス)のメンバーやコンストラクタへアクセスするために使われる重要なキーワードです。主な役割は以下の3つです。親クラスのインスタンス参照:super変数は、直近の親クラスのインスタンスを参照します。親クラスのメソッド呼び出し:superを使うことで、オーバーライドされた親クラスのメソッドを呼び出せます。親クラスのコンストラクタ呼び出し:super()は親クラスのコンストラクタとして機能し、子クラスのコンストラクタ内では必ず最初の行に記述する必要があります。特に、オーバーライドされたメ

  2. Javaの集約(Aggregation)とは?HAS-A関係の基本をわかりやすく解説

    Javaにおける集約(Aggregation)とは、オブジェクト指向プログラミングにおける「HAS-A関係」を表す概念です。あるクラスが、別のクラスのオブジェクトを「持っている(has-a)」という関係性を指します。まずは具体的な例を見てみましょう。集約のコード例public class Vehicle{} public class Speed{} public class Van extends Vehicle { private Speed sp; }この例では、VanクラスがSpeedクラスのインスタンスをフィールドとして保持しています。つまり、「VanはSpeedを持っている(