質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.32%

Java - Jar ファイルの中のリソースの読み込みについて nullを参照してしまう

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 8,442

FoolHotari

score 25

目的

Java で Jar ファイルの中にリソースファイルを入れ、
そのリソースファイルを読み込みたいのですが、
その読み込むクラスと同じ階層のファイルしか読み込むことができません。

一つ下の階層のファイルまで読み込むにはどうすればよいのでしょか。

勉強は Eclipse を使っています。Eclipse で起動したときは下のようなエラーは発生しません。

ファイル構成

![イメージ説明

Out.txt は起動後に生成されます。

ファイルの内容

  • same.txt   <= same
  • first.txt     <= first
  • second.txt <= second
  • third.txt    <= third

それぞれのファイル名が一文ずつ入力されています。

エラーメッセージ

Exception in thread "main" java.lang.NullPointerException
             at java.io.Reader.<init>(Reader.java:78)
             at java.io.InputStreamReader.<init>(InputStreamReader.java:97)
             at step16.Test.<init>(Test.java:23)
             at step16.Test.main(Test.java:45)


生成した Jar ファイルをコマンドプロンプトで起動しました。
リソースファイルが見つからないようです。

ソースコード

package step16;

import java.io.*;

public class Test {
    Test(String name){
        try {
            File f            = new File("Out.txt");
            FileWriter fw     = new FileWriter(f,true);
            BufferedWriter bw = new BufferedWriter(fw);
            PrintWriter pw    = new PrintWriter(bw);

            InputStream is        = getClass().getResourceAsStream(name);
            InputStreamReader isr = new InputStreamReader(is,"UTF-8");
            BufferedReader br     = new BufferedReader(isr);

            while (true){
                String temp = br.readLine();

                if (temp==null){
                    break;
                }

                pw.println(temp);
            }

            pw.close();
            br.close();
        } catch (IOException e) {
        }
    }
    @SuppressWarnings("unused")
    public static void main(String[] args){
        Test t1=new Test("same.txt");             //同じ階層のファイル
        Test t2=new Test("first\\first.txt");     //一つ下の階層のファイル
        Test t3=new Test("../second.txt");        //一つ上の階層のファイル
        Test t4=new Test("../third\\third.txt");  //一つ上の階層にあるディレクトリにあるファイル
    }
}

実行結果

Eclipseで起動
Out.txt の内容は 

  1. same 
  2. first 
  3. second 
  4. third

でした。

Test.jar にエクスポートして起動(ダブルクリックやコマンドプロンプトから)
Out.txt の内容は

  1. same

でした。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+1

https://docs.oracle.com/javase/jp/8/docs/technotes/guides/lang/resources.html内
・「位置に依存しない方法でのリソースへのアクセス」
・・「java.lang.Classのメソッドの使用」内の↓辺りではないかと。。。

『上記URLより引用-ここから』
getResourceおよびgetResourceAsStreamメソッドは、指定された名前のリソースを検索します。これらのメソッドは、指定された名前のリソースが見つからないとnullを返します。指定されたクラスに関連するリソースを検索するための規則は、そのクラスのClassLoaderによって実装されます。Classのメソッドは、命名規則の適用後にClassLoaderメソッドに委譲されます。リソース名が「/」で始まる場合は、その名前がそのまま使用されます。そうでない場合は、すべてのピリオド(.)がスラッシュ(/)に変換されてから、パッケージ名が先頭に付加されます。
『上記URLより引用-ここまで』

ということで、ClassLoaderを使った例は検索すれば、多々見つかるので、
とりあえず、提示されたコードをエラー無く
java -classpath Test.jar top.third.step16/Test
するため、下記の様にしてみました。

※first\\firstはfirst/firstへ変更。
※t3,t4はクラストップからのパスへ変更。

src
 |-top
 |  |-third
 |  |  |-step16
 |  |  |  |-first
 |  |  |  |  |-first.txt
 |  |  |  |-Test.java
 |  |  |  |-same.txt
 |  |  |-third.txt
 |  |-second.txt
    public static void main(String[] args){
        Test t1=new Test("same.txt");             //同じ階層のファイル
        Test t2=new Test("first/first.txt");      //一つ下の階層のファイル
        Test t3=new Test("/top/second.txt");      //一つ上の階層のファイル
        Test t4=new Test("/top/third/third.txt"); //一つ上の階層にあるディレクトリにあるファイル
    }

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/03/01 13:19

    main メソッドを訂正したところ、期待通りに読み込まれました。
    ありがとうございます。

    キャンセル

+1

HouraijiICさんの回答の通りかと思います。

Javaのclass loaderのAPIドキュメントを見ても今一つ詳しく書いてくれてはいないですね。

Class#getResourceはClassLoader#getResourceに移譲されるのですが、その際に相対パスで指定したリソース名は絶対パスに変換されます。指定リソース名を絶対パスに変換する規則では「一つ上の階層を".."で解釈する」といったようなことはしてくれず単純にパッケージ名(の"."を"/"に置き換えたもの)へ指定名を連結して生成されます。よって次のようなものがリソースの絶対パスとして解釈されてしまいます。

"step16/first\first.txt"
"step16/../second/second.txt"

上記はjar内ではなくWindowsのディレクトリー上に直接置かれたクラスに対するClassLoaderではWindowsのファイルパスとして解釈され/\もディレクトリーの区切りとして解釈され..は単にそういう名前のディレクトリーとして解釈されます。ご存知のようにWindowsでもLinuxでも".."は一つ上のディレクトリーを表す単なるディレクトリーなので質問者さんが意図したとおりのファイルを見つけることができます。しかしLinux上で同様のことをすると\はディレクトリーの区切りとはみなしてくれないため同様のトラブルが発生すると思います。

またjarファイルの中にあるクラスのClassLoaderは、指定されたパスをjava.util.jar.JarFile#getJarEntryに渡すパスとして解釈します。jarファイルのエントリー名はディレクトリーに似た階層を表現できはしますがそこには「..を一つ上の階層として解釈する」といったような仕様はなく指定した名前と単純比較されるだけです。このためご質問のような結果となります。

以上よりgetResourceに指定する名前はClassLoaderが解釈できるような名前でなくてはならないことを意識し、どのような場所に置かれたクラスでも同様にアクセスできるようにするためには/を区切りに用い、..は用いないようにするのがよいといえるでしょう。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/03/01 13:24

    今後は ClassLoader で .. を使用しないようにします。
    ありがとうございます。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.32%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る