全体像
public は「どこからでも見える・使える」という意味のアクセス修飾子です。パッケージが違っても、クラス外からでも、他のモジュールからでも、public にしたメンバー(クラス・メソッド・フィールド・コンストラクタ)は参照できます。つまり、public は“外部に約束する窓口(API)”を示すラベルであり、ライブラリやアプリ全体の利用者に向けて「ここは使って良い、仕様として守る」という意思表示になります。
適用できる対象と基本ルール
public はクラス、インターフェース、enum、メソッド、コンストラクタ、フィールドに付けられます。トップレベルのクラスは public にすると「ファイル名=クラス名.java」である必要があり、どのパッケージからもインポートして使えます。メソッドやコンストラクタを public にすると、外部コードが自由に呼べるため、引数の検証や例外方針を含めた「契約の明文化」が不可欠です。
// どのパッケージからも使える公開クラス
package com.example.api;
public final class Email {
private final String value;
public Email(String raw) { // 公開コンストラクタ(契約)
var v = raw == null ? "" : raw.trim().toLowerCase();
if (!v.contains("@")) throw new IllegalArgumentException("bad email");
this.value = v;
}
public String value() { return value; } // 公開メソッド(契約)
}
Java可視性の範囲と意味合い(重要ポイントの深掘り)
public は「完全に開く」ことを意味します。package-private(修飾子なし)や protected と違い、パッケージ境界や継承の有無に影響されません。だからこそ、公開した API は後方互換性の責任が重くなります。内部の実装詳細(ヘルパーや一時的な構造)は公開せず、パッケージ内に閉じるか private にするのが基本です。public は「使い方をドキュメント化し、破壊的変更を避ける」ための宣言でもあります。
// 内部実装は非公開にし、公開APIは薄く・安定に
package com.example.impl;
class EmailNormalizer { // package-private:外から見えない
static String normalize(String s) {
return s == null ? "" : s.trim().toLowerCase();
}
}
package com.example.api;
public final class EmailUtil { // 公開はユーティリティの“契約”だけ
public static boolean isValid(String s) {
var v = com.example.impl.EmailNormalizer.normalize(s);
int at = v.indexOf('@');
return at > 0 && at == v.lastIndexOf('@') && at < v.length() - 1;
}
}
Java使いどころと設計の意図
public は外部に提供する機能だけに付けます。ライブラリなら「利用者が呼ぶべき関数・クラス」、アプリなら「コントローラやエントリポイント、DTO など外部と接する部分」が該当します。フィールドは基本 public にせず、private フィールド+検証付きメソッドで整合性を守ります。値オブジェクトは public クラス+public メソッドで読み取りを提供し、書き換えは不可(不変)に寄せると安全です。
public final class Product {
private int price; // 外部から直接触らせない
public Product(int price) {
if (price < 0) throw new IllegalArgumentException("price>=0");
this.price = price;
}
public int price() { return price; } // 読み取り用の公開メソッド
public void reprice(int newPrice) { // 変更は契約通りに検証して公開
if (newPrice < 0) throw new IllegalArgumentException("price>=0");
this.price = newPrice;
}
}
Java実務での注意点(重要ポイントの深掘り)
public は「依存を増やす」ので、むやみに広げないことが最大のポイントです。公開した瞬間から、その仕様に依存する利用者が現れ、破壊的変更が難しくなります。変更に強くするには、次の3つを徹底します。
- 契約の明確化(引数の前提、失敗時の例外、戻り値の意味)
- カプセル化(フィールドは private、操作は検証付きメソッド)
- パッケージ境界の設計(内部は package-private に閉じ、外は public で最小限)
// 契約を明確にした公開APIの例
public final class FileLoader {
// 契約:UTF-8で読み、読み込み失敗は IOException
public String readUtf8(java.nio.file.Path p) throws java.io.IOException {
return java.nio.file.Files.readString(p, java.nio.charset.StandardCharsets.UTF_8);
}
}
Java例題で身につける
例 1: 公開APIと内部実装の分離
// 公開API
package com.example.api;
public final class Url {
private final java.net.URI uri;
public Url(String s) {
try { this.uri = new java.net.URI(s); }
catch (java.net.URISyntaxException e) { throw new IllegalArgumentException("bad url: " + s, e); }
}
public java.net.URI toUri() { return uri; }
}
// 内部ヘルパー(非公開)
package com.example.internal;
class Urls {
static boolean isHttp(java.net.URI u) { return "http".equalsIgnoreCase(u.getScheme()); }
}
Java例 2: DTO の公開と不変設計
// 外部とのデータ受け渡し用:公開クラス+不変
public final class UserDto {
private final String id;
private final String name;
public UserDto(String id, String name) {
this.id = id == null ? "" : id.trim();
this.name = name == null ? "" : name.trim();
}
public String id() { return id; }
public String name() { return name; }
}
Javaよくあるつまずきと回避
「なんでも public」にすると、内部詳細まで依存が広がって後から変更できなくなります。まず private で閉じ、必要なら package-private、継承前提なら最小限の protected、それでも外部に見せる必要がある箇所だけ public にします。フィールドを public にするのも典型的な落とし穴で、整合性が簡単に壊れます。必ず private にし、検証付きのメソッドで操作させてください。公開した API の契約(受け入れる値、失敗の扱い、戻り値の意味)をドキュメントや名前で伝えることも重要です。
仕上げのアドバイス(重要部分のまとめ)
public は「外部に約束する窓口」。公開範囲は最小に、契約は明確に、内部は隠す。フィールドは public にせず、private と検証付きメソッドで整合性を保つ。パッケージ境界で内部(package-private)と外部(public)を分ける——この線引きを守るだけで、読みやすさ・安全性・変更耐性が一気に上がります。
