PR

Java:package(import)の基本を3分で整理

Java

Javaのパッケージ(package)は「関連するクラスやインターフェースを整理し、名前の衝突を防ぐためのフォルダのような仕組み」です。Javaのプログラムが大規模になると、たくさんのクラスやインターフェースが必要になってきます。これらを整理せずに置いておくとコードがごちゃごちゃしてきて管理が非常に難しくなります(あのクラスはどこにあったっけ・・・?このクラス名は有効?すでに使用済・・・?など)。

そこでJavaでは「package:パッケージ」という仕組み使って、関連するクラスやインターフェースをまとめて整理し、必要に応じてパッケージを後からインポートして利用する方式を採用します。

パッケージには、1:標準で提供されるもの、2:他の人から提供されるもの、3:独自に作成するものなど、いくつかの種類があります。

このページではJavaにおけるパッケージとは何か?実際の使い方や作り方までを網羅的にわかりやすくご説明します。

スポンサーリンク

Javaのパッケージとは?わかりやすく

パッケージ(package)はクラスやインターフェースを1つにまとめて整理したものと理解すればOK。そのうえで、単にまとめるだけではなく、様々な目的で利用される性質を持つ!ということを冒頭でご説明しておきます。

まず初めにパッケージの基本的な仕組みと、その利用目的について簡単にご説明しておきます。

パッケージの役割1:クラスやインターフェースの整理

Javaのプログラムが大規模になると、多くのクラスやインターフェースが生成されるようになります。パッケージはこれらを論理的なグループとしてまとめ管理しやすくするために用いられます。

参考 クラスとは? / インターフェースとは?

例えば、java.utilというパッケージには、コレクションフレームワークやユーティリティクラスが含まれていますがこのように同じ目的のクラスを1つのグループにまとめるために使われるというのがパッケージの第1の役割です。

パッケージの役割2:名前空間の提供

第2の役割は名前空間を提供するということ。名前空間というと難しく聞こえますが、要するに同じ名前のクラスを異なるパッケージに同時に存在させることを可能にするということです。

基本的に同じクラス名は一度使ったら別のソースコードでは利用できません。が、パッケージを分ければ利用可能!ということです。

例えば、Javaには日付を扱うためのクラスが2つあります。1つはjava.utilパッケージに属するDateクラス、もう1つはjava.sqlパッケージに属するDateクラスこれらは同じ名前のクラスですが、異なるパッケージに属しているため、1つのプログラムに同居することが可能になっています。

  • java.util.Date: 一般的な日付と時間を扱うためのクラス。
  • java.sql.Date: データベースの日時型を扱うためのクラス。
import java.util.Date;  // 一般的な日付を扱うクラスをインポート
import java.sql.Date;   // データベースの日時型を扱うクラスをインポート

public class DateExample {
    public static void main(String[] args) {
        // java.util.Dateを使って現在の日付と時間を取得
        Date utilDate = new Date();
        System.out.println("java.util.Date: " + utilDate);

        // java.sql.Dateを使って特定の日付を設定
        Date sqlDate = new Date(System.currentTimeMillis());
        System.out.println("java.sql.Date: " + sqlDate);
    }
}

このように、パッケージは名前空間を提供し、同じ名前のクラスが異なるパッケージに存在することを可能にする、というのがパッケージの2つ目の役割。

ポイント JVMはパッケージ名とクラス名を合わせた完全修飾名でクラスを指定する

JVMがクラスを読み込むときの仕組みは以下の通り。(この動きを知っていると、パッケージの役割がより鮮明度が高く理解できるかと思います。)

  1. JVMによるクラスの読み込み
    • 名前の解決: ソースコードでは、クラスは「java.lang.String」のようにドットで区切られています。JVMはこれを元に、どのパッケージのどのクラスかを特定します。
    • 内部表現への変換: JVM内部では、この完全修飾名が「java/lang/String」のようにスラッシュ(/)で区切られた形式に変換されます。この形式は、ファイルシステム上のディレクトリ構造(例えば、java/lang/String.class)と対応しています。
    • クラスローダーの役割: クラスローダーが、この内部表現を用いて適切な場所(例えばJARファイルやディレクトリ)からクラスファイルを探し、メモリに読み込みます。
  2. なぜこの方法が採用されているか
    • 効率的な検索: 内部表現に変換することで、ファイルシステム上のフォルダ構造と一致し、効率よくクラスファイルを見つけられます。
    • 一意の識別: パッケージ名を含めた完全修飾名により、同名のクラスが異なるパッケージに存在しても混乱なく識別できます。

このように、JVMはクラスを読み込む際にパッケージ名とクラス名を合わせた完全修飾名を内部的に使用し、内部表現に変換して効率よくクラスファイルを検索・読み込みする仕組みを採用しています。

パッケージの役割3:アクセス制御

第3の役割はアクセス制御です。Javaのパッケージはアクセス修飾子参考 アクセス修飾子とは?)と組み合わせることで、アクセス制御をより細かく管理するために使われます。

パッケージ自体が特定のアクセス修飾子を持つわけではありませんが、アクセス修飾子とパッケージを組み合わせることで、クラスやメンバー(フィールドやメソッド)のアクセス範囲を決定します。

アクセス修飾子同一クラス同一パッケージ内の他のクラスサブクラス(同一パッケージ)サブクラス(異なるパッケージ)他のパッケージのクラス
public
protected〇(※1)
デフォルト
private

※1: protectedメンバーは、異なるパッケージのサブクラスからアクセスする場合、サブクラス内でthisを使ってアクセスする必要がある。

サンプルコード アクセス修飾子とパッケージの関係の簡易サンプル

  • パッケージAのクラス
// パッケージAのクラス
package com.example.packageA;

public class ClassA {
    public String publicField = "Public Field";
    protected String protectedField = "Protected Field";
    String defaultField = "Default Field";  // デフォルトアクセス(パッケージプライベート)
    private String privateField = "Private Field";

    public void showFields() {
        System.out.println("publicField: " + publicField);
        System.out.println("protectedField: " + protectedField);
        System.out.println("defaultField: " + defaultField);
        System.out.println("privateField: " + privateField);
    }
}
  • 同じパッケージ内のクラス
// パッケージAの別のクラス
package com.example.packageA;

public class ClassB {
    public void accessFields() {
        ClassA obj = new ClassA();
        System.out.println(obj.publicField);     // アクセス可能
        System.out.println(obj.protectedField);  // アクセス可能
        System.out.println(obj.defaultField);    // アクセス可能
        // System.out.println(obj.privateField); // アクセス不可(コンパイルエラー)
    }
}

ClassBは、ClassApublicprotected、およびデフォルト(パッケージプライベート)フィールドにアクセスできますが、privateフィールドにはアクセスできません。

  • 異なるパッケージのクラス
// パッケージBのクラス
package com.example.packageB;

import com.example.packageA.ClassA;

public class ClassC extends ClassA {
    public void accessFields() {
        ClassA obj = new ClassA();
        System.out.println(obj.publicField);     // アクセス可能
        // System.out.println(obj.protectedField);  // アクセス不可(インスタンス経由の場合)
        // System.out.println(obj.defaultField);    // アクセス不可
        // System.out.println(obj.privateField);    // アクセス不可

        System.out.println(this.protectedField); // アクセス可能(サブクラス経由の場合)
    }
}

ClassCは、ClassApublicフィールドにアクセスできます。同様に、ClassCClassAのサブクラスであるため、protectedフィールドにもアクセスできますが、defaultおよびprivateフィールドにはアクセスできません。

パッケージはプログラムを1つにまとめるだけでなく、名前空間の提供とアクセス制御も同時に行っている!ものだとおさえておきましょう。

まとめ Javaのパッケージの役割・必要性

  1. クラス名の衝突を回避できる
    同じクラス名を使いたい場合でも、パッケージを異なるものにすれば衝突しにくくなります。
  2. ソースコードを整理しやすい
    論理的に関連するクラスをまとめることで、コードの見通しが良くなり、保守・管理が楽になります。
  3. アクセス制御を細かく設定できる
    同一パッケージ内かどうかでアクセス修飾子を調整できるため、クラスやメソッドの公開範囲を制御しやすくなります。
  4. 再利用性や共有が容易になる
    パッケージごとにまとまったライブラリとして扱えるため、他のプロジェクトで再利用しやすくなります。

で、このパッケージは自分で作るものもあれば、誰かから提供されるものもあります。続いてはパッケージの種類についてご説明します。

パッケージの種類

Javaのパッケージは大きく分けて以下4つに分類されます。ザックリいえば、①誰かが作ってくれたもの②自分で作ったもの の2種類ということになるのですが、ここでは正式に以下4分類をご紹介します。

1. 標準パッケージ(Standard Packages)

標準パッケージは、Java開発キット(JDK)に含まれており、基本的な機能やAPIを提供するもの。これらのパッケージはJavaプログラムの基盤を構成し、多くの一般的な操作に使用されます。

ポイント 標準パッケージの具体例

  1. java.lang
    • Javaの基本的なクラスをまとめたもの。例: String, Math, Integerなど。
  2. java.util
    • コレクションフレームワークやユーティリティクラスをまとめたもの。例: ArrayList, HashMap, Dateなど。
  3. java.io: 入出力操作に関するクラスをまとめたもの。例: File, InputStream, OutputStreamなど。

2. 拡張パッケージ(Extension Packages)

拡張パッケージは、標準ライブラリには含まれないが、Javaプラットフォームによって公式に提供される追加の機能を含むパッケージjavaxで始まることが多く、特定の用途に対応したクラスやインターフェースをまとめたものです。

ポイント 拡張パッケージの具体例

  1. javax.servlet
    • サーブレットAPIを提供し、Webアプリケーションの開発に使用される。例: HttpServlet, ServletRequest, ServletResponseなど。
  2. javax.swing
  3. javax.xml
    • XML処理に関するクラスを提供。例: DocumentBuilder, XPath, Transformerなど。

3. カスタムパッケージ(Custom Packages)

カスタムパッケージは、開発者が独自に作成するパッケージです。プロジェクト固有のクラスやインターフェースを整理し、再利用性を高めるために使用されます。

詳しくは後述しますが、以下のようにクラスファイルの最初の個所でpackageキーワードを用いて宣言します。

package com.example.myapp;

public class MyClass {
    // クラスの内容
}

4. サードパーティパッケージ(Third-Party Packages)

サードパーティパッケージは、外部のライブラリやフレームワークに含まれるパッケージです。これらのパッケージは、特定の機能や拡張機能を提供し、Javaエコシステムの一部として広く利用されています。

以下のようなcom.google.gson: JSON処理のためのGsonライブラリなどが一例です。

import com.google.gson.Gson;

public class JsonExample {
    public static void main(String[] args) {
        Gson gson = new Gson();
        String json = gson.toJson(new Person("John", 30));
        System.out.println(json);
    }
}

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

上記分類を頭に入れたところで、ここからはカスタムパッケージの作り方と、パッケージの使い方についてご説明します。

カスタムパッケージの作り方

ここからは、カスタムパッケージを作成する手順を順を追って1つ1つ説明していきます。

ステップ1:パッケージの宣言

カスタムパッケージを作成するには、クラスファイルの最初にpackageキーワードを利用してパッケージを宣言します。パッケージ名は通常、逆ドメイン名形式を使用して一意にします。例えば、com.example.myappというパッケージを作成する場合、以下のように宣言します。

package com.example.myapp;

public class MyClass {
    public void sayHello() {
        System.out.println("Hello from MyClass in com.example.myapp package!");
    }
}

Javaでパッケージを宣言するときは、必ずソースファイルの先頭行で宣言する必要があります。

以下のようにimport文と順番を逆にしてしまうとコンパイルエラーが発生するので要注意。

import com.example.packageA.ClassA;
package com.example.packageB;

public class ClassC {
    // sample
}

ステップ2:ディレクトリ構造の作成

次に、パッケージ名に対応するディレクトリ構造をプロジェクト内に作成します。パッケージcom.example.myappに対応するディレクトリ構造は以下の通り。

project_root/
└── src/
    └── com/
        └── example/
            └── myapp/
                └── MyClass.java
  1. ディレクトリ構造との対応
    パッケージ名はディレクトリ構造と対応させる必要があります。例えば com.example.utils というパッケージ名であれば、ソースコードは com/example/utils というフォルダ構造の中に配置しなくてはなりません。
  2. ディレクトリ構成とパッケージ名の不一致に注意
    もしソースコードが本来のパッケージ構造と異なる場所に置かれていた場合、コンパイルエラーや実行時のクラスロードエラーの原因となります。開発現場では、ビルドツールやIDE(統合開発環境)がプロジェクト構成とパッケージを整合させてくれる場合が多いですが、手動で管理するときには注意が必要です。
  3. パッケージ階層を設計する意義
    大規模開発においては、ソースを整理し拡張しやすい構造にするために、適切なパッケージ階層を設計することが重要です。パッケージ名の付け方は会社やプロジェクトごとにガイドラインがある場合が多く、一般的には逆ドメイン名(com.example.project など)をルートにした階層構造がよく利用されます。

パッケージ名に特段の決まりはありません。ルールは、ソースファイルを配置するディレクトリと構造を対応させるということだけなので、必ずしもドメイン名を逆にした文字列ではなくてもOKではあります。

一方、Javaのパッケージ名はドメイン名の逆順で作成する!というのがもはや暗黙の了解になっているためパッケージ名を付けるときはこの無難な命名にするのがよいでしょう。

ステップ3:実際のコードを作成する

次に、カスタムパッケージを使うクラスを作成し、これらのクラスを他のクラスからインポートして使用します。インポートを行う場合はimportキーワードを利用します。

importキーワードの使い方は後ほど再度詳しくご説明します。

サンプルコード カスタムパッケージのクラス

// ファイル: src/com/example/myapp/MyClass.java
package com.example.myapp;

public class MyClass {
    public void sayHello() {
        System.out.println("Hello from MyClass in com.example.myapp package!");
    }
}

サンプルコード カスタムパッケージを使用するクラス

// ファイル: src/com/example/anotherpackage/Main.java
package com.example.anotherpackage;

import com.example.myapp.MyClass;

public class Main {
    public static void main(String[] args) {
        MyClass myClass = new MyClass();
        myClass.sayHello();
    }
}

ステップ4:パッケージをコンパイルして実行

最後にカスタムパッケージを含むプロジェクトをコンパイルします。プロジェクトのルートディレクトリで以下のコマンドを実行して、すべてのJavaファイルをコンパイルします。

javac -d bin src/com/example/myapp/MyClass.java src/com/example/anotherpackage/Main.java

-d binオプションは、コンパイルされたクラスファイルをbinディレクトリに出力することを指定しています。

次に、コンパイルされたクラスを実行します。

java -cp bin com.example.anotherpackage.Main

このコマンドは、クラスパス(-cp bin)を指定して、Mainクラスを実行します。出力は以下のようになります。

Hello from MyClass in com.example.myapp package!

まとめると、カスタムパッケージを作成する手順は以下の通り。

  1. クラスファイルの最初にパッケージを宣言する。
  2. パッケージ名に対応するディレクトリ構造を作成する。
  3. カスタムパッケージのクラスを作成し、他のクラスからインポートして使用する。
  4. プロジェクトをコンパイルし、実行する。

パッケージの使い方/インポート方法

最後にパッケージの使い方についてご説明しておきます。

パッケージはimport文でインポートすることで簡単に利用できるようになります。

import文の使い方

他のパッケージのクラスを使用するためには、import文を使ってクラスをインポートします。import文ではパッケージ名とクラス名を指定します。

以下は、java.utilパッケージのArrayListクラスをインポートして利用する方法です。ドットで区切ったときの最後の部分がクラス名であり、それ以前がパッケージ名となります。

import java.util.ArrayList;

public class ImportExample {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("Hello");
        list.add("World");
        System.out.println(list);
    }
}

ポイント import文

  • 異なるパッケージのクラスを使う場合は import が必要
    例えば com.example.main.Main から com.example.utils.Helper を使いたい場合は、import com.example.utils.Helper; を追加する必要があります。
  • java.lang パッケージのクラスは特別に import 不要
    StringMath など、java.lang パッケージに属するクラスは自動的に利用できます。

基本的には、同じパッケージ内なら import 不要、異なるパッケージなら import 必要 というルールで覚えておけばOKです。

Tips:名前の衝突を避ける方法

異なるパッケージに同じ名前のクラスが存在する場合、名前の衝突が発生することがあります。このような場合には、クラスの完全修飾名(パッケージ名を含むクラス名)を使用することで、名前の衝突を避けることができます。

以下の例では、java.util.Datejava.sql.Dateの両方を使用していますが、名前の衝突を避けるために、完全修飾名を使用しています。

import java.util.Date;

public class DateExample {
    public static void main(String[] args) {
        // java.util.Dateを使用
        Date utilDate = new Date();
        System.out.println("java.util.Date: " + utilDate);

        // java.sql.Dateを使用
        java.sql.Date sqlDate = new java.sql.Date(System.currentTimeMillis());
        System.out.println("java.sql.Date: " + sqlDate);
    }
}

Tips:ワイルドカード(*)を使ったインポート

パッケージ内の複数のクラスをインポートする場合、ワイルドカード(*)を使用することで、パッケージ内のすべてのクラスを一度にインポートすることが可能です。

必要なクラスだけを個別にインポートする方がコードの可読性が向上しますが、一応Tipsとしてご紹介。

以下は、java.utilパッケージ内のすべてのクラスをインポートする例です。

import java.util.*;

public class WildcardImportExample {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("Hello");
        list.add("World");
        System.out.println(list);

        HashMap<Integer, String> map = new HashMap<>();
        map.put(1, "One");
        map.put(2, "Two");
        System.out.println(map);
    }
}

import文で「*」を使うと、そのパッケージ内にある全てのクラスやインターフェースが利用可能になるという意味ですが、実際にインポートされるのは、あくまでもそのパッケージに含まれるクラスやインターフェースだけという点に注意が必要です。

  • サブパッケージは含まれない
    例えば、import java.util.*; と書いた場合、java.utilパッケージ内のクラスはすべて利用可能になりますが、java.util.concurrentなどのサブパッケージ内のクラスは自動的にはインポートされません。
  • 必要なクラスだけがロードされる:
    コンパイル時には名前解決のために全てが候補として認識されますが、実際にメモリに読み込まれるのは、プログラムで実際に使用されているクラスだけです。

このように、「*」を使っても、対象となるのは指定したパッケージに含まれるクラスやインターフェースだけであり、サブパッケージやその他の要素は含まれません。

無名パッケージ(default package)

無名パッケージ(default package)とは、Javaソースファイルに package 宣言を書かずにクラスやインターフェースを定義したとき、暗黙的に属するパッケージのことです。下記のように package 宣言を記述しないで書いたソースファイル内のクラスは、すべて無名パッケージに含まれます。

// パッケージ宣言なし
public class Sample {
    // ...
}

無名パッケージの特徴・注意点

  1. 原則として同一ソースディレクトリのみ有効
    無名パッケージ内のクラスは、同じディレクトリに置かれたクラス同士からのみ参照(import)できます。複数のディレクトリに無名パッケージのクラスがあっても、それら同士を参照し合うことはできません。
  2. 規模拡大に向かない
    無名パッケージは可視性が極めて限定的であり、複数のパッケージ間でコードを整理することが難しいため、大規模なプロジェクトには向いていません。小規模なサンプルコードや学習用のコードなど、パッケージ分けを意識しないケースで一時的に使われることが多いです。
  3. IDEやビルドツールとの相性
    一般的なIDE(Eclipse、IntelliJ IDEA、VS Codeなど)やビルドツール(Maven、Gradle)を使う場合、無名パッケージ内のクラスが存在するとソース管理が煩雑になりやすいです。プロジェクト開発時には通常、命名したパッケージを使うことを推奨します。
  4. アクセス制御の面
    無名パッケージは、パッケージ単位でのアクセス制御が難しいという面があります。特定のクラスだけを狙ってアクセス制限をかけるといったパッケージスコープの活用ができないので、アクセス制御の観点でもあまり使われません。

サブパッケージとは?

サブパッケージは、基本的なパッケージの「下位」にあるパッケージです。見た目の階層構造はあるものの、実際には親パッケージとは独立した別のパッケージとして扱われます。具体的に見ていきましょう。

サブパッケージの構造

  • 例:
    java.util というパッケージがあるとします。この中にさらに細かく分けられたパッケージとして java.util.concurrent が存在します。ここで java.util.concurrentjava.util のサブパッケージと呼ばれます。
  • 階層的な名前付け:
    サブパッケージはドット(.)で区切られた名前で表現され、階層的な意味を持っているように見えます。

サブパッケージと親パッケージの違い

  • 独立性:
    親パッケージをインポートしても、そのサブパッケージ内のクラスは自動的にはインポートされません。
    たとえば、import java.util.*; としても、java.util.concurrent 内のクラスは利用できず、個別にインポートする必要があります。
  • 管理と整理:
    プログラムが大きくなると、機能ごとにクラスを整理する必要があります。サブパッケージを使うことで、関連するクラスをさらに細かくグループ分けし、見通しを良くすることができます。

注意点とTips

  • インポートの注意:
    親パッケージの「*」を使ったインポートはサブパッケージには適用されないので、サブパッケージ内のクラスを使う場合は、個別にインポートするか、サブパッケージ自体を「*」でインポートする必要があります。
  • 名前の階層はあくまで見た目:
    サブパッケージは名前に階層があるだけで、親パッケージとの「継承関係」や「包含関係」があるわけではありません。各パッケージは完全に独立して管理されます。
  • 整理のメリット:
    サブパッケージを使うことで、コードが増えてもどこにどの機能があるかが分かりやすくなり、保守性が向上します。例えば、並行処理に関するクラスは java.util.concurrent にまとめ、その他のユーティリティは java.util にまとめるといった具合です。

サブパッケージは、プログラムを機能や目的ごとに整理するための便利な手法です。親パッケージと見た目で階層構造になっていますが、実際には別々のパッケージとして扱われるため、インポートの際には注意が必要です。こうした特徴を理解することで、より効率的にJavaのコードを管理・整理できるようになります。

タイトルとURLをコピーしました