アクセス修飾子とゲッター
せっかく正しく数えている人数を、別のクラスから勝手に書き換えられたら困りますよね。今回はアクセス修飾子でフィールドを守り、ゲッターで必要な値だけを取り出す方法を見ていきましょう。
他のクラスからのアクセス
今回はアクセス修飾子というのが何なのかを分かりやすく説明したいと思います。オブジェクト指向の三大要素の一つである「カプセル化」というのを実現する為に、アクセス修飾子は非常に重要な役割を果たします。
アクセス修飾子と言えば、publicというのがそれです。クラスの前に、
public class クラス名という風につけたり、メソッドの修飾子としても、つけていましたよね。
public static void main(String[] args)アクセス修飾子にはpublic、private、protectedがあります。さらに、何も付けない場合はパッケージアクセス(package-private)になります。「デフォルト」と呼ぶこともありますが、defaultという修飾子を書くわけではありません。
表にするとこんな感じです。
| 指定 | アクセスできる範囲 |
|---|---|
| public | そのクラスにアクセスできる場所から利用可能 |
| protected | 同じパッケージ内と、別パッケージのサブクラスから一定の条件で利用可能 |
| 指定なし | 同じパッケージ内 |
| private | 宣言したクラス内(入れ子のクラスを含む同じトップレベルクラス内) |
ここでは、別々のファイルに書いたHumanクラスとHumanTestクラスを扱います。protectedの詳しい条件は、継承を学んでから確認しましょう。
下に行くほどアクセス制限が強いわけですが、4つもあるとややこしいと思うので、初めはpublic(公開)とprivate(非公開)だけ使えるようになったらいいと思います。つまり、どこからでもアクセスできる(public)のか、それとも、他のクラスからはアクセスできない(private)のか。
このアクセス制限というのが具体的にどういうことなのかを分かりやすく説明するために、以下のクラスを使います。
Human.java
public class Human{
static int count_Human = 0; // メンバ1
String name; // メンバ2
int birthday; // メンバ3
int manpukudo; // メンバ4
Human(String name, int birthday, int manpukudo){ // コンストラクタ1
this.name = name;
this.birthday = birthday;
this.manpukudo = manpukudo;
count_Human++;
}
Human(){ // コンストラクタ2
this("不明", 0, 50);
}
void eat(){ // メンバ5
this.manpukudo += 60;
}
}続けて読んでいただいている方には見慣れたHumanクラスですね。今回はアクセス制限に注目するため、コンストラクタを2つに絞り、前回の食事回数の制限は省略しています。自分で宣言したメンバは5つです。
まず注目してもらいたいのが、staticなフィールドであるcount_Humanです。count_HumanはこのHumanクラスのオブジェクトをこれまでに作った数をカウントしているstaticな変数です。newされる度にカウントが一つ増えるようにコンストラクタ1の中にcount_Human++;を仕込んでいます。
Human.java
Human(String name, int birthday, int manpukudo){ // コンストラクタ1
this.name = name;
this.birthday = birthday;
this.manpukudo = manpukudo;
count_Human++;
}参考:オブジェクトって何? 〔コンストラクタとは〕 〔this( )の意味〕
以下はHumanクラスと同パッケージ内にあるHumanTestクラスです。以下のコードで、Humanオブジェクトを作るたびにcount_Humanの値が増えることが確認できます。
HumanTest.java
public class HumanTest{
public static void main(String[] args){
System.out.println(Human.count_Human); //①
Human human1 = new Human();
System.out.println(Human.count_Human); //②
Human human2 = new Human();
System.out.println(Human.count_Human); //③
}
}①の段階ではまだ一つもHumanオブジェクトは作られていないので「count_Human」は0です。②の段階では1、③の段階では2になります。
さて、このプログラムではcount_HumanはHumanインスタンスを作るたびに1増えるので、作った累計を表します。不要になったインスタンスの数を引いているわけではありません。ところが、やろうと思えばこんなこともできてしまいます。
HumanTest.java
public class HumanTest{
public static void main(String[] args){
System.out.println(Human.count_Human); //①
Human.count_Human = 100;
Human human1 = new Human();
System.out.println(Human.count_Human); //②
Human human2 = new Human();
System.out.println(Human.count_Human); //③
}
}見ての通り、count_Humanに100を代入しています。これでは②の時点でcount_Humanは101になってしまいます。こんなことをされたらHumanオブジェクトの数を表すはずの変数count_Humanの値の整合性が合わなくなってしまいます。
他のクラスから上記のような方法でcount_Humanに勝手におかしな値を代入されるのを防ぐために、アクセス修飾子を使います。
privateなメンバ
現状でcount_Humanのアクセス制限はどうなっているかと言うと、
Human.java
public class Human{
static int count_Human = 0;
String name;
int birthday;
int manpukudo;
//以下省略
}変数count_Humanの宣言部分に「public」も何もついていませんね。ということは、count_Humanのアクセス制限はデフォルト状態です。デフォルトでは同一パッケージ内のクラスからなら普通にアクセス出来てしまいます。
なので、先程のようにHumanTestクラスの中から「Human.count_Human = 100;」とやれば普通にHumanクラスのcount_Humanの値にアクセスし、書き換えられてしまいます。
そんな他のクラスから無理矢理、手を突っ込んでこの変数の値をいじられるような事態を防ぐために、変数count_Humanにアクセス制限をかけてしまいます。
Human.java
public class Human{
private static int count_Human = 0;
String name;
int birthday;
int manpukudo;
//以下省略
}変数count_Humanの宣言部分にprivateというアクセス修飾子をつけました。「private」をつけると、その変数は他のクラスからアクセスできなくなります。つまり、
HumanTest.java
Human.count_HumanとHumanTestクラスから直接アクセスできなくなるんです。書き換える場合も、読み取るだけの場合もコンパイルエラーになります。
これで他のクラスから、
HumanTest.java
Human.count_Human = 100;こんな書き換えはできなくなります。カウンターを更新する場所をHumanクラスの内部に限定できるわけです。
今回のHumanクラスでは、count_Humanを変更する処理はコンストラクタ内にだけ書いてあります。privateでもHumanクラス内からはアクセスできるので、コンストラクタ内で
Human.java
count_Human++;とやるのは全然問題ありません。そのクラス内でいじっているのか、他のクラスから手を突っ込んでいじっているのかの違いをよく理解する必要があります。
繰り返しになりますが、他のクラスから
HumanTest.java
Human.count_Humanとやって変数count_Humanにアクセスするのを防ぐのがアクセス修飾子privateの役割です。
privateなメンバにアクセスするゲッター
これでHumanTestクラスからcount_Humanを直接書き換えることは防げました。ただし、一つ問題が発生します。次のコードは値を読み取るだけですが、これもコンパイルエラーになります。
HumanTest.java
public class HumanTest{
public static void main(String[] args){
System.out.println(Human.count_Human);
Human human1 = new Human();
System.out.println(Human.count_Human);
Human human2 = new Human();
System.out.println(Human.count_Human);
}
}HumanTestクラスでは、count_Humanの値の変化が分かるようにインスタンスを作ってはcount_Humanの値を出力するという処理を行っていました。
count_Humanの値を取得する為に「Human.count_Human」とやっていたわけですが、count_Humanがprivateなメンバになったことで、これもできなくなってしまうんです(コンパイルエラーになる)。
この操作は何もcount_Humanの整合性を崩すような悪さをしているわけではありませんが、privateなメンバには、悪さをしようとしなかろうと、上記の方法ではアクセスできません。
困ったことになりました。Humanオブジェクトの数を正確にカウントできるようにcount_Humanをprivateにしたのに、その為にcount_Humanの値を取り出せないなんて本末転倒とはまさにこのことです。
そこで登場するのがゲッターと呼ばれるメソッドです。
privateなフィールドの値をゲットするメソッド、それがゲッターです。Humanクラスにcount_Humanの値をゲットするゲッターメソッドを作ってあげましょう。
Human.java
public class Human{
private static int count_Human = 0;
String name;
int birthday;
int manpukudo;
// ~コンストラクタ省略~
public int getCount_Human(){
return count_Human;
}
void eat(){
this.manpukudo += 60;
}
}これがゲッターです。ゲッターというのはただのメソッドであって、以下の特徴を持つメソッドをゲッターと呼称しているにすぎず、特殊なメソッドでもなんでもありません。ごく普通のメソッドです(ちなみにコンストラクタというのはメソッドではないので、同じようなニュアンスで捉えないように)。
今回はゲッターを他のクラスから利用できるように、publicを付けます。ゲッターだから必ずpublicにするという決まりはなく、公開したい範囲に応じてアクセス修飾子を選びます。
ゲッターはゲットしたいフィールドの値を戻り値として返します。なのでその戻り値の型をメソッド名の前に書きます。ここではゲットしたいフィールドはcount_Humanなので、intです。
ゲッターメソッドの名前は普通は「getフィールド名」という形にします。フィールド名の頭文字を大文字にするのが普通です。ここでは「getCount_Human( )」としています。
今回のゲッターの中身はreturn count_Human;だけです。値を変更せず、現在の値を戻り値として返します。
これでゲッターの完成です。
と言いたいところなんですが、ちょっと話が混乱するかも知れませんが、おさらいも兼ねてもうひねり。。
count_Humanはstaticなフィールドです。staticなフィールドということはインスタンスにではなくクラスに属するフィールドだと言うことですね。
ところがこのゲッターは今のところ非staticなメソッドです。つまりインスタンスメソッドです。インスタンスメソッドということは、他のクラスからこのゲッターメソッドを呼ぶ時に、
HumanTest.java
Human human1 = new Human();
human1.getCount_Human()という風にインスタンスを作ってから、インスタンスメソッドとして呼ばなければなりません。クラスフィールド(staticなフィールド)はせっかくインスタンスを作っていなくても存在しているフィールドなのに、それをゲットするゲッターがインスタンスメソッドだと、インスタンスがなければそのクラスフィールドにアクセスできないことになります。
伝わりますでしょうか?(ゲッターの説明にstaticを放り込むようなことになって申し訳ないです。しかし賢明な読者様なら乗り越えてくれると信じて強行突破します)
つまり、今回のようにクラス全体の値を取り出すゲッターはstaticにするのが自然です。インスタンスが一つもない状態でも、人数を取得できるようにしたいからです。
ゲッターをstaticにすることで、
HumanTest.java
Human.getCount_Human()という使い方ができるようになるので、インスタンスを作っていなくてもこのメソッドを使えるわけです。というわけで、このゲッターをstaticにしておきましょう。
Human.java
public class Human{
private static int count_Human = 0;
String name;
int birthday;
int manpukudo;
Human(String name, int birthday, int manpukudo){
this.name = name;
this.birthday = birthday;
this.manpukudo = manpukudo;
count_Human++;
}
Human(){
this("不明", 0, 50);
}
public static int getCount_Human(){
return count_Human;
}
void eat(){
this.manpukudo += 60;
}
}これでstaticなフィールドの値をゲットするstaticなゲッターが完成しました。さっそくこれを使ってcount_Humanをゲットしてみましょう。
HumanTest.java
public class HumanTest{
public static void main(String[] args){
System.out.println(Human.getCount_Human());
Human human1 = new Human();
System.out.println(Human.getCount_Human());
Human human2 = new Human();
System.out.println(Human.getCount_Human());
}
}先ほどは、「Human.count_Human」でcount_Humanにアクセスしていた部分を「Human.getCount_Human()」に変えました。
ゲッターはpublicにしてあるので普通に、
HumanTest.java
Human.getCount_Human()として大丈夫です。その結果、戻り値としてcount_Humanの値が返ってくるわけです。Human.javaとHumanTest.javaを同じパッケージに置き、HumanTestのmainメソッドを実行すると、結果はコンソールに表示されます。
0
1
2このように、フィールドをprivateにしてしまって、そのフィールド値を取得する為の(publicな)ゲッターを作ることによって、そのフィールド値は他のクラスから書き換えることはできないが、取得することはできるという状態を作ることができるわけです。
値を更新する処理はクラスの内部で管理し、外部には必要な操作だけを公開する。これがカプセル化の考え方です。privateにするだけで自動的に正しい値になるわけではないので、クラス内部の処理も正しく書く必要があります。
このアクセス修飾子とゲッターの使い方は初心者には、なんとなく回りくどい、まどろっこしい感じがすると思います。
一人で書いているプログラムでも、別のクラスからうっかり値を書き換えることはあります。privateにしておけば、その間違いをコンパイル時に見つけられます。
また、値の管理方法を変更したくなったときも、呼び出す側がゲッターだけを使っていれば、クラス内部の変更の影響を抑えられます。単に「他人にいじられないようにする」ためではなく、自分でも扱いやすいクラスにするための仕組みなんです。
次回は、ゲッターの相方みたいな存在のセッターについて説明します。
質問・コメント
読み込み中この記事についての質問や、学習して気づいたことをお寄せください。
この記事のコメント
旧サイトに寄せられたコメントも当時の投稿内容のまま掲載しています。現在の記事では変更している内容や、旧環境についてのやり取りも含まれます。
コメントを読み込んでいます。