サンプルコードで身につける: パッケージ・完全修飾名・import・クラスパス・クラスローダ
解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。
1用語早見表: 名前の付け方と、クラスの探し方
このレッスンの用語の早見表です。前半の4つはソースに書く名前の話、後半の2つは実行時にを探す話です。 は Node と同じ単語ですが、することが違います。 は完全修飾名の略として使われる慣習的な略語で、の語ではありません。PATH は OS の環境変数で、とは別物です。
用語 | 一言で手元では: 何として見えるか近いもの: Node / NestJS で近いものパッケージ | クラスをまとめる名前空間手元では: package com.example.demo;近いもの: フォルダとモジュールのパス(弱い対応。相対パスという考え方が無い)単純名 | パッケージ名を付けない短い名前手元では: List、ArrayList近いもの: import した後の名前完全修飾名 | パッケージ名と単純名を . でつないだ名前手元では: java.util.List。FQCN と略されることもある近いもの: 無い(Node は名前ではなくパスで指す)import | クラスを単純名で書けるようにする宣言手元では: import java.util.List;近いもの: 無い(Node の import は名前が同じだけで別物)クラスパス | .class を探しに行く場所の一覧手元では: java -cp out:lib/x.jar近いもの: node_modules の探し方(弱い対応。当たらない点は本文)CLASSPATH | クラスパスを渡す環境変数手元では: java -cp を書くと、こちらは使われない近いもの: 無いPATH | OS が java などのコマンドを探す場所の一覧クラスパスとは別物手元では: シェルの環境変数 PATH近いもの: Node を使うときと同じ PATHクラスローダ | クラスを探して読み込む役の部品手元では: getClassLoader() で見える近いもの: 対応を確かめていない
2完全修飾名・単純名・パッケージ名を表示する
実行中のクラスに名前を聞いて表示します。getName() が完全修飾名、getSimpleName() がです。String のは で、このパッケージのクラスは import を書かなくても単純名で使えます(自分と同じパッケージのクラスも同じです)。このファイルには が無いので、Main のパッケージ名は空になります。
3どのクラスを、誰が、どこから読み込んだか
クラスパスの値と、3つのクラスを読み込んだを表示します。クラスパスは . (今いるフォルダ)で、自分の Main はそこから app という名前のローダが読み込みました。String や java.sql.Driver は に入っているクラスで、別のローダが JDK の中から読み込みます。クラスパスに無い名前を頼むと になります。
4import を書いても書かなくても、同じ .class になる
import の有無だけが違うソースを4つコンパイルして、 を比べた記録です。import して単純名で書いた a と、import せず完全修飾名で書いた b は同じ内容でした。import だけして使わない c と、import の無い d も同じで、c の .class に Base64 という文字は 0 件、実行時の読み込みのログにも出ません。行番号の情報がずれないよう、b と d には同じ行数のコメントを入れてあります。
# a: import java.util.Base64; と書き、Base64.getEncoder() を使う# b: import を書かず、java.util.Base64.getEncoder() と書く# c: import java.util.Base64; と書くが、どこでも使わない# d: import を書かず、Base64 も使わない## a: javac ok, Demo.class 614 bytes, sha256 3bd0c2bdc5bda77a## b: javac ok, Demo.class 614 bytes, sha256 3bd0c2bdc5bda77a## c: javac ok, Demo.class 404 bytes, sha256 b0e22163e1ff1150## d: javac ok, Demo.class 404 bytes, sha256 b0e22163e1ff1150##### cmp a b (import+simple name vs no import+FQN)IDENTICAL##### cmp c d (unused import vs no import)IDENTICAL##### javap -v c/Demo.class | grep -c Base640...##### run a with -Xlog:class+load | grep Base64[0.009s][info][class,load] java.util.Base64 source: jrt:/java.base[0.009s][info][class,load] java.util.Base64$Encoder source: jrt:/java.baseAQID##### run c (unused import) with -Xlog:class+load | grep -c Base640
5混同しやすいもの: import してあっても、使う行までは探しに行かない
app.Main は lib.util.Helper を import していますが、使うのは引数を渡したときだけです。Helper.class を消しても、引数なしなら最後まで動きました。引数を渡すと、Helper を使う行で になります。Node の import は相手のファイルを読み込みます。Java の import は読み込みをしないので、足りないクラスは使う行に来るまで分かりません。
## java -cp out app.Mainmain startmain endexit=0## java -cp out app.Main xmain startHelper class initializedhello from Helpermain endexit=0## rm out/lib/util/Helper.class ; java -cp out app.Mainmain startmain endexit=0## java -cp out app.Main x (Helper.class is gone)main startException in thread "main" java.lang.NoClassDefFoundError: lib/util/Helperat app.Main.main(Main.java:9)Caused by: java.lang.ClassNotFoundException: lib.util.Helper...