Экспортированный JAR не может прочитать локальный файл

В моем приложении Java есть локальный файл под названием «классы» (простой List<String>) в папке верхнего уровня под названием «активы». Когда я запускаю приведенный ниже код из Eclipse, файл читается без проблем.

try { // read in classes from local file
    ObjectInputStream inputStream =
            new ObjectInputStream(new FileInputStream("assets\\classes".replace("\\", "/")));
    listOfClasses = (List<String>) inputStream.readObject();
    System.out.println("*** Reading local copy of classes file ***");
    inputStream.close();
} catch (FileNotFoundException e) { e.printStackTrace();
} catch (ClassNotFoundException e) { e.printStackTrace();
} catch (IOException e) { e.printStackTrace(); }

Когда я экспортирую свое приложение в виде JAR-файла и запускаю его на другом компьютере, я получаю java.io.FileNotFoundException в 3-й строке. Я добавил папку «assets» в путь сборки, поэтому она должна быть в JAR.

Кто-нибудь знает, что здесь не так?


person Mark Cramer    schedule 21.08.2016    source источник
comment
Что вы подразумеваете под экспортом своей программы в виде JRE? JRE обычно означает программу, которую вы используете для запуска своей Java-программы, например. грамм. команда java является частью JRE. Я также не понимаю, какое это имеет отношение к FileInputStream, который является просто старой программой для чтения файлов, которая работает так же, как и любая другая функция открытия файлов.   -  person Sergei Tachenov    schedule 21.08.2016
comment
JRE должен быть jar, а ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("assets\\classes".replace("\\", "/"))); должен быть ObjectInputStream inputStream = new ObjectInputStream(getClass().getResourceAsStream("/assets/classes")); в коде, чтобы работать в jar-файле, как предполагалось.   -  person guleryuz    schedule 21.08.2016
comment
Это JAR, а не JRE. Извини за это. Итак, да, я иду Экспорт -> Запускаемый файл JAR -> и т. д. ObjectInputStream inputStream = new ObjectInputStream(getClass().getResourceAsStream("/asset‌​s/classes")); однако выдает NullPointException, когда я развлекаюсь в Eclipse.   -  person Mark Cramer    schedule 21.08.2016


Ответы (1)


FileOutputStream просто открывает локальный файл. Он должен быть абсолютным (/home/user/... или c:\temp\...) или относительным относительно текущего каталога. В Windows, например, это каталог, содержащий JAR по умолчанию.

Если вы хотели упаковать файл classes в JAR, вам следует использовать getResourceAsStream, как упоминал @guleryuz. Путь должен быть либо абсолютным, начиная с корня CLASSPATH (то есть корня файла JAR), например "/asset‌​s/classes", либо (лучше) относительно пакета вызывающего класса.

Говоря о классах, я бы настоятельно не рекомендовал использовать getClass(), потому что это полиморфный метод. Есть два способа получить класс: вызвав getClass() для объекта или обратившись к статическому class «полю». Если у вас есть экземпляр MyClass с именем myObject, то myObject.getClass() и MyClass.class по сути одно и то же. Но! Если ваш класс переопределен классом из другого пакета, скажем, TheirClass, то для экземпляра TheirClass getClass() вернет TheirClass.class, даже если вы вызываете getClass() из своего кода. Таким образом, если ваш класс не объявлен как final, использовать getClass().getResource... всегда плохая идея, потому что вы никогда не знаете, когда кто-то переопределит ваш класс. И вы не должны знать или даже заботиться!

Учитывая все это, скажем, у вас есть класс с именем MyClass в пакете с именем my.package. JAR может иметь следующую структуру:

my/
    package/
        MyClass.class
        assets/
            classes

Тогда вызов MyClass.class.getResourceAsStream("assets/classes") должен помочь. В качестве альтернативы это может быть MyClass.class.getResourceAsStream("/my/package/assets/classes"). Обратите внимание, что абсолютный путь не начинается с каталога проекта, где у вас может быть src или src/main или что-то другое, что использует ваша система сборки. Он относится к корню ПУТИ К КЛАССУ (верно, абсолютный путь относительный к корню ПУТИ К КЛАССУ, даже если это звучит странно). То есть он начинается с каталога, где находятся ваши пакеты.

И последнее, но не менее важное: если вы используете систему сборки, подобную Maven (например, Maven или Gradle), то исходные коды Java помещаются в src/main/java, а файлы ресурсов, такие как ваш файл, — в src/main/resources. Они оказываются в одном и том же месте в вашем JAR-файле. То есть src/main/java/package/MyClass.java компилируется в package/MyClass.class, а src/main/resources/package/assets/classes переходит в package/assets/classes. Но для того, чтобы система сборки поняла, что делать с вашими файлами, вы должны поместить их в правильные каталоги: *.java перейти в java, ресурсы — в resources.

Итак, принимая во внимание соглашения, подобные Maven, структура проекта должна быть

src/
    main/
        java/
            my/
                package/
                    MyClass.java
src/
    main/
        resources/
            my/
                package/
                    assets/
                        classes

Учитывая такую ​​структуру, вы получите JAR-файл со структурой, подобной приведенной выше. Выглядит немного безумно, но очень полезно, когда вы освоитесь.

Если вы не используете систему сборки, подобную Maven, то я понятия не имею, как настроить Eclipse. У разных IDE могут быть свои соглашения, и я всегда использовал Maven с Eclipse. Некоторым IDE нравится, например, помещать исходные коды и ресурсы Java в одно и то же дерево каталогов.

person Sergei Tachenov    schedule 21.08.2016
comment
Становимся ближе. Я переместил файл в MyProgram/src/resources/classes (я разархивировал JAR, и он там), но getClass().getResourceAsStream("src/resources/classes") возвращает значение null. (getClass().getResourceAsStream("/src/resources/classes") тоже не работает.) У меня нет класса в src (они все в src/main), так как мне не использовать getClass()? - person Mark Cramer; 21.08.2016
comment
Это расстраивает. Проблема с Eclipse заключается в stackoverflow.com/a/2195501/852795. Если я перемещу файл в /src/main, все будет работать. Кстати, если я перемещу его /bin/main, это тоже сработает. В любом случае использование getRourseAsStream() является правильным ответом для JAR, поэтому я отмечу это как «принято». Спасибо! - person Mark Cramer; 21.08.2016
comment
@Марк, посмотри мои правки о getClass и .class. Вы почти никогда не захотите использовать getClass для доступа к ресурсам, потому что его можно переопределить и просто вернуть не то, что нужно. - person Sergei Tachenov; 21.08.2016
comment
Спасибо. Я переключился на MyClass.class. У меня есть файл для работы, так что теперь я работаю над тем, чтобы сделать то же самое с папкой файлов. Это действительно безумие. - person Mark Cramer; 21.08.2016
comment
@Mark, что касается src/main, см. мои правки о Maven и Gradle. Я не знаю, используете ли вы Maven/Gradle или нет (может быть, вам следует, это решило бы множество подобных проблем!), но если у вас есть src/main/java, то, возможно, вам следует иметь свои ресурсы в src/main/resources. В любом случае, ни src, ни src/main не должны присутствовать в вызове getClass, даже если вы используете абсолютные пути, потому что они выше иерархии CLASSPATH, а абсолютные пути начинаются с одного и того же уровень. - person Sergei Tachenov; 21.08.2016
comment
Так что я ожидаю что-то вроде src/main/java/my/package/MyClass.java, src/main/resources/my/package/assets/classes и MyClass.class.getResourceAsStream("assets/classes") звонка. Это соответствовало бы обычному шаблону. По крайней мере, так делает Maven и чего ожидает большинство Java-разработчиков. - person Sergei Tachenov; 21.08.2016
comment
@Marc, см. больше правок об этом (поскольку считается плохим стилем оставлять такие вещи в комментариях). - person Sergei Tachenov; 21.08.2016
comment
Для всех, кто приходит сюда, у меня это работает. Я использую Eclipse (без Maven или Gradle), поэтому я переместил все свои файлы в src/main/resources (я создал папку), а затем сделал ObjectInputStream is = new ObjectInputStream(MyClass.class.getClassLoader().getResourceAsStream("classes")). Я считаю, что getClassLoader() был трюк. Тогда List<String> listOfClasses = (List<String>) is.readObject();, кажется, работает как шарм. Спасибо за помощь! - person Mark Cramer; 24.08.2016