クラスの基本
クラスとは①
以前は、メソッドについて説明しましたが、それと処理対象となるデータを組み合わせた構造を表すのがクラスになります。メソッドよりも一回り大きな単位の部品であるクラスはオブジェクト指向プログラミングの基礎的な技術になります。
データの扱い
まずは以下のプログラムを見てみましょう。二人の銀行口座のデータを表す変数に値を設定して表示する単純なものです。
//Accounts.java
//二人分の銀行口座データを扱うプログラム
public class Accounts {
public static void main(String[] args){
String firstAccountName = "山田太郎";
String firstAccountNo = "12345";
long firstAccountBalance = 1000;
String secondAccountName = "山田花子";
String secondAccountNo = "67891";
long secondAccountBalance = 200;
firstAccountBalance -= 200;
secondAccountBalance += 100;
System.out.println("■山田太郎の口座");
System.out.println(" 口座名義:" + firstAccountName);
System.out.println(" 口座番号:" + firstAccountNo);
System.out.println(" 預金残高:" + firstAccountBalance);
System.out.println("■山田花子の口座");
System.out.println(" 口座名義:" + secondAccountName);
System.out.println(" 口座番号:" + secondAccountNo);
System.out.println(" 預金残高:" + secondAccountBalance);
}
}
二人分の銀行口座データを6個の変数で表しています。しかし、バラバラに宣言された口座名義、口座番号、預金残高の変数が、1つの銀行口座に関するものであるという関係はプログラム上で表現されていません。
クラス
我々がプログラムを作る際には、現実世界のオブジェクト(物)や概念を、プログラムの世界のオブジェクト(変数)に投影します。以下の図のように考えるのがクラス(class)の基本になります。
クラス
前の項目に基づいて書き直したプログラムを以下に示します。
//AccountTester.java
//銀行口座クラスのテスト
class Account{
String name; //口座名義
String no; //口座番号
long balance; //預金残高
}
public class AccountTester {
public static void main(String[] args){
Account first = new Account();
Account second = new Account();
first.name = "山田太郎";
first.no = "12345";
first.balance = 1000;
second.name = "山田花子";
second.no = "67891";
second.balance = 200;
first.balance -= 200;
second.balance += 100;
System.out.println("■山田太郎の口座");
System.out.println(" 口座名義:" + first.name);
System.out.println(" 口座番号:" + first.no);
System.out.println(" 預金残高:" + first.balance);
System.out.println("■山田花子の口座");
System.out.println(" 口座名義:" + second.name);
System.out.println(" 口座番号:" + second.no);
System.out.println(" 預金残高:" + second.balance);
}
}
このプログラムは、2つのクラスから構成されるという点でこれまでと大きく異なります。それぞれのクラスの概略は以下の通りです。
- Account:銀行口座クラス
- AccountTester:クラス Account をテストするクラス
これまでのプログラムのクラスは、mainメソッドを中心とした構造でした。このプログラムでそれに相当するのが、AccountTester です。
クラス宣言
以下の図に注目してみましょう。これは、クラス Accountが「口座名義、口座番号、預金残高をセットにしたもの」であることを表すための宣言です。先頭の class Account~ が宣言の開始に辺り、このような宣言をクラス宣言(class declaration)と呼ばれます。
{ }の中に置かれているのは、クラスを構成するデータを表すフィールド(field)の宣言です。クラス Accountは、3つのフィールドから構成されています。
- 口座名義を表す String 型の name
- 口座番号を表す String 型の no
- 預金残高を表す long型の balance
クラスとオブジェクト
クラス宣言は、型を宣言するものであって、実体(変数)を宣言するものではありません。クラス Account 型の変数は、以下のように宣言します。
Account first; //山田太郎の口座(クラス型変数)
Account second; //山田花子の口座(クラス型変数)
この宣言で作られるのは、銀行口座クラスの実体ではなく、それを参照するためのクラス型変数(class type variable)になります。
クラスの実体は別に生成する必要があり、配列と同じように生成は、new 演算子を利用して行います。
new Account()
生成の様子を図で表すと以下のようになります。
new演算子によって生成されたクラス型の「実体」をインスタンス(instance)と呼び、生成することをインスタンス化と呼びます。
クラス型の変数とインスタンスは、関連付けが必要です。そのために以下の代入を行います。
first = new Account();
second = new Account();
これで、上記の2つは生成されたインスタンスを参照することになります。
インスタンス変数とフィールドアクセス
クラス Account 型のインスタンスは、口座名、口座番号、預金残高が「セット」になった変数です。その中の特定のフィールドをアクセスするために利用するのがメンバアクセス演算子(member access operator)です。通称はドット演算子ですので、覚えておきましょう。
インスタンス内のフィールドは、インスタンス毎に作られる変数で、インスタンス変数(instance variable)と呼びます。
フィールドの初期化
配列内の個々の構成要素は「既定値」である0で初期化されたようにクラス内の個々のインスタンス変数も、既定値で初期化されます。
問題点
クラスの導入によって、口座のデータを表す変数間の関係がプログラムに実装されましたが、まだ問題があります。
- 確実な初期化に対する無保証…口座インスタンスのフィールドが明示的に初期化されていないので、思いもよらぬ結果を招くことがあります。
- データの保護に対する無保証…このプログラムでは、他から自由に読み書きができるので、現実では他人が口座からお金を下ろせてしまいます。
銀行口座クラス改良
以上を踏まえて改良したプログラムを以下に示します。
//AccountTester2.java
//銀行口座クラスのテストversion2
class Account2{
private String name; //口座名義
private String no; //口座番号
private long balance; //預金残高
//コンストラクタ
Account2(String n, String num, long z){
name = n;
no = num;
balance = z;
}
//口座名義を調べる
String getName(){
return name;
}
//口座番号を調べる
String getNo(){
return no;
}
//預金残高を調べる
long getBalance(){
return balance;
}
//k円預ける
void deposit(long k){
balance += k;
}
//k円おろす
void withdraw(long k){
balance -= k;
}
}
public class AccountTester2 {
public static void main(String[] args){
Account2 first = new Account2("山田太郎", "12345", 1000);
Account2 second = new Account2("山田花子", "67891", 200);
first.withdraw(200);
second.deposit(100);
System.out.println("■山田太郎の口座");
System.out.println(" 口座名義:" + first.getName());
System.out.println(" 口座番号:" + first.getNo());
System.out.println(" 預金残高:" + first.getBalance());
System.out.println("■山田花子の口座");
System.out.println(" 口座名義:" + second.getName());
System.out.println(" 口座番号:" + second.getNo());
System.out.println(" 預金残高:" + second.getBalance());
}
}
データ隠ぺい
新しいクラス Account2 の構造を示したのが、以下の図になります。クラス宣言の内部が大きく3つの部分で構成されているのが分かります。
1.フィールド
フィールドの宣言には、キーワード private が付いています。これがついたアクセス性は、非公開アクセス(private access)となります。非公開アクセスのフィールドは、クラスの外部に対して存在を隠します。
そのため、クラス AccountTester2 のmainメソッドでは、非公開のフィールドにアクセスすることができません。情報を公開するのはクラス側の判断になります。このように、データを外部から隠して不正なアクセスから守ることをデータ隠ぺい(data hiding)といいます。データの保護性、隠ぺい性だけでなく、プログラムの保守性を向上できます。また、private が指定されていないフィールドはデフォルトアクセス(default access)となります。
2.コンストラクタ
この部分はコンストラクタ(constructor)と呼ばれます。形だけ見るとメソッドと似ていますが、以下が異なります。
- クラスと同じ名前である。
- 返却型がない。
3.メソッド
以前書いたメソッドとは異なり、static を付けずに宣言しています。また、フィールドやメソッドのことをメンバ(member)と総称します。