全体像
パッケージプライベート(package-private)は「アクセス修飾子を何も付けない」状態の可視性で、同一パッケージ内からだけ見える(使える)という意味です。クラス・メソッド・フィールド・コンストラクタに適用され、外部パッケージからは完全に隠されます。public のように外へ広く公開せず、private のようにクラス内部に閉じもしない——“パッケージという小さな仲間内だけで共有する”ための中間の可視性です。
何に適用できるかと基本ルール
トップレベルクラスの既定はパッケージプライベート
ファイル直下(トップレベル)のクラスでアクセス修飾子を省略すると、パッケージプライベートになります。外部パッケージからは宣言自体が見えないため、インポートもできません。ライブラリの内部実装やヘルパーを隠すのに最適です。
// com.example.internal パッケージ内
package com.example.internal;
// 修飾子なし → package-private(同パッケージ内からだけ見える)
class EmailNormalizer {
static String normalize(String s) {
return s == null ? "" : s.trim().toLowerCase(java.util.Locale.ROOT);
}
}
Javaメンバー(フィールド・メソッド・コンストラクタ)の既定もパッケージプライベート
クラスの中でも修飾子を省略すると、そのメンバーは同一パッケージ内からだけ参照できます。APIの表面積を減らし、外部からの誤用を防ぎます。
package com.example.internal;
public final class Sanitizer {
// 修飾子なし → package-private
static boolean blank(String s) { return s == null || s.trim().isEmpty(); }
}
Java見える範囲の正確なイメージ(重要ポイントの深掘り)
同一パッケージ内だけが対象
同じパッケージに属するクラスからは、パッケージプライベートの型やメンバーを自由に参照できます。異なるパッケージからは、継承していても見えません。protected と違い、「サブクラスから自分自身に対してだけ見える」という特例はありません。
// com.example.api
package com.example.api;
public final class EmailUtil {
public static boolean isValid(String s) {
// 他パッケージの package-private は参照不可
// return com.example.internal.EmailNormalizer.normalize(s).contains("@"); // コンパイルエラー
return s != null && s.contains("@");
}
}
Javaパッケージ設計が可視性設計になる
パッケージプライベートは“パッケージ境界”に依存します。内部実装を閉じ込めるためには、APIと実装を別パッケージに分けることが重要です。境界が整理されていれば、「内部=package-private」「外部=public」の筋が通ります。
実務での使いどころ
内部実装・ヘルパーの非公開化
利用者が直接触るべきでないクラスやメソッドは、パッケージプライベートにして隠します。公開すべき契約(API)は public の薄い窓口だけにします。
// 内部(非公開)
package com.example.internal;
class Urls {
static boolean isHttp(java.net.URI u) { return "http".equalsIgnoreCase(u.getScheme()); }
}
// 公開(最小限の契約)
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 boolean isHttp() {
// com.example.internal.Urls は別パッケージなので呼べない → API側で必要分だけ提供する設計に
return "http".equalsIgnoreCase(uri.getScheme());
}
}
Javaパッケージ内の協調(テスト支援や分割統治)
同じパッケージ内で協調して動く複数クラスの内部連携には、パッケージプライベートが向いています。テストも同一パッケージに置くことで、内部メソッドを直接検証できます(ただし公開API経由の振る舞い検証が基本)。
よくある落とし穴と設計指針(重要ポイントの深掘り)
パッケージの粒度が粗すぎて“なんでも見える”
同じ大きなパッケージに詰め込みすぎると、package-private が実質“広い公開”になり、内部漏れが増えます。レイヤ(api/domain/infrastructure)や機能単位でパッケージを分け、境界を小さく保ちましょう。
クラスを別パッケージに移動するとアクセスが切れる
パッケージプライベートはパッケージ境界に強く依存します。リファクタリングでパッケージをまたぐと、非公開メンバーへのアクセスが壊れます。境界越えが必要になったら、public の最小APIを新設して内部詳細を隠したまま外に必要機能だけ見せます。
protected と取り違えない
異なるパッケージのサブクラスからでも“自分自身に対して見える”のが protected、完全に見えないのが package-private。継承拡張ポイントを露出したいなら protected、内部連携だけに絞るなら package-private を選びます。
例題で身につける
例 1: 内部ユーティリティの隠蔽
// com.example.internal
package com.example.internal;
class Strings {
static String normalize(String s) {
return s == null ? "" : s.trim().toLowerCase(java.util.Locale.ROOT).replaceAll("\\s+", " ");
}
}
// com.example.api
package com.example.api;
public final class Name {
private final String value;
public Name(String raw) {
// 内部の normalize を直接使えないので、API側で必要な処理だけ持つ
var v = raw == null ? "" : raw.trim().replaceAll("\\s+", " ");
if (v.isEmpty()) throw new IllegalArgumentException("name required");
this.value = v;
}
public String value() { return value; }
}
Java例 2: 同一パッケージでの連携
// com.example.domain
package com.example.domain;
class Validator { // package-private
static void checkPrice(int price) {
if (price < 0) throw new IllegalArgumentException("price>=0");
}
}
public final class Product {
private int price;
public Product(int price) {
Validator.checkPrice(price); // 同パッケージなので参照可
this.price = price;
}
public void reprice(int newPrice) {
Validator.checkPrice(newPrice);
this.price = newPrice;
}
}
Java仕上げのアドバイス(重要部分のまとめ)
パッケージプライベートは「同一パッケージだけに見せる」ための可視性です。APIは public の薄い契約だけにし、内部実装・ヘルパーは package-private に閉じる。パッケージ境界の設計が要で、粒度を適切に分けるほど可視性が効きます。protected と誤用せず、継承拡張が必要なら protected、内部連携だけなら package-private と線引きする——この基本を守れば、外に見せるべきものだけが見え、変更に強い設計になります。
