SJ blog
beginner
S

信頼度ランク

S 公式ソース確認済み
A 成功実績多数・失敗例少数
B 賛否両論
C 動作未確認・セキュリティリスク高
Z 個人所感

Javaのパッケージ — クラスに「住所」をつける仕組み

なぜJavaにはパッケージがあるのか。完全修飾名・ディレクトリ構造・importの意味を根本原理から解説する初心者向け記事。

一言結論

パッケージは「名前の衝突」を防ぐ名前空間。完全修飾名はクラスの住所であり、importはその住所の省略記法にすぎない。

最初に:「名前が被る」問題

Javaでプログラムを書いていると、StringListといったクラス名を当たり前のように使う。でもちょっと立ち止まって考えてほしい。世界中に何百万人もいるJava開発者が、みんな好きな名前でクラスを作っている。

// あなたが作ったクラス
class Logger { ... }

// ライブラリAのLogger
class Logger { ... }

// ライブラリBのLogger
class Logger { ... }

全員がLoggerと名付けたら、コンパイラはどれを使えばいいか判断できない。

この問題はJavaが生まれた1990年代から予測されていた。その解決策が パッケージ だ。

パッケージとは「住所」だ

パッケージは、クラスに住所をつける仕組みだ。

東京都渋谷区○○1-2-3 の 田中太郎

住所がなければ「田中太郎」が世界中にいても届けられない。住所があれば一意に特定できる。

Javaではこれをクラス名の前に . でつなげて表現する:

java.lang.String
com.example.myapp.Logger
org.apache.commons.lang3.StringUtils

java.lang.String は「java.lang パッケージにある String クラス」という意味だ。これを 完全修飾名(Fully Qualified Class Name / FQCN) と呼ぶ。

完全修飾名を使えば、たとえ世界中にLoggerクラスがあっても:

com.example.myapp.Logger logger1 = new com.example.myapp.Logger();
org.slf4j.Logger         logger2 = org.slf4j.LoggerFactory.getLogger(...);

と書けば、どちらのLoggerかを完全に区別できる。

ディレクトリ構造がパッケージを表す

パッケージ名は、ファイルの置き場所(ディレクトリ)と完全に一致する 必要がある。

src/
└── com/
    └── example/
        └── myapp/
            └── Logger.java   ← パッケージは com.example.myapp

Logger.java の中身はこう書く:

package com.example.myapp;  // ← ディレクトリと完全に一致させる

public class Logger {
    // ...
}

なぜディレクトリと一致させなければならないのか?

答えはシンプルで、コンパイラとJVMが「クラスファイルを探す」ときにこのルールを使うからだ。

com.example.myapp.Logger が必要になったとき、コンパイラは:

  1. クラスパス(クラスを探す場所)の中から
  2. com/example/myapp/Logger.class を探す

パッケージ名の . をスラッシュに置き換えたパスだ。もしディレクトリ構造とパッケージ名がずれていたら、クラスが見つからずエラーになる。

❌ Logger.javaをsrcの直下に置いて package com.example.myapp と書く → コンパイル・実行時にエラー
✅ src/com/example/myapp/Logger.java に置いて package com.example.myapp と書く → OK

importは「住所の省略宣言」だ

完全修飾名を毎回書くのは大変だ。java.util.ArrayList<java.lang.String> なんて何度も書きたくない。そこでimportを使う:

import java.util.ArrayList;

// これ以降は省略形で書ける
ArrayList<String> list = new ArrayList<>();

// importしなければ毎回フルネームが必要
java.util.ArrayList<String> list2 = new java.util.ArrayList<>();

import は「このクラスは完全修飾名の代わりに短縮名で書く」という宣言だ。コンパイル時に完全修飾名に展開されるだけで、実行に影響はまったくない。

❌ 誤解: importするとそのクラスがメモリに読み込まれる
✅ 正解: importはただの「省略記法の宣言」。クラス読み込みは実行時に必要になったとき

ワイルドカードimportもある:

import java.util.*;  // java.utilパッケージの全クラスを省略記法で使う

ただし「どのクラスを使っているか」が分かりにくくなるため、明示的なimportを好む開発者も多い。

java.langだけは特別

Stringを使うとき、import java.lang.String; を書かない。書かなくても使える。

これは特例で、java.lang パッケージだけが 自動的にimportされるjava.lang はJavaの根幹を担うパッケージで、String, Integer, Object, System, Math などが含まれている。これらは頻繁すぎて毎回importを書くのが非現実的なため、言語仕様レベルで自動importが保証されている。

パッケージ名の慣習

パッケージ名には一般的な慣習がある:

ドメイン名を逆さにして先頭に使う
  例: example.com → com.example.myapp

なぜドメイン名を逆にするのか?ドメイン名は世界中で一意だからだ。com.exampleというパッケージ名を使えるのはexample.comの所有者だけ、という自然な一意性が生まれる。

Javaの標準ライブラリが java.*javax.* で始まるのも同じ理由で、Javaを開発したSun Microsystems(現Oracle)のドメインに由来している。

まとめ

パッケージ     = クラスの「住所」(名前空間)。名前の衝突を防ぐ
完全修飾名     = パッケージ名 + クラス名(例: java.lang.String)
ディレクトリ構造 = パッケージと完全に一致する(コンパイラが探せるように)
import        = 完全修飾名を省略するための宣言(実行には影響しない)
java.lang     = 唯一の自動import対象(Javaの根幹クラス群)

名前の衝突という現実問題に対して、「住所をつける」という直感的な解決策がパッケージだ。仕様を暗記するより、この思想を理解しておくと、パッケージ周りのエラーが出たときに何が起きているか迷わなくなる。