Класс Java присутствует в пути к классам, но при запуске происходит сбой с ошибкой: не удалось найти или загрузить основной класс

У меня есть файл jar foobar.jar, содержащий следующие два класса:

public class Foo {

    public static void main(String[] args) {
        System.out.println("Foo");
    }
}

Другой класс выглядит так:

import javax.batch.api.chunk.ItemProcessor;

public class Bar implements ItemProcessor {

    public static void main(String[] args) {
        System.out.println("Bar");
    }

    @Override
    public Object processItem(Object item) throws Exception {
        return item;
    }
}

Если я выполняю программу, используя следующую команду, программа ведет себя так, как ожидалось, и печатает Foo:

$ java -cp foobar.jar Foo
Foo
$ 

Но если я попытаюсь запустить программу с помощью метода main в классе Bar, JVM выдаст ошибку запуска и завершит работу:

$ java -cp foobar.jar Bar
Error: Could not find or load main class Bar
$

Это такая же ошибка, как если бы я попытался запустить программу, используя класс, которого нет в банке, например.

$ java -cp foobar.jar BarNotThere
Error: Could not find or load main class BarNotThere
$

Почему я получаю эту ошибку? Тот факт, что метод Foo.main можно запустить и я могу декомпилировать класс Bar из jar-файла, доказывает, что класс должен быть доступен в пути к классам. Я понимаю, что это может быть связано с тем, что интерфейс ItemProcessor не находится в пути к классам. Но разве я не должен получить java.lang.ClassNotFoundException в таком случае?


person boskoop    schedule 18.03.2017    source источник
comment
Обратите внимание, что эта проблема частично рассматривается в stackoverflow.com/questions/18093928/. Я столкнулся с этой проблемой и попытался решить ее, используя указанный вопрос. Но сначала я не смог ее решить, потому что проблема с суперклассом, не находящимся в пути к классам, является своего рода крайним случаем. Вот почему я добавил этот конкретный случай в качестве нового вопроса/ответа.   -  person boskoop    schedule 19.03.2017


Ответы (1)


Проблема действительно в том, что интерфейс ItemProcessor не находится в пути к классам. Обратите внимание, что в сообщении об ошибке указано «найти или загрузить основной класс». В случае BarNotThere JVM действительно не может найти основной класс. Но в случае Bar он не может загрузить основной класс.

Чтобы полностью загрузить класс, JVM также нужны экземпляры каждого объекта суперкласса. Во время этого процесса для Bar JVM пытается загрузить объект класса для ItemProcessor. Но так как этого интерфейса нет в пути к классам, загрузка основного класса Bar завершается ошибкой, и запуск завершается с ошибкой Error: Could not find or load main class Bar.

Если вам трудно найти проблемный класс (поскольку об этом не сообщается), вы можете использовать инструмент jdeps для проверки пути к классам. Просто используйте тот же путь к классам, но запустите jdeps вместо java:

$ jdeps -cp foobar.jar Bar
foobar.jar -> java.base
foobar.jar -> not found
   <unnamed> (foobar.jar)
      -> java.io
      -> java.lang
      -> javax.batch.api.chunk                              not found

(Это было создано с помощью openjdk-9, фактический вывод может сильно различаться в зависимости от версии Java)

Это должно дать вам достаточно подсказок, где искать недостающий класс.


Дополнительные пояснения

Обратите внимание на разницу между загрузкой и инициализацией класса. Если загрузка класса завершается ошибкой во время инициализации (что означает, что класс был успешно найден и загружен), вы получите ожидаемое значение ClassNotFoundException. См. следующий пример:

import javax.batch.api.chunk.ItemProcessor;

public class FooBar {

    private static ItemProcessor i = new ItemProcessor() {
        @Override
        public Object processItem(Object item) throws Exception {
            return item;
        }
    };

    public static void main(String[] args) {
        System.out.println("Foo");
    }
}

В этом случае класс FooBar может быть загружен во время запуска. Но его нельзя инициализировать, так как для статического поля i нужен класс ItemProcessor, которого нет в пути к классам. Инициализация является предварительным условием, если выполняется статический метод класса, как в случае, когда JVM пытается вызвать метод main.

$ java -cp foobar.jar FooBar
Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" java.lang.NoClassDefFoundError: javax/batch/api/chunk/ItemProcessor
        at java.lang.Class.getDeclaredMethods0(Native Method)
        at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
        at java.lang.Class.privateGetMethodRecursive(Class.java:3048)
        at java.lang.Class.getMethod0(Class.java:3018)
        at java.lang.Class.getMethod(Class.java:1784)
        at sun.launcher.LauncherHelper.validateMainClass(LauncherHelper.java:544)
        at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:526)
Caused by: java.lang.ClassNotFoundException: javax.batch.api.chunk.ItemProcessor
        at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
        ... 7 more
$
person boskoop    schedule 18.03.2017
comment
Но как я могу узнать, какой класс не находится в пути к классам, когда происходит Error: Could not find or load main class Bar? - person zczhuohuo; 22.05.2018
comment
@zczhuohuo Вы можете использовать инструмент jdeps для анализа пути к классам. Я добавлю это к ответу. - person boskoop; 23.05.2018