はじめに
Java など、オブジェクト指向言語に触れたとき困惑するものの一つが アクセス修飾子 だと思います。
なんでこんなもん必要なんだ って気持ちのせいで、一向に身につかない...
全部 public でいいじゃん。無駄にアクセスできないエラーが出てくるだけじゃん。
そんな方(過去の自分を含む)へ贈る記事です。
アクセス修飾子ってそもそも何?
とりあえず Java で話をすすめていきます。
アクセス修飾子とは以下みたいなやつですね。
class MyAccess {
public String public_str = "public access OK!"; // 変数に public をつけるとどこからでもアクセスできる
private String private_str = "private access OK!"; // 変数に private をつけるとクラス内でしかアクセスできない
}
public class Main {
public static void main(String[] args) {
MyAccess ma = new MyAccess();
System.out.println(ma.public_str); // public access OK!
System.out.println(ma.private_str); // エラー。読み取れない。
ma.public_str = "public rewrite!"; // 書き換えることもできる
ma.private_str = "private rewrite!"; // エラー。書き換えも出来ない
}
}
まず、public_str
とか private_str
などの、クラス内にある変数のことをフィールドと言います。
アクセス修飾子は、そのフィールドの前につけることで、そいつをクラスの外部から触れられるかどうかをコントロールできる機能です。
public
をつければ、そいつはどこからでもアクセスできる状態に。
逆に、private
をつけるとそのクラスの中でしかアクセスできなくなってしまいます。
そのため、上の例では private_str
の書き換えや読み取りの部分でエラーが起きているわけですね。
このように、アクセス修飾子をつけるとそのフィールドの読み取りや書き変えができる範囲を制限できるわけです。
ちなみにメソッドやクラス自体にもつけられますが、今回はフィールドに重点を置いて話します。
他にもいくつかアクセス修飾子の種類があるのですが、この記事はとりあえずアクセス修飾子というものの必要性を理解してもらうためにわかりやすい public と private に限って話します。
必要性さえ理解できれば、他のアクセス修飾子も理解がスムーズになると思います。
アクセスを制限できると何が嬉しいの?
バグが減らせる
さて、フィールドにアクセスできる範囲を制限できると何が嬉しいのでしょうか?
先ほどの例だと、無駄にエラーが出やすくなっているだけな気がします。
確かに、実際アクセス修飾子を使うとエラーが増えますが、これは喜ばしいことです。それはなぜか?
アクセス制限のうれしさを話す前に、まずはバグとエラーの違いをしっかり理解する必要があります。
バグとエラーの違いについて
バグとエラーの両者が混合しているせいで、アクセス修飾子つけたらバグ増えるじゃん!という風になり、混乱しているかもしれません。
ですが、全く別物なので注意しましょう。
例えば、「金額を入力されたら、消費税10%を上乗せして出力するコードを書け」という課題が出たとします。
このとき、「金額を入力されたら、消費税15%を上乗せして出力するコード」というバグがあるコードを書いてしまい、気づかずにそのまま提出してしまったら減点でしょう。
では、消費税が10%以外ならエラーが起きるようにコードを設計したらどうでしょうか。
もし15%にしたらエラーが起きるので、バグに気づかずそのまま提出してしまう事を避けられると思いませんか?
このように、エラーはバグが起きていることを教えてくれる存在なのです。(当然、課題に限らず実製品でも同じことが言えます)
さあ、アクセス修飾子を使ってバグのあるコードをエラーとして教えてもらえるようにしてやりましょう。
書き換えられなくしてバグを防ぐ
さて、よそから書き換えられたくない!っていうフィールドを private
にすることで触れなく出来るのは大きなメリットです。
意図しない書き換えによって、バグが発生するのを防げますからね。
例えば、Calc クラスを作って消費税計算をできるコードを作ってみましょう。仕様として、消費税率は10%固定とします。
以下は悪い例ですね。
class Calc {
public double tax = 0.1;
public double calc(double price) {
return price * (1 + tax);
}
}
public class Main {
public static void main(String[] args) {
Calc calc = new Calc();
double price = 1000.0;
double priceWithTax = calc.calc(price);
System.out.println("Prive with tax: " + priceWithTax); // 1100
}
}
現状は問題なく機能はするのですが、これでは main
内で tax
フィールドが書き換えられてもエラーが出ません。
仕様では10%固定なのにこれはまずいですね。こうなると書き換えに気づけないため、バグの温床になってしまいます。
ここで tax
を private
にしてしまえば、書き換えられることがなくなります!
これでバグらなくなりますね!
わー...うれしい...?
そんなミスしなくね?
まあ確かに。私もそう思います。
個人的には、ミスを防ぐというよりも、main 内では tax の書き換えによるバグは絶対に起きないっていう確信を持てることのほうが重要だと思っています。
実際、上のような状況で書き換えてバグが起きることは多分あんまり無いです。
しかし、開発が進んでいき main
が 1000行くらいに渡るコードになったとき、計算結果がおかしくなるバグが起きたとしましょう。
そのとき、「tax
フィールドの書き換えなんかしないので、それが原因ってのはあり得ないです」って言い切れますか?
まあ、自分一人なら言い切れるかもしれませんね。
ただ共同開発なんかしてたら、疑いたくなっちゃいませんかね?確信は持てない...
ここで tax
フィールドを private
にしておけば...絶対ないって確信を持てます!
「もしかして、main
のどっかで tax
フィールド書き換えるコード書いちゃったかな?」とか不安になることは絶対ありません!
どっかで書き換えてるのか?って1000行のコードをだらだら読まずに済みます。
他の原因を探すことに注力できるのです。これはでかい。
コードが読みやすくなる
アクセス修飾子があるだけで、コードの見方が変わります
例えば、あなたはあるプロジェクトに新しく入ってきたとしましょう。
そして初めて以下のコードを見ました...このクラスはどのように扱えばいいでしょうか?
class Calc {
private double tax = 0.1;
private double discount = 0.5;
public double calc(double price) {
double priceWithDiscount = calcDiscount(price);
double priceWithTax = calcTax(priceWithDiscount);
return priceWithTax;
}
private double calcDiscount(double price) {
return price * (1 - discount);
}
private double calcTax(double price) {
return price * (1 + tax);
}
}
よく見てください。public
になっているメソッドは calc
しかありません!
じゃあこれは calc
だけ触ればいいんだなってわかります
public class Main {
public static void main(String[] args) {
Calc calc = new Calc();
double price = 1000.0;
double result = calc.calc(price);
System.out.println("Prive: " + result); // 550
}
}
private
になっているものは気にする必要が無いのです!
このように、クラスを外部から使うだけなら private
な奴らを気にする必要が無いのもとても便利な特徴です。
クラスの内部向けのごちゃごちゃした処理を隠し、外部向けのメソッドだけ触れるようにする。
こうすれば、そのクラスを使う人は内部のごちゃごちゃを理解せずとも、そのクラスを安心して使えるというわけです。
テレビのリモコンがあるとして、その中身の構造などわからなくてもボタンを押せば使えるようなものです。
でもここで、テレビのリモコンに「照射する赤外線の量」を変えるボタンがついていたらどうですか?
「え~赤外線ってどんくらいの強さがいいんだろう...調べないと...」ってなりますよね?面倒です。
そんなボタンはいらないから、無駄にいじれるようにしないで隠しておけばいいのです。
public
と private
の関係も同じです。外部でいじる必要が無いものは隠しておきましょう。
ゲッターやセッターについて
プライベートにするメリットがわかったでしょうか?
さて、アクセス修飾子について学ぶと、ゲッターやセッターを使いましょうとか言ってくると思います。
せっかくアクセスできないようにしたフィールドにアクセスできるようにする意味の分からない機能のように見えますね。
しかし、これはアクセス修飾子の機能をより活かすとってもすばらしい実装です。
これも、実際に使う場面を考えれば理解できると思います。
ゲッターとは?
ゲッターというのは、private なフィールドの読み取り(get)を可能にするものです。
これは、クラス内ならアクセスできるという性質を活かし、クラス内のメソッドで値を返すようにすることで実現できますね。
class User {
private String name = "Remi";
public String getName() {
return name; // 内部ならアクセスできる!
}
}
public class Main {
public static void main(String[] args) {
User user = new User();
System.out.println(user.getName()); // Remi
}
}
セッターとは?
反対にセッターは、書き込み(set)を可能にすることですね。
同じくメソッドで実現できます。
class User {
private String name = "Remi";
public String setName(name: String) {
this.name = name; // 内部ならアクセスできる!
}
}
public class Main {
public static void main(String[] args) {
User user = new User();
user.setName("John"); // メソッドによって書き換え可能!
}
}
ゲッターはどう役立つか?
例えば、先ほどの消費税計算機に対し、計算結果とともに消費税率を表示するという機能を追加したいと要望が来たとします。
さて、困りました。private
にしているので読み取りも書き込みもできません。でも public
にしてしまったら書き換えることも可能になってしまいます...
ここでゲッターが使えます!
class Calc {
private double tax = 0.1;
// 消費税率を返すメソッド(ゲッター)
double getTax() {
return tax;
}
public double calc(double price) {
return price * (1 + tax);
}
}
public class Main {
public static void main(String[] args) {
Calc calc = new Calc();
double price = 1000.0;
double priceWithTax = calc.calc(price);
System.out.println("Tax: " + calc.getTax()); // tax が読み取れた!
System.out.println("Price with tax: " + priceWithTax);
}
}
クールですね。これにより、書き込みはできないけど読み取りはできるようになりました!
セッターはどう役立つか?
ではセッターはどういう場面で役立つでしょう?
例えばフランス(消費税率20%)と日本(消費税率10%)の税率を切り替えられるようにしたいという要望が来たとします。
これまた困ります。private
だと書き換えられません。
「てかもう public
でよくね?書き込みも読み取りもするんだしさ?」
いや...でもそうすると 20% と 10% 以外に書き換えれちゃうからなぁ...なんとか制限してやりたい...
というわけで以下のような限定的なセッターを作りましょう。
class Calc {
private double tax = 0.1;
public double calc(double price) {
return price * (1 + tax);
}
// 消費税率を返すメソッド(ゲッター)
double getTax() {
return tax;
}
// 日本の税率をセットするセッター
void setJapanTax() {
tax = 0.1;
}
// フランスの税率をセットするセッター
void setFranceTax() {
tax = 0.2;
}
}
public class Main {
public static void main(String[] args) {
Calc calc = new Calc();
double price = 1000.0;
calc.changeTaxJapan(); // 日本の税率をセット!
double priceInJapan = calc.calc(price);
System.out.println("Price in japan: " + priceInJapan);
calc.changeTaxFrance(); // フランスの税率をセット!
double priceInFrance = calc.calc(price);
System.out.println("Price in France: " + priceInFrance);
}
}
ナイスです。セッターの何がいいって、代入する際にいろいろと縛りを入れられるところなんですよね。
これにより、main からは tax を 0.1 と 0.2 以外には変えることが出来なくなります。
まとめ
- バグを抑えられる...というより、意図しない代入によるミスは無いという確信が持てる!
- コードの見通しが良くなる
- ゲッターは、読み取りだけを出来るようにできてとっても便利
- セッターは、書き込みに縛りを入れられるのでとっても便利