public フィールドがなぜ問題になるのか
public フィールドは
「この変数は、どこからでも読み書きしていいです」
と世界中に宣言しているのと同じです。
一見「楽」「コードが短い」で済みますが、その代わりに
オブジェクトが自分の状態を守れなくなる
どこで値が変わったか追えなくなる
後から設計を変えにくくなる
というツケを確実に払うことになります。
オブジェクト指向のキモは「カプセル化=中身を隠す」です。
public フィールドは、そのカプセルをパカッと開いて、中身をむき出しにしている状態です。
典型例で見る「public フィールドの危険さ」
public フィールドだけのクラス
よくある例から見てみます。
public class User {
public String name;
public String email;
public int point;
}
Javaこのクラスは、どこからでもこう書けます。
User u = new User();
u.name = null;
u.email = "aaa";
u.point = -100;
Javaもし業務的には
名前は必須
メールは「@」を含む形式
ポイントは 0 以上
のようなルールがあったとしても、public フィールドはそれを一切守ってくれません。
「ルールを破った状態のオブジェクト」がシステム中に簡単に生まれてしまいます。
この瞬間に、
このクラスは、自分の一貫性を自分で守ることをあきらめた
と言ってもいいレベルです。
どこで壊れたか分からなくなる
例えばプロジェクトのどこかで User をこう使っていたとします。
u.point -= 500; // どこかの処理
...
u.point = 0; // 別の処理
...
u.point *= 2; // さらに別の処理
Javaある日、「ポイントがマイナスになっている」と気づいたとき、
どの行が悪さをしたのかを探すのは地獄です。
フィールドが public だと、「書き換える入口」がプロジェクト中に無限に増えます。
結果、バグの原因を特定するときに、あちこちのコードを追いかけないといけません。
カプセル化と不変条件を壊してしまう(重要)
本来オブジェクトは「自分のルールを知っている」
オブジェクト指向の基本は、
データ(状態)と、そのデータに対する振る舞い(メソッド)を一体にする
そのクラスが「自分に関するルール」を知っていて、守る
という考え方です。
User なら、本来はこうあるべきです。
名前は必ず設定されている
メールは常に正しい形式
ポイントはマイナスにならない
これらは「User としての不変条件」です。
本当は User のコンストラクタやメソッドでチェックされるべきです。
public フィールドにすると、その責任がどこかへ消えてしまいます。
誰でも、いつでも、どんな値でも押し込める。
「おかしな状態にしないでね」という“人間のマナー”に頼るしかなくなります。
設計として一番弱いパターンです。
不変条件を守れないクラスは、長期的に扱いづらい
不変条件を守れないクラスは、
使うたびに「今は正しい状態だっけ?」と疑ってかからないといけない
どこか別の場所で状態を書き換えられている可能性を常に考えないといけない
という、信用できない存在になります。
逆に言うと、
コンストラクタとメソッドの使い方さえ守れば、常に正しい状態
と言い切れるクラスは、長期的にものすごく扱いやすいです。
public フィールドは、その「信用」の土台を自分から捨てています。
public フィールドが将来の変更を縛る理由
一度 public にすると「API」になってしまう
public フィールドは、クラスの外から直接アクセスされます。
user.nameuser.point
これを一度やってしまうと、そのフィールド名や型を変えた瞬間、
それを使っている全てのコードがコンパイルエラーになります。
例えば、後から「ポイントは Money クラスで持ちたい」と思っても、
public Money point;
Javaに変えると、プロジェクト中の user.point を全部修正することになります。
これはかなり重い変更です。
もし最初からフィールドを隠し、メソッド越しに扱っていれば、
public int getPoint();
public void addPoint(int added);
Javaなどを
public Money point();
public void addPoint(Money added);
Javaのように比較的スムーズに変えられます。
public フィールドは、後から設計を変える自由度を自分で削っています。
「本当は隠したい事情」も外に漏れる
実装の都合で、一時的に「フラグ」や「キャッシュ」をフィールドに持つことがあります。
public class User {
public boolean loadedFromCache; // つい public に…
}
Javaこういうフィールドを public にしてしまうと、
外側のコードがそれに依存し始める
ユーザに関係ない内部の事情が、外部 API に染み出す
という問題が出てきます。
本来隠しておきたい「中の事情」が public を通じて外部契約になってしまうと、
後からその実装をガラッと変えたいときに苦しくなります。
「public フィールドの代わりに何を使うか」の具体例
private フィールド+ルール付きメソッド
さきほどの User を、「自分の状態を守れる形」に書き直してみます。
public final class User {
private String name;
private String email;
private int point;
public User(String name, String email, int point) {
setName(name);
setEmail(email);
setPoint(point);
}
public String name() {
return name;
}
public String email() {
return email;
}
public int point() {
return point;
}
public void changeName(String newName) {
setName(newName);
}
public void changeEmail(String newEmail) {
setEmail(newEmail);
}
public void addPoint(int added) {
if (added < 0) {
throw new IllegalArgumentException("マイナス加算は禁止");
}
int result = this.point + added;
if (result > 10_000) {
result = 10_000;
}
this.point = result;
}
private void setName(String name) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("名前は必須");
}
this.name = name;
}
private void setEmail(String email) {
if (email == null || !email.contains("@")) {
throw new IllegalArgumentException("メール形式が不正");
}
this.email = email;
}
private void setPoint(int point) {
if (point < 0) {
throw new IllegalArgumentException("ポイントは0以上");
}
this.point = point;
}
}
Javaここでは
フィールドはすべて private
変更は意味のあるメソッド(changeName, changeEmail, addPoint)経由
コンストラクタと private セッターで不変条件をチェック
としています。
これなら
User 生成時点で「変な状態の User」は作れない
後から外部コードにポイントを好き勝手に書き換えられない
「名前を変える」「ポイントを増やす」という操作がメソッド名で分かる
状態になります。
public フィールドをやめるというのは、
「値をむき出しに渡す」のをやめて、「意味のある操作」に変換することだと思ってください。
DTO のような「入れ物」なら public でもいいのか?
境界だけの「単なる箱」は例外的にアリ
画面入力や API のリクエスト/レスポンスを受けるための DTO では、
「ただのデータの束」と割り切って public フィールドを使うこともあります。
public class UserRequestDto {
public String name;
public String email;
}
Javaこのクラス自体にビジネスルールを持たせず、
「入力値を一旦受け取る入れ物」として使うだけなら、
public フィールドでも実害は少ないです。
ただし、その DTO をドメインの深いところまで持ち込むと危険です。
本来ルールを持つべき Entity まで public フィールドだらけになると、
今まで話してきた問題が全部発生します。
境界向け DTO は public フィールドでもよい場合がある。
でも、ドメインモデル(User, Order, Account など)は public フィールドにしない。
この線引きを意識しておくと、安全です。
まとめ:public フィールドを見たときに自分に問うこと
public フィールドの一番の問題は、
そのクラスが「自分の状態を自分で守る」という大事な責任を放棄してしまうこと
どこからでも書き換えられて、バグの原因が追いづらくなること
一度公開した途端、それが「変えにくい外部 API」になってしまうこと
です。
コードを書いていて public フィールドを置きたくなったら、
一度こんなふうに自問してみてください。
この値は、本当にどこからでも自由に書き換えていいのか
このクラスにとっての「ありえない状態」は何か
その「ありえない状態」を、どうやって(コンストラクタやメソッドで)防ぐべきか
