【Java】パッケージとインポートについて

Java

パッケージとは

Javaのパッケージとは、クラス名の競合を回避するための名前空間のことです。あるパッケージに属するクラスは、先頭に . で繋いでパッケージ名を付加することにより、世界中で一意となる完全修飾クラス名で指定できるようになります。

Namespace(名前空間) とは、一意であることが要求される物事の名前を、論理的なグループに分ける(空間を区切る)ことによって、重複の可能性を下げ、衝突が起こらないようにするための仕組みのことです。

これはよく姓名を例に説明されます。山田家の中では単に”太郎”と呼べば誰を指しているか一意に定まりますが、開発チームに太郎が複数人いる場合は、”山田太郎”と呼ぶことによって初めて、その他の太郎と区別し特定することができます。この時、姓が名前空間にあたります。姓という名前空間がなければ、開発チーム内で”太郎”という名前が競合する状態となってしまいます。

Javaにおいてもこの例と同様、同じパッケージに属していればクラス名のみで参照することができ、パッケージが異なる場合は、パッケージ名を含めた完全修飾クラス名で指定する必要があります。

その他、Domain(ドメイン) もこの考え方で理解することができます。ドメインは、インターネット上のリソースを識別するため、世界中で一意でなければなりませんが、それを担保するために名前空間の考え方を利用しています。

Domain Name System > 動作 > ドメインの階層構造 の 図 のように、ドメイン名の構造 は、. で区切られた部分から成り、右から順に 大分類 > 中分類 > 小分類 のようなイメージで、各階層の権威DNSサーバが、ゾーン内の下位ドメインで重複が起こらないよう名前を管理しています。これにより、ドメインを一意に保ち、ルート(ドメインの右側)から順に辿っていくことで、対応するIPアドレスに変換し名前解決を行えるような仕組みを構築しています。これを特に、ドメイン名前空間やDNS名前空間と呼称することもあります。

A package is a namespace that organizes a set of related classes and interfaces. Conceptually you can think of packages as being similar to different folders on your computer. You might keep HTML pages in one folder, images in another, and scripts or applications in yet another.
パッケージとは、関連するクラスとインターフェースのセットを整理する名前空間です。概念的には、パッケージはコンピュータ上の複数のフォルダのようなものと考えることができます。HTMLページをあるフォルダに、画像を別のフォルダに、スクリプトやアプリケーションをさらに別のフォルダに保存するといった具合です。

Objects, Classes, Interfaces, Packages, and Inheritance > What is a Package?

※ここではコンピュータ上のフォルダが例に挙げられていますが、異なるフォルダに分けて保存する概念に似ているとだけ言っている点に注意が必要です。フォルダの中にサブフォルダを作成し、階層化するといったことについては言及されていないため、フォルダの例を見たからといってディレクトリ構造のイメージを持ってしまうのは、後述 の通り誤りです。

To make types easier to find and use, to avoid naming conflicts, and to control access, programmers bundle groups of related types into packages.
型を簡単に見つけて使用できるようにし、名前の競合を回避し、アクセスを制御するために、プログラマは関連する型のグループをパッケージにまとめます。

Definition: A package is a grouping of related types providing access protection and name space management.
定義: パッケージとは、アクセス保護と名前空間管理を提供する関連する型のグループです。

Packages > Understanding Packages

パッケージとは、クラス名の競合を回避するための名前空間のこと。

ソースコード内でのパッケージの使われ方

Javaでは、指定が不要な一部の場合を除き、基本的に必ず完全修飾クラス名を使用して対象のクラスを参照します。これにより、いくつものクラスが存在する中であっても目的のクラスを適切に呼び出すことが可能となります。

クラスやインターフェース等を使用する際、後述 のインポート文を記述した場合を除き、基本的にはパッケージ名を含む完全修飾クラス名で指定する必要があります。しかし例外が二つあり、単にクラス名だけで指定できる場合があります。

一つは、同じパッケージに属するクラスを使用する場合です。これは 姓名の例 で示したように、同じ名前空間のクラスであれば、わざわざパッケージ名を含めて指定せずとも、クラス名だけで一意に識別することができるためです。

もう一つは、java.langパッケージに属するクラスを使用する場合です。これはこのパッケージが、Javaプログラミング言語の設計にあたり基本的なクラスを提供しており、利用頻度が高いためです。

Code in a compilation unit automatically has access to all classes and interfaces declared in its package and also automatically imports all of the public classes and interfaces declared in the predefined package java.lang.
コンパイルユニット内のコードは、そのパッケージで宣言されている全てのクラスとインターフェイスに自動的にアクセスでき、また、定義済みパッケージ java.lang で宣言されている全ての public クラスとインターフェイスも自動的にインポートします。

Chapter 7. Packages and Modules

従って以上のことから、次の3つの場合は完全修飾クラス名ではなく、単にクラス名だけを指定して使用することができます。

  • 同じパッケージに属するクラスを使用する場合
  • java.langパッケージに属するクラスを使用する場合
  • インポートされたクラスを使用する場合

例えば、Javaの標準APIには List という名称のクラス(インターフェース)が二つ存在します。一つは順序付きコレクションである List 、もう一つはGUI用コンポーネントの List です。このような場合、単に List とするだけでは、どちらを指しているのか分からなくなってしまいます。これが、クラス名が競合している状態です。そこで、パッケージを使用することにより、両者を明確に区別できるようになります。

前者は一般的なデータ構造のリストを表現し、java.util パッケージに含まれます。後者はGUIプログラミングをサポートする AWT(Abstract Window Toolkit) の一部であり、java.awt パッケージに含まれます。従って、パッケージ名を付加した完全修飾クラス名はそれぞれ以下のようになり、同じコード内であっても指定した方を適切に呼び出すことができます。

java.util.List utilList = java.util.List.of();
java.awt.List  awtList  = new java.awt.List();

ソースコード内で、同名のクラスを区別するために使用される。

プログラム実行時のパッケージの使われ方

完全修飾クラス名の使われ方として、同じコード内でクラスを区別するための使用例を挙げましたが、もう一つ、プログラムの実行時に対象となるクラスを特定する際にも、完全修飾クラス名は使用されます。

例えば、次のような com.example パッケージの Main クラスを用意し、これをコンパイルしてクラスファイルを作成しておきます。

package com.example;

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}
ソースファイルをコンパイルしてクラスファイルを作成する手順の詳細はこちらです。

この場合、実行コマンドは次のようになります。しかし、パッケージを含めた完全修飾クラス名を指定せず単に Main としてしまうと、エラーになります。(※ 後述 の通り実際には、加えてクラスファイルの配置先も適切にしておく必要があります。)

# 実行コマンド(完全修飾クラス名を指定)
# → 正常終了
$ java com.example.Main
Hello World

# 実行コマンド(完全修飾クラス名でなく単にMainを指定)
# → エラー
$ java Main
エラー: メイン・クラスMainを検出およびロードできませんでした
原因: java.lang.ClassNotFoundException: Main

Getting Started で使用されるようなシンプルなクラスの場合、デフォルトパッケージ であることが多いため、javaコマンドの引数には「クラスファイルを指定(※ただし拡張子を除く)」のように誤解してしまうこともあるかもしれません。しかし こちら でも説明している通り、正確には指定するのはクラスファイルではなくクラスです。従ってパッケージがある場合は、それを含めた完全修飾クラス名を指定する必要があります。

プログラム実行時、対象のクラスを指定するために使用される。

パッケージの宣言方法と命名規則

パッケージの使われ方はここまでに示した通りですが、宣言するには次のように package 文を使用し、必ずファイルの先頭に記述します。パッケージ名は . 区切りの文字列であり、予約語を除く、識別子(英数字, $, _ かつ 数字以外始まり)に沿った形式という制約はあるものの、基本的には任意の名前を自由に付けることができます。

パッケージを宣言せずにクラスを定義することも可能です。これは、無名パッケージあるいはデフォルトパッケージと呼ばれます。

For small programs and casual development, a package can be unnamed (§7.4.2) or have a simple name, but if code is to be widely distributed, unique package names should be chosen using qualified names. This can prevent the conflicts that would otherwise occur if two development groups happened to pick the same package name and these packages were later to be used in a single program.
小規模なプログラムやカジュアルな開発では、パッケージに名前を付けない( §7.4.2 )か、あるいは単純な名前を付けることができます。しかしコードを広く配布する場合は、修飾名を用いて一意のパッケージ名を選択する必要があります。これにより、2つの開発グループが偶然同じパッケージ名を選択し、後にそれらのパッケージが単一のプログラムで使用されることになっても、競合が発生するのを防ぐことができます。

Chapter 7. Packages and Modules

An ordinary compilation unit that has no package declaration, but has at least one other kind of declaration, is part of an unnamed package.
パッケージ宣言を持たないものの、少なくとも1つの他の種類の宣言を持つ通常のコンパイル単位は、名前のないパッケージの一部です。

Chapter 7. Packages and Modules > 7.4.2. Unnamed Packages

ただし上記の記述の通り、パッケージ宣言の無いクラスは “パッケージに属していない” のではなく “無名パッケージに属している” と捉える必要がある点には注意が必要です。

  • パッケージ宣言の無いクラス
    • 無名パッケージ(デフォルトパッケージ)に属している
    • × パッケージに属していない

例えば以下のようにパッケージを宣言した場合、Mainクラスは my.java.sample パッケージに属し、完全修飾クラス名 my.java.sample.Main で参照できるようになります。

package my.java.sample; // パッケージ宣言(※ファイルの先頭に記述)パッケージ名「my.java.sample」

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}

ただし、パッケージの命名は基本的に自由であるものの、外部に公開する場合はパッケージ名が世界中で一意となるように命名する必要があります。前述 の通りパッケージとは名前空間であり、重複してはそこに属するクラスを完全修飾クラス名で識別できなくなってしまうためです。

また、公開予定のない閉じたプロジェクトであっても、パッケージは一意となるように命名しておく方が無難です。万が一、公開されている外部ライブラリとパッケージ名が重複してしまった場合、それらを使用することができなくなってしまうためです。

どういうことかというと、例えば内部利用のプライベートなアプリケーションを作成していたとします。この時、特に公開予定も無いため、深く考えずにパッケージ名を考えました。オリジナル(original)のアプリなので org 、春(Spring)に開発を始めたので springframework 、プログラミングスクール(Boot Camp)の卒業制作なので boot と続け、パッケージ名を org.springframework.boot としました。ここにmainメソッドを持つクラス、春に開発を始めたアプリのエントリポイントとして、SpringApplicationクラスを作成し、完全修飾クラス名を org.springframework.boot.SpringApplication とする自作クラスを生み出しました。

これは一見、公開せず単体で利用するのであれば何の問題も無いように見えます。実際、文字列を出力する次のようなサンプルコードは正常に動作します。

package org.springframework.boot;

public class SpringApplication {
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}
$ javac org\springframework\boot\SpringApplication.java

$ java org.springframework.boot.SpringApplication
Hello World

しかしこのコードは、Spring Boot を使おうとした時に問題が生じます。

新たに Spring Boot 起動用のMainクラスを作成してみますが、そこでSpringフレームワークが提供するクラスを二つ使います。そのうちの一つに SpringApplication というものがあり、このクラスの runメソッド 呼び出し部分で、メソッドが未定義であるというエラーが発生してしまいます。

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class, args); // × The method run(Class<Main>, String[]) is undefined for the type SpringApplication
    }
}

これは、呼び出そうとしているSpringApplicationクラスが、それが定義されたSpringフレームワーク内で、非営利団体向けドメイン org(組織:organization)の Spring Framework プロジェクト( springframework )を迅速に開始するための Spring Boot( boot )と命名された、org.springframework.boot パッケージに属しているためです。

これは先ほど自作したSpringApplicationクラスと、たまたまパッケージ名が重複してしまっており、完全修飾クラス名が競合してしまっています。これによりインポート文で指定された org.springframework.boot.SpringApplication が、自作クラスなのかSpringフレームワーク内のクラスなのか区別できなくなり、自作クラスの方は、mainメソッドのみでrunメソッドが定義されていないため、エラーとなってしまいます。

このように、本来なら完全修飾クラス名により呼び出せるはずのフレームワーク内のクラスが、自作したクラスと重複してしまっているために呼び出せなくなってしまうことがあります。そのため、外部へ公開する予定のないプログラムであっても、フレームワークやライブラリを利用する際に、適切な名前空間によって識別できるよう、一意となるパッケージ名を定義する必要があります。

もし個人用のプログラムで、所有しているドメインが無い場合は、例示用に予約されている Example Domain を利用し com.example とすることも手段の一つです。これにより少なくとも、利用するフレームワークやライブラリと競合して不都合が生じることは確実に回避できます。

では、世界中で一意であることが保証されるにはどのように命名すれば良いでしょうか。世の中に存在する全てのパッケージを確認するのは現実的ではありません。そこで、既に一意であることが保証されている、インターネットドメインを利用します。

前述 の通り、ドメインもパッケージと同じく . 区切りの文字列で表現され、さらにドメイン名前空間により、ルートからの木構造で一意に識別できるようになっています。従ってこれをパッケージ名に使用することで、ドメインを所有する組織単位でパッケージに重複が無いことを保証できます。あとは組織内でのルールを設けて残りの部分が重複しないようにし、ドメインより下位部分を命名すれば、あらゆるパッケージ名は一意となり、名前空間を担保できるようになります。

パッケージの先頭にそのまま利用せず、敢えてドメイン名の逆順とする点ついて、一見不可解に見えるかもしれません。これは、ドメインが木構造により . 区切りの単位で右から順に、広い範囲から狭い範囲へと空間が区切られていることを考えると、パッケージ名としては逆順にしておく方が適切ということが分かります。

Companies use their reversed Internet domain name to begin their package names—for example, com.example.mypackage for a package named mypackage created by a programmer at example.com.
企業は、逆順にしたインターネットドメイン名をパッケージ名の先頭に使用します。例えば、example.com のプログラマが作成した mypackage という名前のパッケージの場合、com.example.mypackage となります。

Packages > Naming a Package and Naming Conventions

例えば ObjectUtils というクラス名を持つユーティリティには、Apache Commons Lang Spring の二種類があります。これらはクラス名が重複してしまっていますが、それぞれがドメイン名の逆順から始まるパッケージに属しているため、互いに完全修飾クラス名の重複を気にすることなく、一意の命名を行うことができています。

仮に自作クラスに ObjectUtils という命名をしたくなった場合も、例えば自身(または所属する組織)が所有するドメインが example.com であった場合、まずパッケージの先頭を com.example とします。この時点で既に一意の接頭辞で始まっているため、残りの部分は自由に(あるいは組織内のルールに則って)命名すれば良くなります。この時、既に世の中のライブラリに ObjectUtils というクラスが存在していることや、それらがどのような名称のパッケージに属しているのかということを、一切意識することなく、慣習に従うだけで重複を避けられている点が重要です。

var obj = new Object();

org.apache.commons.lang3.ObjectUtils.isEmpty(obj); // Apache Commons Lang
org.springframework.util.ObjectUtils.isEmpty(obj); // Spring
com.example.my.utilities.ObjectUtils.isEmpty(obj); // 自作クラス

このようにパッケージ名は、Javaの開発者がドメイン名の逆順という慣習に従うことによって、名前空間の重複を防いでいます。留意する必要があるのは、これはあくまで慣習であり、これに従わない命名をしてもエラーにはならないということです。

パッケージは、ファイルの先頭に package 文を記述して宣言する。
パッケージ名の先頭は、慣習に従いドメイン名の逆順で命名する。

インポートとは

クラスを使用する際、完全修飾クラス名で指定するというのは 前述 の通りです。しかし次のように、com.example.Main クラスで、java.util.Date というクラスのインスタンスを生成する際、毎回 java.util.Date と記述するのは冗長です。

package com.example;

public class Main {
    public static void main(String[] args) {
        // 毎回、完全修飾クラス名を記述するのは効率が悪く、可読性も落ちる。
        java.util.Date date = new java.util.Date();
    }
}

そこで、この記述を省略し、単にクラス名だけを指定できるようにする機能があります。これがインポートです。package 文は必ずファイルの先頭に記述する必要がありましたが、import 文はその次に記述します。

package com.example;

import java.util.Date; // import の後ろに完全修飾クラス名を記述

public class Main {
    public static void main(String[] args) {
        // インポート文を宣言することで、パッケージ名を省略し、単にクラス名だけで済むようになる。
        Date date = new Date();
    }
}

インポートと聞くと、何か外部のデータを取り込んだりファイルを読み込んだりする機能に感じられますが、上記の通り、単に完全修飾クラス名を省略してクラス名だけで記述できるようにするための機能です。

An import declaration allows a named class, interface, or static member to be referred to by a simple name (§6.2) that consists of a single identifier.
インポート宣言により、名前付きクラス、インターフェース、または static メンバを、単一の識別子で構成される単純な名前( §6.2 )で参照できるようになります。

Chapter 7. Packages and Modules > 7.5. Import Declarations

インポートは、パッケージ宣言の次に import 文を記述して宣言する。
インポートは、完全修飾クラス名を省略して単純な名前で参照できるようにする機能。

同名クラスのインポート

Javaの Date という名称を持つクラスにはもう一つ、java.sql.Date というものがあります。従って、完全修飾クラス名を指定して、次のように区別して使用することができます。

java.util.Date utilDate = new java.util.Date();
java.sql.Date  sqlDate  = new java.sql.Date(0);

しかし、このような場合にインポート文を使おうとすると、単に Date とした時にどちらを指しているのか特定できなくなってしまうため、コンパイルエラーとなります。

import java.util.Date;
import java.sql.Date; // × The import java.sql.Date collides with another import statement

Date utilDate  = new Date();
Date  sqlDate  = new Date(0);

このような場合、必ずどちらかを完全修飾クラス名で指定する必要があります。こうすることで、単にクラス名を指定した場合はインポート宣言がある方を、完全修飾クラス名を使用した場合は指定された方を、それぞれ呼び出すことができます。

import java.util.Date;

         Date utilDate  = new Date();
java.sql.Date  sqlDate  = new java.sql.Date(0);

Javadoc に記載の通り java.sql.Datejava.util.Date を継承しているため、以下のように java.util.Date で受け取ることもできます。

import java.util.Date;

Date date     = new Date();           // java.util.Date
Date utilDate = new java.util.Date(); // java.util.Date
Date sqlDate  = new java.sql.Date(0); // java.sql.Date

ただし、この親子関係はパッケージによるものではなく、あくまでも extends の継承によるものである点、注意が必要です。後述 の通り、パッケージには親子関係を表現する機能はありません。

Javaで同名クラスを同時にインポートできず、一方を完全修飾クラス名で指定する必要があるというのは上記の通りです。これは、インポート文を記述して単純なクラス名で参照できるようにすると、パッケージで区切っていた部分が失われ結局重複が発生し、名前の衝突が起きてしまうためです。

この問題を解決し、同名クラスを区別しつつ、短い名称で参照できるよう、別名を付けてインポートできる機能が備わっている言語も少なくありません。例えばPythonやJavaScriptでは、as を使用することで任意の別名でインポートを行うことができます。

モジュール名の後に as が続いていた場合は、 as の後ろの名前を直接、インポートされたモジュールが束縛します。

>>> import fibo as fib
>>> fib.fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

Python チュートリアル > 6. モジュール > 6.1. モジュールについてもうすこし

import { export1 as alias1 } from "module-name";

aliasN
指定されたインポートを参照する名前。JavaScript の識別子として有効な文字列でなければなりません。

JavaScript リファレンス > 文と宣言 > import > 構文

これらはJavaには存在しない機能ですが、Dateクラスの例を使うと以下のようなイメージで区別できるようにしているというものです。

import java.util.Date as UtilDate;
import java.sql.Date  as SqlDate;

UtilDate utilDate = new UtilDate();
SqlDate  sqlDate  = new SqlDate(0);

同名のクラスは、パッケージが異なっていても一つしかインポート文を記述できない。
同じクラス内で複数の同名クラスを使用したい場合は、完全修飾クラス名で記述する。

パッケージ全体のインポート

インポート文はワイルドカードに対応しており、アスタリスク( * )を使用することで、一行の記述でパッケージ全体をまとめてインポートすることができます。ただし、あくまでパッケージ全体をインポートするための機能であり、部分一致には対応していません。例えば *Logic と記述することにより、クラス名が「~Logic」で終わる物だけをインポートするといったことは不可能です。

To import all the types contained in a particular package, use the import statement with the asterisk (*) wildcard character.
特定のパッケージに含まれる全ての型をインポートするには、アスタリスク( * )ワイルドカード文字を含むインポート文を使用します。

import graphics.*;

Packages > Using Package Members > Importing an Entire Package

例えば com.example.logic に含まれる CalcLogicFileLogic の両方を使用したい場合、それぞれ個別にインポート文を記述せず、* を使用して一行にまとめることができます。

// import com.example.logic.CalcLogic;  ┐
// import com.example.logic.FileLogic; ┤
//                                    ↓
import com.example.logic.*;     // com.example.logic パッケージのクラスをまとめてインポート可能  ※サブパッケージは含まれない
import com.example.logic.sub.*; // または import com.example.logic.sub.SubLogic; <───────┴ 別途インポート文が必要

・・・

var calcLogic = new CalcLogic();
var fileLogic = new FileLogic();

var subLogic = new SubLogic();

注意点として、使用できるようになるのは、そのパッケージに含まれるクラスのみです。サブパッケージは含まれないため、別途インポートが必要になります。これは 後述 の通り、たとえディレクトリ構造上はインポートした物の下位にあっても、パッケージとは包含関係を表すものではなく、隔てられた全く別のものとして認識されるためです。

* を用いたインポートの使いどころについては、こちらで具体例を挙げています。

アスタリスク( * )を用いてパッケージ全体をインポートすることができる。

パッケージの注意点

注意点1(階層を合わせる必要がある)

パッケージの注意点として、実行時にクラスファイルの配置先(ディレクトリ構成)をこれに合わせる必要があるということが挙げられます。前項 で例に挙げた Main クラスとその実行コマンドですが、実はクラスファイルを適切に配置しないと、実行時に次のようなエラーが発生してしまいます。

# クラスファイルの配置先が正しくない状態で
# 完全修飾クラス名を指定 → ClassNotFoundException
$ java com.example.Main
エラー: メイン・クラスcom.example.Mainを検出およびロードできませんでした
原因: java.lang.ClassNotFoundException: com.example.Main

# クラスファイルの配置先が正しくない状態で
# 単にクラス名のみを指定 → NoClassDefFoundError
$ java Main
エラー: メイン・クラスMainを検出およびロードできませんでした
原因: java.lang.NoClassDefFoundError: Main (wrong name: com/example/Main)

完全修飾クラス名を指定しても、指定しなかった時と同様のエラーが発生し、逆に指定しなかった場合、今度は新たな NoClassDefFoundError というエラーが発生してしまいます。

$ java com.example.Main$ java Main
クラスファイルの配置先が正しい場合正常終了ClassNotFoundException
クラスファイルの配置先が正しくない場合ClassNotFoundExceptionNoClassDefFoundError

Java実行環境( JRE )において、そこに含まれる(またはJDKに含まれる)javaコマンドによって、Java仮想マシン(JVM:Java Virtual Machine)上でプログラムを実行する際、都度プログラム実行に必要なクラスを読み込む(ロードする)必要があります。この時、クラスのロードは、クラスローダ( ClassLoader )が担います。

javaコマンドはJavaアプリケーションを起動します。それには、Java Virtual Machine (JVM)を起動し、指定されたクラスをロードして、そのクラスmain()メソッドを呼び出します。

ツール・ガイド > javaコマンド > 説明

そして、クラスローダは必要なクラスをロードする際、そのクラスの完全修飾名を頼りにします。つまり、パッケージをディレクトリのパス、クラス名をクラスファイル名へと変換し、対象のクラスが定義されたクラスファイルを検索するということです。

従って、例えば com.example.Main の場合、以下のように com\example\Main.class としておかなければ、正しく完全修飾クラス名を指定しても ClassNotFoundException が送出されてしまいます。

以下の通り、API仕様やJDKツール仕様、Java言語仕様などから、このことを確認することができます。

クラスのバイナリ名を指定すると、クラス・ローダーはクラスの定義を構成するデータを見つけるか生成します。一般的な方法としては、名前をファイル名に変換して、ファイル・システムからその名前の「クラス・ファイル」を読み込みます。

API仕様 > java.lang.ClassLoader

クラスとインタフェースをパッケージに編成すると、そのパッケージはディレクトリとして表され、すべてのサブパッケージはサブディレクトリとして表されます。

  • パッケージのp.q.rは、ディレクトリ・ツリーp\q\r (Windows上で)またはp/q/r (他のシステム)として表されます。

ディレクトリまたはサブディレクトリ内では、.javaファイルは対応するパッケージまたはサブパッケージ内のクラスおよびインタフェースを表します。

  • パッケージp.q.rで宣言されたクラスZは、p\q (Windows上で)またはp/q (他のシステム)のサブディレクトリrのファイルZ.javaにより表されます。

javacコマンド > ソース・コードの契約 > パッケージのソース・コードの配置
※一部抜粋

As an extremely simple example of storing packages in a file system, all the packages and source and binary code in a project might be stored in a single directory and its subdirectories.
ファイルシステムにパッケージを保存する極めて単純な例として、プロジェクト内の全てのパッケージ、ソースコード、バイナリコードを単一のディレクトリとそのサブディレクトリに保存することが考えられます。

For example, if this simple organization were used on an operating system where the file name separator is /, the package name:
例えば、ファイル名の区切り文字が / であるオペレーティングシステムでこの単純な構成が使用された場合、以下のパッケージ名は、

jag.scrabble.board

would be transformed into the directory name:
以下のディレクトリ名に変換されます。

jag/scrabble/board

7.2. Host Support for Modules and Packages
※一部抜粋

クラス・ファイルは、そのクラスの完全修飾名が反映されたサブパス名を持ちます。たとえば、クラスcom.mypackage.MyClassmyclassesに格納されている場合、myclassesはユーザー・クラス・パスに含まれている必要があり、クラス・ファイルへのフル・パスは、Oracle Solarisの場合は/myclasses/com/mypackage/MyClass.class、Windowsの場合は\myclasses\com\mypackage\MyClass.classでなければいけません。

クラスの検索方法 > Javaランタイムがユーザー・クラスを検索する方法

クラスファイルは、パッケージとディレクトリ構成を合わせて配置する必要がある。
プログラム実行時、クラスローダが対象のクラスを見つけ出すために使用される。

注意点2(階層を表現する物ではない)

ここまで、クラスローダが、パッケージ名を含む完全修飾クラス名を、ディレクトリのパスとクラスファイル名に変換して、クラスファイルの場所を検索する、そのために、クラスファイルの配置先はパッケージと同じディレクトリ階層にする必要がある、と説明しました。

しかし、さらにここで注意が必要なのは、とはいえパッケージとは、ディレクトリ構成を表現するものでは決してないということです。

クラスローダの挙動を考えると、パッケージとはディレクトリ階層のパスであるかのように勘違いしてしまいがちです。しかし最初に記載した通り、パッケージとはあくまでクラス名の競合を回避して識別するための名前空間であり、並列に隔てられているということのみを表現しています。

従って、各パッケージ間に親子関係や包含関係のような相互関係はなく、それぞれが独立して存在しています。階層構造は表現せず、単に完全修飾クラス名という長い名前を付けることによって重複しないようにしているに過ぎません。そしてその命名構造を階層的にすることで、万が一にも重複してしまうことを避けることができているという仕組みです。

Each package has its own set of names for classes and interfaces, which helps to prevent name conflicts. The naming structure for packages is hierarchical.
各パッケージは、クラスとインターフェースの独自の名前セットを持ち、名前の競合を防ぐのに役立ちます。パッケージの命名構造は階層的です。

Chapter 7. Packages and Modules

ここでは確かに hierarchical(階層的)という表現が使用されていますが、あくまで命名構造が階層的であると言っているだけで、物理的には、JVMが認識する単位として同列のフラットな関係です。あくまで人間が理解しやすいよう見かけ上は、論理的にグルーピングされた概念として階層的である、と言えます。

At first, packages appear to be hierarchical, but they are not.
一見、パッケージは階層的であるように見えますが、そうではありません。

Packages > Using Package Members > Apparent Hierarchies of Packages

The hierarchical naming structure for packages is intended to be convenient for organizing related packages in a conventional manner, but has no significance in itself other than the prohibition against a package having a subpackage with the same simple name as a top level class or interface (§7.6) declared in that package.
パッケージの階層的な命名構造は、関連するパッケージを従来の方法で整理するのに便利ですが、あるパッケージが、その中で宣言されているトップレベルのクラスまたはインターフェース( §7.6 )と同じ単純名のサブパッケージを持つのを禁止する以外に、それ自体に意味はありません。

For example, there is no special access relationship between a package named oliver and another package named oliver.twist, or between packages named evelyn.wood and evelyn.waugh. That is, the code in a package named oliver.twist has no better access to the classes and interfaces declared within package oliver than code in any other package.
例えば、oliver というパッケージと oliver.twist というパッケージとの間、または evelyn.woodevelyn.waugh というパッケージとの間には、特別なアクセス関係はありません。つまり、oliver.twist というパッケージ内のコードは、oliver パッケージ内で宣言されているクラスやインターフェースに対して、他のパッケージ内のコードよりも優れたアクセス権限を持つことはありません。

Chapter 7. Packages and Modules > 7.1. Package Members

例えば次のように、異なるパッケージの PrintLogic を呼び出す Main クラスがあった場合、一見ディレクトリ構成上は、MainPrintLogic の上位のクラスのように見えてしまいます。しかし、たとえディレクトリ構成上はそうであったとしても、パッケージが異なればそこに親子関係や階層構造は存在せず、単に完全修飾クラス名によって識別された同列の二つのクラスが存在するだけであるため、インポート文(または完全修飾クラス名を指定)が必要となります。

Main.java
package com.example;

import com.example.logic.PrintLogic;

public class Main {
    public static void main(String[] args) {
        var printLogic = new PrintLogic();
        printLogic.greet();
    }
}
PrintLogic.java
package com.example.logic;

public class PrintLogic {
    public void greet() {
        System.out.println("Hello World");
    }
}

パッケージは階層を表現せず、クラスローダにとってのみディレクトリ構造と対応させておくことが必要という点について、これはコンパイルの可否からも確認することができます。コンパイラは依存関係が解決できなければエラーを発生させますが、ディレクトリ構成がパッケージと揃っていなくても、完全修飾クラス名やインポート宣言が正しければ、コンパイル自体は成功します。この場合、以下のようにjavacコマンドによるコンパイルは成功するものの、javaコマンドによる実行は失敗する、という挙動になります。

パッケージとディレクトリ構成を..合わせた場合合わせない場合
javacコマンドによるコンパイル正常終了正常終了
javaコマンドによる実行正常終了ClassNotFoundException

例えば次のように、前項 の例で使用したMainクラスを用いて、コマンドプロンプトで開いたディレクトリの下に com\example ディレクトリを作成し、そこに Main.java を配置します。この場合、javacコマンドはディレクトリを含めてソースファイルを指定し、javaコマンドはパッケージを含めて完全修飾クラス名を指定することで、それぞれ問題なく実行できます。

# パッケージと同じ階層のソースファイルをコンパイル
# → コンパイル成功
$ javac com\example\Main.java

# パッケージと同じ階層のクラスファイルに定義されたクラスを対象に実行
# → 実行成功
$ java com.example.Main
Hello World

次に、com\example ディレクトリを作らず、ソースファイルを直下に配置して同じことを行ってみます。すると、javaコマンドによる実行はエラーで失敗しますが、javacコマンドによるコンパイルは問題なく正常に実行できます。このことから、コンパイル時にディレクトリ構成は重要ではなく、パッケージとディレクトリ構成の関連付けは、あくまで実行時にクラスをロードするためのヒントに過ぎないということが分かります。

# パッケージと異なる階層のソースファイルをコンパイル
# → コンパイル成功
$ javac Main.java

# パッケージと異なる階層のクラスファイルに定義されたクラスを対象に実行
# → 実行失敗
$ java com.example.Main
エラー: メイン・クラスcom.example.Mainを検出およびロードできませんでした
原因: java.lang.ClassNotFoundException: com.example.Main

複数クラスの場合も同様です。コンパイル時に依存するクラスの確認が行われるため、前述の例 のようにMainクラスにPrintLogicクラスのインポート文がある場合、依存する全てのクラスが解決できる状態である必要がありますが、全てのファイルを指定して同時にコンパイルすることで対応できます。

# パッケージと同じ階層のソースファイルをコンパイル
# 引数を複数指定し必要なソースファイルを全て同時にコンパイル
# → コンパイル成功
$ javac com\example\Main.java com\example\logic\PrintLogic.java

# パッケージと同じ階層のクラスファイルに定義されたクラスを対象に実行
# → 実行成功
$ java com.example.Main
Hello World
# パッケージと異なる階層のソースファイルをコンパイル
# 引数を複数指定し必要なソースファイルを全て同時にコンパイル
# → コンパイル成功
$ javac Main.java PrintLogic.java

# パッケージと異なる階層のクラスファイルに定義されたクラスを対象に実行
# → 実行失敗
$ java com.example.Main
エラー: メイン・クラスcom.example.Mainを検出およびロードできませんでした
原因: java.lang.ClassNotFoundException: com.example.Main
# パッケージと同じ階層のソースファイルをコンパイル
# 依存する必要なクラスを指定せず単一のソースファイルのみをコンパイル
# → コンパイル失敗(※ただし com\example\logic\PrintLogic.class が既に存在している状態であれば同時でなくてもコンパイルに成功する)
$ javac com\example\Main.java
com\example\Main.java:3: エラー: パッケージcom.example.logicは存在しません
import com.example.logic.PrintLogic;
                        ^
com\example\Main.java:11: エラー: シンボルを見つけられません
        var printLogic = new PrintLogic();
                             ^
  シンボル:   クラス PrintLogic
  場所: クラス Main
エラー2個

これは Amazon S3(Simple Storage Service) のようなオブジェクトストレージの概念に似ています。実際には非構造的でフラットなオブジェクトの集合であるものの、その命名(接頭辞)を階層的表現にすることで、物理的に並列な集合を、人間が見た時に論理的にグループ化して解釈できるようにしています。

オブジェクトストレージは、オブジェクトと呼ばれる非構造化形式でデータを格納および管理するテクノロジーです。

オブジェクトストレージとは何ですか?

Amazon S3 では、プレフィックスを使用してストレージを整理できます。プレフィックスは、バケット内のオブジェクトの論理グループです。プレフィックス値は、バケット内の同じディレクトリに同様のデータを保存するためのディレクトリ名に似ています。

オブジェクトの整理、リスト化、使用

Amazon S3 汎用バケットはフラットな構造であり、ファイルシステムに見られるような階層はありません。ただし、構造を分かりやすくするため、Amazon S3 コンソールでは、オブジェクトのグループ化の方法としてフォルダの概念をサポートしています。コンソールでは、これを行うために、グループ化したオブジェクトに共有名のプレフィックスを使用します。つまり、グループ化したオブジェクトに共通の文字列で始まる名前が付けられます。この共通の文字列 (共有プレフィックス) がフォルダ名です。

フォルダを使用して Amazon S3 コンソールのオブジェクトを整理する

Eclipseのパッケージ・エクスプローラにおいて、デフォルトの表示設定が「Flat」になっているのも、おそらくこのような理由からです。

ディレクトリ構成に合わせて階層的な「Hierarchical」表示に変更することも可能です。

パッケージは、ディレクトリ構成や階層構造、親子関係や包含関係を表すものではない。

注意点1 と 注意点2 の整理

このように考えると、注意点1と注意点2が矛盾しているように見え、混乱してしまうかもしれません。前提としてパッケージは階層構造を持たず、全てがフラットな関係で名前空間を提供しています。その上で、パッケージの命名構造の特性により、人間による捉え方と、クラスローダによる変換、JVMによる解釈とで、同じパッケージ名、完全修飾クラス名であっても、それぞれで見え方が異なっているという整理が分かりやすいかもしれません。

. 区切りのパッケージ名を含む完全修飾クラス名は、人間にとっては論理的にグループ化された分類のように見え、クラスローダにとってはクラスファイルの場所を見つけるためのディレクトリ階層に見えます。しかし実際のところJVMにとっては、. が意味の区切りであるという理解すら不要で、各パッケージ単位には意味を見出さず、完全修飾クラス名とは、それを一意に識別するための . を含む単なる長い名前に過ぎないということです。

パッケージとインポートの要点

パッケージとは
パッケージとは、クラス名の競合を回避するための名前空間のこと。

パッケージの使われ方
ソースコード内で、同名のクラスを区別するために使用される。
プログラム実行時、対象のクラスを指定するために使用される。

パッケージの宣言方法と命名規則
パッケージは、ファイルの先頭に package 文を記述して宣言する。
パッケージ名の先頭は、慣習に従いドメイン名の逆順で命名する。

インポートとは
インポートは、パッケージ宣言の次に import 文を記述して宣言する。
インポートは、完全修飾クラス名を省略して単純な名前で参照できるようにする機能。

同名クラスのインポート
同名のクラスは、パッケージが異なっていても一つしかインポート文を記述できない。
同じクラス内で複数の同名クラスを使用したい場合は、完全修飾クラス名で記述する。

パッケージ全体のインポート
アスタリスク( * )を用いてパッケージ全体をインポートすることができる。

パッケージの注意点
クラスファイルは、パッケージとディレクトリ構成を合わせて配置する必要がある。
パッケージは、ディレクトリ構成や階層構造、親子関係や包含関係を表すものではない。
開発者は、その命名構造により論理的に構造化されているかのように捉えることができる。