Java入門|クラスライブラリとパッケージの関係

クラスライブラリのパッケージを理解すると、Javaの便利な機能をもっと自然に使いこなせるようになる

ここまで、Javaではクラスをパッケージごとに整理できることや、別のパッケージのクラスを import して使えることを見てきました。こうしたしくみは、自分で作るクラスだけに関係するものではありません。実は、Javaが最初から用意している便利なクラスたちも、同じようにパッケージで整理されています。

Javaには、文字列の処理、ファイルの読み書き、ネットワーク通信、日付の扱い、画面表示など、さまざまな場面で役立つクラスがたくさん用意されています。これらをまとめたものがクラスライブラリです。そして、そのクラスライブラリの中身も、役割ごとにパッケージへ分類されています。

今回は、クラスライブラリとパッケージの関係をテーマに、Javaの標準クラスがどのように整理されているのか、なぜ import が必要になるのか、同じパッケージの複数クラスをまとめて読み込む方法はどうなっているのか、さらに Java 9 以降のモジュールの考え方まで、順番にやさしく整理していきましょう。

クラスライブラリのクラスもパッケージで整理されている

Javaの学習を進めていると、String や Math など、自分では作っていないクラスを自然に使う場面が出てきます。
これらは Java があらかじめ用意しているクラスライブラリのクラスです。

そして大切なのは、これらのクラスもばらばらに置かれているわけではなく、役割ごとにパッケージに分類されているということです。

つまり、自分で作るクラスに対して学んできた

  • パッケージで整理する
  • 必要に応じて import する
  • パッケージ名で区別する

という考え方は、そのままクラスライブラリにも当てはまります。

クラスライブラリの主なパッケージ

Javaの標準クラスライブラリには、よく使われる代表的なパッケージがいくつもあります。
役割ごとに整理すると、次のようになります。

パッケージ名含まれる主な内容
java.lang基本的なクラス
java.io入出力関連のクラス
java.netネットワーク関連のクラス
java.utilユーティリティ関連のクラス
java.math数値計算に関するクラス
java.text数値や日付などの国際化関連のクラス
java.awtウィンドウ部品関連のクラス
java.awt.eventイベント関連のクラス
java.awt.image画像関連のクラス

この表を見ると、クラスライブラリがかなり丁寧に分類されていることがわかります。
たとえば、ファイルの読み書きをしたいなら java.io、便利なコレクションや日付の補助的な機能を使いたいなら java.util、というように、目的ごとに見る場所が変わってくるわけです。

パッケージで整理されていると何がうれしいのか

クラスライブラリのクラスがパッケージで分類されていると、必要な機能を探しやすくなります。
また、同じ名前のクラスが別の用途で存在していたとしても、パッケージ名によって区別できます。

たとえば、大規模なライブラリの中で何百ものクラスがあるとき、全部が同じ場所に置かれていたらかなり混乱します。
でも、役割ごとにグループ分けされていれば、どの分野のクラスなのかが見えやすくなります。

パッケージ整理がない場合パッケージ整理がある場合
必要なクラスを探しにくい用途ごとに探しやすい
名前の衝突が起こりやすいパッケージ名で区別できる
ライブラリ全体が把握しづらい役割ごとに見通しがよくなる

つまり、パッケージはクラスを分類するためだけでなく、大きなライブラリを使いやすくするための仕組みでもあります。

クラスライブラリのクラスを使うときは import が基本になる

クラスライブラリのクラスを利用するときも、これまで学んできたのと同じように、別パッケージのクラスを使うなら import を行うのが基本です。

たとえば、入出力関連のクラスである BufferedReader を使いたいときは、ファイルの先頭に次のように書きます。

import java.io.BufferedReader;

このようにしておけば、コードの中で毎回 java.io.BufferedReader と書かなくても、BufferedReader だけで使えるようになります。

ここでも import の役割は同じです。
つまり、別パッケージにあるクラスを、ファイルの中で使いやすくするための準備です。

なぜ import が必要になるのか

Javaの標準クラスライブラリにはたくさんのクラスがあります。
そのクラスたちはパッケージごとに分けられているので、別パッケージのクラスを使うときには、その場所を明らかにする必要があります。

たとえば BufferedReader は java.io に属しているので、何も準備しなければ、

java.io.BufferedReader

という完全な名前で書かなければなりません。

でも、それではコードが少し見づらくなります。
そこで import を使って先に読み込んでおけば、その後は短いクラス名だけで使えるようになります。

書き方特徴
java.io.BufferedReader完全な名前で明確だが長い
import java.io.BufferedReader; と書いたうえで BufferedReader短くて読みやすい

この点は、自分で作ったパッケージ付きクラスを使うときとまったく同じ考え方です。

java.lang だけは特別

ここで、とても大事な例外があります。
それが java.lang パッケージ です。

java.lang に含まれるクラスは、通常は import を書かなくても使えます。
たとえば、String クラスや Math クラスなどは、普段から特別な import を書かずに使ってきたはずです。

これは、java.lang が Java において特別扱いされているからです。
そのため、java.lang.String や java.lang.Math と毎回書かなくても、単に String や Math で利用できます。

この点を整理すると、次のようになります。

パッケージimport の必要性
java.lang通常は不要
それ以外のパッケージ基本的に必要

つまり、String を import なしで使えていたのは、String が特別な存在だからではなく、java.lang に含まれているからなのです。

同じパッケージの複数クラスを import するときの面倒さ

たとえば java.io のクラスをいくつも使いたいとします。
その場合、必要なクラスを1つずつ import することもできます。

import java.io.BufferedReader;
import java.io.IOException;

この書き方は正しいのですが、クラス数が増えると少し面倒になります。
特に、同じパッケージから何個も使うときは、似たような import 文がたくさん並ぶことになります。

たとえば、入出力関連のクラスを何種類も使う場面では、ファイルの先頭が import 文でかなり長くなることがあります。

* を使うとパッケージ内のクラスをまとめて import できる

同じパッケージのクラスをたくさん使うときは、アスタリスクを使った書き方が便利です。

import java.io.*;

このように書くと、java.io パッケージに含まれるクラスをまとめて利用できるようになります。
毎回クラス名を1つずつ並べる必要がないので、ファイル先頭がすっきりしやすくなります。

この書き方は、特に同じパッケージのクラスを複数使うときに役立ちます。

書き方特徴
import java.io.BufferedReader;1つのクラスだけ読み込む
import java.io.IOException;これも1つだけ
import java.io.*;java.io の複数クラスをまとめて使いやすくする

ここで押さえたいのは、同じパッケージの複数クラスを扱うときに * が便利だということです。

ただし * ではサブパッケージまでは読み込まれない

ここはとても大切な注意点です。
アスタリスクを使って import しても、そのパッケージの下にあるサブパッケージまでまとめて読み込まれるわけではありません

たとえば、

import java.awt.*;

と書いても、java.awt の中のクラスは使いやすくなりますが、サブパッケージである java.awt.image のクラスまでは自動では対象になりません。

もし java.awt.image のクラスも使いたいなら、別に次のような import が必要です。

import java.awt.image.*;

この点はよく誤解されやすいので、表で整理しておきましょう。

import 文利用しやすくなる範囲
import java.awt.*;java.awt のクラス
import java.awt.image.*;java.awt.image のクラス
import java.awt.*; だけjava.awt.image までは含まれない

つまり、サブパッケージは別パッケージとして扱われる、というこれまでの学習内容が、ここでもそのまま生きています。

サブパッケージが別扱いだとわかる例

ここまでパッケージやサブパッケージを学んできた流れを思い出すと、これは自然な話です。
java.awt と java.awt.image は、名前のつながりはありますが、コード上では独立した別パッケージです。

たとえば自分で作るパッケージでも、

  • pa
  • pa.sub

が別パッケージでしたね。

それと同じで、

  • java.awt
  • java.awt.image

も別々に扱われます。

この考え方がわかっていると、なぜ * でサブパッケージまで読み込まれないのかも納得しやすくなります。

図で整理すると見やすい

この図は、左側に Java のクラスライブラリ全体があり、その中に用途別のパッケージが並んでいます。
たとえば java.io は入出力、java.net はネットワーク、java.util は補助的な便利機能というように、役割ごとにクラスが整理されている様子を表しています。

モジュールという上位の考え方

Java 9 以降では、パッケージよりさらに大きな単位として、モジュールという考え方も使われています。
これは、どのパッケージを公開するのか、どの機能を外から利用できるようにするのかを、より大きなまとまりで管理するための仕組みです。

モジュールを使うときは、module-info.java というファイルを用意して、そこにモジュール名や公開するパッケージ、利用するほかのモジュールなどを書きます。

つまり、パッケージがクラスの整理単位だとすると、モジュールはパッケージの整理や公開範囲を管理するさらに上位の単位と考えるとわかりやすいです。

モジュールの役割をやさしく見ると

モジュールは少し難しそうに見えますが、考え方はそこまで複雑ではありません。
大きなプログラムを作るときに、

  • どの機能を外部に見せるか
  • どの機能を内部だけで使うか
  • どのまとまりがどのまとまりを利用するか

を整理するための仕組みです。

これまで学んできたパッケージが、クラスの住所や分類を決めるものだったのに対して、モジュールはそのパッケージ群をさらに管理するもの、と考えるとつながりが見えやすいです。

単位役割
クラス実際の処理を持つ部品
パッケージクラスを整理する単位
モジュールパッケージ群の公開範囲や依存関係を管理する単位

標準クラスライブラリのモジュール

Java 9 以降では、標準クラスライブラリもモジュール単位で整理されています。
たとえば、基本的なクラス群は java.base などのモジュールに含まれています。

一般的な Java の学習や標準ライブラリの利用では、モジュール名を細かく意識せずに使える場面が多いです。
そのため、最初のうちはパッケージと import を理解しておけば十分なことが多いです。

ただし、実用的なライブラリや少し大きな構成を扱うときには、必要なモジュールを指定する場面もあります。
そのため、Java にはパッケージのさらに上にモジュールという考え方がある、ということを知っておくとよいです。

名前のないモジュールという考え方

モジュールを明示していないクラスについては、名前のないモジュールに含まれるという扱いになります。
これは、すべてを最初から厳密なモジュール構成にしなくても Java を使えるようにするための仕組みです。

つまり、モジュールを指定していない普通の学習用プログラムでも、Java の動作としては問題ありません。
そのようなコードは、名前のないモジュールに属していると考えられます。

この考え方があるおかげで、初心者の段階ではモジュールを強く意識しなくても Java を学習しやすくなっています。

ここで押さえたいポイント

ここまでの内容を整理すると、クラスライブラリとパッケージの関係では、次の点が特に大切です。

大事なポイント内容
クラスライブラリのクラスもパッケージで整理されている用途ごとに分類されている
別パッケージのクラスを使うときは import が基本コードを読みやすくできる
java.lang は通常 import 不要String などをそのまま使える
* を使うと同じパッケージの複数クラスをまとめて扱いやすいimport 文を短くできる
* ではサブパッケージまでは含まれない別パッケージとして扱う必要がある
Java 9 以降にはモジュールという上位概念があるパッケージ群をさらに管理する仕組み

クラスライブラリを使いこなすために大切なこと

Javaの学習では、自分でクラスを作ることも大切ですが、すでに用意されているクラスライブラリを上手に使うことも同じくらい大切です。
そして、そのクラスライブラリをうまく使うためには、どのパッケージに何があるのかという視点を持つことが欠かせません。

たとえば、

  • 文字列なら java.lang
  • 入出力なら java.io
  • ネットワークなら java.net
  • 便利機能なら java.util

というように、役割ごとのまとまりを意識できるようになると、必要なクラスを探しやすくなります。

さらに、import の意味がしっかりわかっていれば、コードも読みやすく整理しやすくなります。
パッケージの考え方は、自分のクラスを整理するときにも、標準ライブラリを使うときにも、どちらにもつながる大事な土台になっています。