2015年1月15日木曜日

Java アプリケーションのリソースパス

Java のアプリケーションで画像などのリソースを読み込んで表示に使う場合,読み込み方法に何を使うか,ファイルはどこに置くか,そのときリソースのパスはどのように指定するかについて,まとめました.リソースの読み込みはアプレットではまた別です.
リソースに画像を使う場合,読み込みの方法は,読み込んだ画像は表示に使うくらいで面倒くさいことはしないなら,ImageIcon クラスを使ってしまうのが簡単です.画像のサイズが小さければ,読み込みの時間も気にならない程度です.
Image img; // インスタンス変数

ImageIcon icon = new ImageIcon(リソースパス);
img = icon.getImage();
このとき,どこに置いた画像に対して,どのようにリソースパスを書けばよいのか,いくつかのことが絡んでいたので,参考資料も含めて,覚えておく意味でメモとしました.

フォルダ配置とリソースパス: どうすることにしたか

Eclipse 風に,ソースフォルダとクラスフォルダを分けて,プロジェクトフォルダを起点に次のようにフォルダを構成します.「testproj」は,プロジェクト名,「MainFrame.java」は main メソッドを含むクラス(MainFrame)のソースファイル名,「images」は画像フォルダの名前,「strawberry.png」は画像ファイル名とします.
  |
  +-- ここまでのパス
        |
        +-- testproj (プロジェクトフォルダ)
              |
              +-- bin (コンパイルした結果のクラスファイル用のフォルダ)
              |
              +-- src (ソース用のフォルダ)
              |     |
              |     +-- パッケージのフォルダ列 (無名パッケージならなし)
              |               |
              |               |-- MainFrame.java のファイル
              |               |    (メインメソッドを含むクラスのソースファイル,
              |               |     クラスごとに作れば,その他複数,
              |                     文字コードは utf-8 で)
              |
              +-- images (画像用のフォルダ)
                    |
                    +-- strawberry.png (画像ファイル,複数もあり)
コマンドラインからコンパイル・実行するときは,ワーキングフォルダを testproj(プロジェクトフォルダ)にして,main メソッドを含むクラスの java ファイルから次のようにします.
javac -encoding utf-8 -sourcepath src -d bin src/パッケージのフォルダ列/MainFrame.java 
java -cp bin;. パッケージ名.MainFrame (cp オプションの書き方は Windows用)
java -cp bin:. パッケージ名.MainFrame (cp オプションの書き方は Unix用)
ソースの画像の読み込み部分は,クラス MyClass で実行するとして,次のようにします.
Image img; // インスタンス変数

java.net.URL url = MyClass.class.getResource("/images/strawberry.png");
ImageIcon icon = new ImageIcon(url);
img = icon.getImage();
getResource の手前,クラスリテラルの MyClass.classObject クラスのインスタンスメソッド getClass() に置き替えることができます.
何れにしても,リソースパスは, ImageIcon のコンストラクタには,直接は渡さず,Class クラスの getResource メソッドに渡して URL に変換してから渡します.
このソース部分のまま,jar ファイルで実行できるようにするには,testproj(プロジェクトフォルダ)にマニフェストファイル Manifest.txt を作成し,ワーキングフォルダ testproj(プロジェクトフォルダ)で
jar cvfm 適当なファイル名.jar Manifest.txt images -C bin .
とします.ここでできた jar ファイルには上記画像フォルダも含まれます.
プロジェクトフォルダを持ち歩きながら,Eclipse とコマンドラインを適当に行ったり来たりして作成し,時々プロジェクトのスナップショットを残して,どのスナップショットもその時の状態で動き,最後は jar に丸めて持ち歩いて実行する,という環境に適しています.
ポイントは:
  • リソースフォルダ(上記images)はプロジェクトフォルダの直下に置く.
  • ImageIcon のコンストラクタには,Class クラスの getResource メソッドを使ってリソースフォルダのパスを URL に変換したものを渡す.
  • このようなフォルダ構成で Class クラスの getResource メソッドを使うときは,リソースパスを絶対形式で使う(MyClass.class.getResource("/images/strawberry.png")).
  • コマンドラインからクラスファイルを実行するときは,プロジェクトフォルダで,javacp オプションに,クラスファイルのフォルダ(bin)とプロジェクトフォルダ(.)を指定する
  • jar コマンドで jar ファイルに丸めるときは,リソースフォルダ(images)とクラスファイルをファイルとして指定するが,クラスファイルは C オプションを使って -C bin . のようにして, bin フォルダ自身を jar に含めないようにする
  • Eclipse で実行するときは,クラスパスを java コマンドの cp オプションと同じになるように設定する
ここで,getResource を使って一旦 URL に変換する理由とか,そのとき画像ファイルを絶対パスのように書く理由とか,環境変数の CLASSPATH を設定したらどうなるのかとか,と気になった色々について,以下です.

相対パスを String で

ImageIcon のコンストラクタに new ImageIcon("images/strawberry.png") のように String でリソースの相対パスを渡した場合,フォルダ images はワーキングフォルダ(working directory, curren directory, current working directory)からの相対になり,「ワーキングフォルダまでのパス/images/strawberry.png」が読み込まれます.コマンドラインからクラスファイルを実行する場合,Eclipse から実行する(Run Configuration はデフォルトのみ)場合,いずれも上記のようなフォルダ配置で問題なく実行できます.コマンドラインでは,プロジェクトフォルダがワーキングフォルダになるように移動しておきます.ただし,jar に丸めて実行するとき,リソースが jar ファイルの外になってしまいます.
new ImageIcon("images/strawberry.png") 」のようにした場合,images フォルダを jar ファイルに一緒に丸めても,jar ファイルの中は探せません.Java のクラスファイルを jar に丸めて,ダブルクリックで起動するとすれば,jar ファイルがあるフォルダに images フォルダも置くことになります.jar ファイルをコマンドラインから実行する場合は,ワーキングフォルダに images フォルダを置きます. jar ファイルを持ち歩くとき,images フォルダも忘れずに持ち歩かなければならず,面倒です.
Oracle の Swing チュートリアルの 「How to Use Icons」では,このような使い方を「アプリケーションの一部ではない(not part of the application)」場合と書いてあります.このチュートリアルによれば,一般的には,画像は「アプリケーションの一部として(as part of the application)」提供するもので,その場合は,画像のパスは Class クラスの getResource メソッドで取得すべき,のようです.

getResource メソッド

getResource(String) メソッドは,クラスローダを使って String のリソースパスを URL に変換するものです(「位置に依存しない方法でのリソースへのアクセス 」,原文: 「Location-Independent Access to Resources」).
getResource メソッドは,Class クラス(java.lang.Class日本語)と ClassLoader クラス(java.lang.ClassLoader日本語)にあり,Class クラスの getResource日本語)は引数で渡されるリソースパスに前処理をして,ClassLoadergetResource日本語)メソッドに引き渡し,URL への変換の実際は ClassLoadergetResource メソッドが実行します(ClassLoader のメソッドに委譲する,The Class methods delegate to ClassLoader methods).前処理は,Class クラスの getResource に引数で渡された名前(String 型)の整形であり,次のようにされます.
相対形式(名前が 「/」で始まらない)の場合:
  • パッケージ名(ただし,パッケージ名の区切りの「.」は「/」に変換される)を先頭に付加する.
絶対形式(名前が「/」で始まる)の場合:
  • 先頭の「/」が削除されるのみで,後はそのまま渡される.
絶対形式の先頭の「/」は,パッケージ名を補うかどうか(付けると補わない)の選択に使われています.
ClassLoadergetResource メソッドは,引数(String)の名前をそのまま使って,リソースの URL を作って返します.ClassLoadergetResource を直接使う際は,前処理をプログラム作成者が自分ですることになります.リソースをパッケージの下に置く場合,Oracle のチュートリアルの通りにパッケージを作っていると,パッケージ名は「com.example.region.mypackage」のように一意の修飾子を含むので,結構面倒です.Oracle のオススメは,Class クラスの getResource を使え,のようです.
Class クラスの getResource は,引数に絶対形式を使うとパッケージフォルダ列が追加されないので,上の例のようにプロジェクトフォルダ直下に画像ファイルを置きたいときは,絶対形式でパスを書いて Class クラスの getResource を使うことになります.他方,相対形式でリソースパスを書いた場合,パッケージ名がフォルダ形式で追加されるので,コンパイル結果のクラスファイルと同様に,bin (クラスパスの設定によって bin 以外にもできる)の下のパッケージフォルダの一番下に画像フォルダを置いておく必要があります.Eclipse で実行する場合は,src の方に入れておけば,bin の方へのコピーは Eclipse がしてくれます.
Class クラスの getResource では引数に絶対形式を使ってパッケージ名の追加をさせないようにするだけなので,上記フォルダ構成のようにパッケージの下ではなくプロジェクトフォルダの直下にリソースフォルダを置く場合,次のいずれかのように, ClassLoadergetResource を使った方が素直かもしれません.
java.net.URL url = MyClass.class.getClassLoader().getResource("images/strawberry.png");
java.net.URL url = getClass().getClassLoader().getResource("images/strawberry.png");

Class クラスの オブジェクト

Class クラスの getResource メソッドはインスタンスメソッドなので,Class オブジェクトがターゲットに必要になります.Class オブジェクトを取得するため,クラス階層ルートの Object クラス(java.lang.Object日本語)の getClass()日本語) メソッドを使う,または,クラスリテラル(class literal)を使って,例えば「MyClass」という名前のクラスなら,MyClass.class のようにして,Class オブジェクトを拾います.
ターゲットが MyClassthis ならば,
java.net.URL url = this.getClass().getResource("images/strawberry.png");
java.net.URL url = this.getClass().getResource("/images/strawberry.png");
のように,あるいは,MyClass クラスの名前をクラスリテラルに使って,
java.net.URL url = MyClass.class.getResource("images/strawberry.png");
java.net.URL url = MyClass.class.getResource("/images/strawberry.png");
のようにすることでリソースパスの URL が取得でき,これを使って,
ImageIcon icon = new ImageIcon(url);
img = icon.getImage();
とすることで,アプリケーションのクラスパスから画像を読み出せます.リソースパスの相対形式と絶対形式の違いは上述の通りで,フォルダ配置との関係を以下に書きます.

画像ファイルのパス

Class クラスの getResource(String) の引数を相対形式にした場合と絶対形式にした場合で,画像ファイルのフォルダ images を置く場所が変わります.上で述べたように,引数が相対形式であれば,先頭にそのクラスのパッケージ名が補われるので,得られる URL はパッケージフォルダの下の image フォルダとなります.パッケージ名が長ければ,image フォルダのフォルダ階層が深くなります.絶対形式にした場合は,何も修飾されないので,パッケージフォルダなしの image フォルダとなります.
相対形式の場合のパッケージフォルダ(最上位)の前(上),絶対形式の場合の image フォルダの前(上)は何になるかです.これは,クラスパスから探されます.詳細なドキュメントは,原文「Java Platform, Standard Edition Tools Reference」,日本語「Java Platform, Standard Editionツール・リファレンス」にあります.
クラスパスは,
java コマンドでクラスファイルを実行する場合:
  • cp(または classpath)オプションで指定される
  • 環境変数 CLASSPATH で指定される
  • いずれでもなければ ワーキングフォルダ (この順で優先順位が高い)
java コマンドで jar オプションを使って jar ファイルを実行する場合,または jar ファイルをダブルクリックで実行する場合:
  • jar ファイル自身となり,getResource を使った場合は,画像フォルダが jar ファイルの中で探される
となります.オプションで指定されるクラスパスまたは jar ファイル名が相対形式なら,ワーキングフォルダからの相対です.
したがって,画像フォルダ images を置く場所は,Class クラスの getResource の引数の形式によって,
  • 相対形式の場合は,クラスファイルがあるフォルダ(長いかもしれないパッケージの一番下,無名パッケージならクラスパスのどれか)の下,
  • 絶対形式の場合は,クラスパスのどれかの下
とすることになります.画像フォルダを含めて jar に丸めるときもこの位置関係を崩さないように丸めれば,同じソースで実行できるようになります.
jar ファイルを実行する場合は,クラスパスが jar ファイルになるので,
  • Class クラスの getResource の引数を相対にして,画像フォルダをクラスファイルがあるフォルダに置き,パッケージフォルダを丸めて jar にする
  • Class クラスの getResource の引数を絶対にして,java コマンドのクラスパスに画像フォルダを置いているフォルダを含め,jar に丸めるときはパッケージフォルダの一番上の階層と画像フォルダが同一のレベルになるようにする,
で,同じソースで適当にやりくりできます.
Oracle のサンプルは,パッケージフォルダの中(クラスファイルのある位置)に画像フォルダが入っていることが多いようです(Class クラスの getResource の引数を相対形式で使う).パッケージの中にリソースも入れてしまうと,ソースフォルダとクラスフォルダを分けた上で持ち運び,あっちこっちでリソースも含めて編集してコマンドラインからも実行したいというときに,リソースをクラスフォルダへコピーするか,ソースのパッケージフォルダもクラスパスに入れるかで,なかなか面倒な上に,ソースフォルダをクラスパスに入れるようにするのは授業では使いにくいところがあります.
以上のことから,冒頭のようなフォルダ構成とコマンドを使っています.
最後に Eclipse.

Eclipse のクラスパス

Eclipse でも,相対的な関係が上記のようになるように設定すればよいので,どうということもありませんが,フォルダとパスの設定が GUI なので,どこで何をどうするが画面依存になり,面倒です.長くなったので,次のエントリとします.

0 件のコメント:

コメントを投稿