2019/02/06
sponsored link
他のクラスからのアクセス
今回はアクセス修飾子というのが何なのかを分かりやすく説明したいと思います。オブジェクト指向の三大要素の一つである「カプセル化」というのを実現する為に、アクセス修飾子は非常に重要な役割を果たします。
アクセス修飾子と言えば、publicというのがそれです。クラスの前に、
public class クラス名
という風につけたり、メソッドの修飾子としても、つけていましたよね。
public static void main(String[] args)
「public」の他に「private」と「protected」と、もう一つ、デフォルトってのがあります。デフォルトっていうのは何かと言うとアクセス修飾子を何もつけていない状態です。何もつけていなかったらアクセス制限について何も規定していないのではなく、何もつけていないなりのアクセス制限がかけられることになります。
表にするとこんな感じです。
public | どこからでもアクセス可能 |
---|---|
protected | 同一パッケージ内のクラス、継承したクラスからのみ可能 |
デフォルト | 同一パッケージ内のクラスからのみ可能 |
private | そのクラスからのみ可能 |
下に行くほどアクセス制限が強いわけですが、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クラスです。Humanクラスにはご覧のように5つのメンバと2つのコンストラクタがあります。
まず注目してもらいたいのが、staticなフィールドであるcount_Humanです。count_HumanはこのHumanクラスのオブジェクトを作られた数をカウントしているstaticな変数です。newされる度にカウントが一つ増えるようにコンストラクタ1の中にcount_Human++;を仕込んでいます。
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増えるし、それ以外で変化することはないので、その値はHumanオブジェクトの数と等しくなります。が、やろうと思えばこんなこともできてしまいます。
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」をつけると、その変数は他のクラスからアクセスできなくなります。つまり、
Human.count_Human
とできなくなるんです。privateな変数にこれをやるとコンパイルエラーになるので鉄壁のガードです。
これで他のクラスから、
Human.count_Human = 100;
こんな横暴はできなくなるので、Humanオブジェクトの数を表すcount_Humanの整合性が崩れることはありません。一件落着です。
count_Humanの値が書き換えられるのはHumanオブジェクトを作った時だけということになります。ちなみにprivateなメンバにもそのクラス内からなら自由にアクセスできるので、Humanクラスのコンストラクタ内で
count_Human++;
とやるのは全然問題ありません。そのクラス内でいじっているのか、他のクラスから手を突っ込んでいじっているのかの違いをよく理解する必要があります。
繰り返しになりますが、他のクラスから
Human.count_Human
とやって変数count_Humanにアクセスするのを防ぐのがアクセス修飾子privateの役割です。
privateなメンバにアクセスするゲッター
これで、count_Humanはprivateなメンバになったので、HumanTestクラスからアクセスできなくなりました。これで整合性は保てるようになったのですが、一つ問題が発生します。
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 取得したいフィールド;」だけです。ただ単にそのフィールドの値を返すだけのメソッドにします。ここでは、return count_Human;としています。
これでゲッターの完成です。
と言いたいところなんですが、ちょっと話が混乱するかも知れませんが、おさらいも兼ねてもうひねり。。
count_Humanはstaticなフィールドです。staticなフィールドということはインスタンスにではなくクラスに属するフィールドだと言うことですね。
ところがこのゲッターは今のところ非staticなメソッドです。つまりインスタンスメソッドです。インスタンスメソッドということは、他のクラスからこのゲッターメソッドを呼ぶ時に、
Human human1 = new Human(); human1.getCount_Human()
という風にインスタンスを作ってから、インスタンスメソッドとして呼ばなければなりません。クラスフィールド(staticなフィールド)はせっかくインスタンスを作っていなくても存在しているフィールドなのに、それをゲットするゲッターがインスタンスメソッドだと、インスタンスがなければそのクラスフィールドにアクセスできないことになります。
伝わりますでしょうか?(ゲッターの説明にstaticを放り込むようなことになって申し訳ないです。しかし賢明な読者様なら乗り越えてくれると信じて強行突破します)
つまり、何が言いたいかといえば、staticなフィールドの値をゲットするゲッターメソッドはstaticにするべきだということです。
ゲッターをstaticにすることで、
Human.getCount_Human()
という使い方ができるようになるので、インスタンスを作っていなくてもこのメソッドを使えるわけです。というわけで、このゲッターをstaticにしておきましょう。
Human.java
public class Human{
private static int count_Human = 0;
String name;
int birthday;
int manpukudo;
// ~コンストラクタ省略~
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にしてあるので普通に、
Human.getCount_Human()
として大丈夫です。その結果、戻り値としてcount_Humanの値が返ってくるわけです。実行結果はこうなります。
このように、フィールドをprivateにしてしまって、そのフィールド値を取得する為の(publicな)ゲッターを作ることによって、そのフィールド値は他のクラスから書き換えることはできないが、取得することはできるという状態を作ることができるわけです。
つまり整合性を保ちながらも、その値を引っ張り出すことはできる状態です。
このアクセス修飾子とゲッターの使い方は初心者には、なんとなく回りくどい、まどろっこしい感じがすると思います。
実際、1人でプログラムを組んでいる限り、publicにしているからと言って誰かが勝手にその値を書き換えたりする危険にさらされるわけではありません。
Human.count_Human = 100;
こんな悪さをされないように、とは言うものの、するのは自分以外いないのですから、それを防ぐ為に策を講じるというのもなんか間抜けな話です。
しかし、オブジェクト指向というプログラミング方法を実現する為にはこのアクセス制限という考え方が非常に重要なポイントになってきます。オブジェクト指向というものを感じる為にも、この一見まどろっこしいprivate&ゲッターをよく理解して使うようにして下さい。
次回は、ゲッターの相方みたいな存在のセッターについて説明します。
今だけ→転職できなければ全額返金の「エンジニア転職保証コース」
絶対エンジニアになる!→テックエキスパート
フリーランスエンジニアの収入例を見てみる→レバテックフリーランス
コメント
それを防ぐにアクセス修飾子を使って→それを防ぐのに
この変数の値をいじられるような事態をを防ぐために、→事態を防ぐために、
以下はHumanテストと同パッケージ内にあるHumanTestクラスです。←何かが間違っているような
by あさやん 2015/11/16 22:26
staticの説明の部分、大変参考になりました。
とある事情で、インスタンス化することができないクラスのメンバ変数にアクセスしなければならず「static public変数にするしかないのかな?」と悩んでおりました。
ありがとうございました!
by Pi 2016/04/08 17:17
めっちゃ難しいです!
私のこの頭でオブジェクト指向をマスターするには、ここが壁と思って頑張ります!!
by あらきん 2018/09/09 12:45