请注意,类路径语法是不同的适用于 Windows 与 Linux 和 Mac OS。 (类路径分隔符在 Windows 上为 ;,在其他系统上为 :。如果您为您的平台使用了错误的分隔符,您将不会收到明确的错误消息。相反,您将在将被静默忽略的路径。)
原因 #2a - 错误的目录位于类路径中
当您将目录放在类路径上时,它理论上对应于限定名称空间的根目录。类位于该根目录下的目录结构中,通过将完全限定名映射到路径名.例如,如果“/usr/local/acme/classes”在类路径上,那么当 JVM 查找名为 com.acme.example.Foon 的类时,它将查找具有此路径名的“.class”文件:
/usr/local/acme/classes/com/acme/example/Foon.class
如果您将“/usr/local/acme/classes/com/acme/example”放在类路径上,那么 JVM 将无法找到该类。
原因 #2b - 子目录路径与 FQN 不匹配
如果您的类 FQN 是 com.acme.example.Foon,那么 JVM 将在目录“com/acme/example”中查找“Foon.class”:
-
如果您的目录结构与上述模式中的包命名不匹配,则 JVM 将找不到您的类。
-
如果你尝试改名通过移动一个类,它也会失败......但异常堆栈跟踪会有所不同。很容易说这样的话:
Caused by: java.lang.NoClassDefFoundError: <path> (wrong name: <name>)
因为类文件中的 FQN 与类加载器期望找到的不匹配。
举一个具体的例子,假设:
- 你想运行
com.acme.example.Foon类,
- 完整文件路径为
/usr/local/acme/classes/com/acme/example/Foon.class,
- 您当前的工作目录是
/usr/local/acme/classes/com/acme/example/,
然后:
# wrong, FQN is needed
java Foon
# wrong, there is no `com/acme/example` folder in the current working directory
java com.acme.example.Foon
# wrong, similar to above
java -classpath . com.acme.example.Foon
# fine; relative classpath set
java -classpath ../../.. com.acme.example.Foon
# fine; absolute classpath set
java -classpath /usr/local/acme/classes com.acme.example.Foon
笔记:
- 在大多数 Java 版本中,
-classpath 选项可以缩短为 -cp。检查java、javac 等的相应手册条目。
- 在类路径中选择绝对路径名和相对路径名时要仔细考虑。请记住,如果当前目录更改,相对路径名可能会“中断”。
原因 #2c - 类路径中缺少依赖项
类路径需要包含所有其他您的应用程序所依赖的(非系统)类。 (系统类是自动定位的,你很少需要关心这个。)为了正确加载主类,JVM 需要找到:
(注意:JLS 和 JVM 规范允许 JVM 在一定范围内“延迟”加载类,这会影响何时引发类加载器异常。)
原因 #3 - 该类已在错误的包中声明
偶尔会发生有人将源代码文件放入
源代码树中的错误文件夹,或者他们遗漏了package 声明。如果您在 IDE 中执行此操作,IDE 的编译器会立即告诉您这一点。同样,如果您使用一个不错的 Java 构建工具,该工具将以检测问题的方式运行 javac。但是,如果您手动构建您的 Java 代码,您可以这样做,编译器不会注意到问题,并且生成的“.class”文件不在您期望的位置。
还是找不到问题?
有很多东西要检查,很容易错过一些东西。尝试将-Xdiag 选项添加到java 命令行(作为java 之后的第一件事)。它将输出有关类加载的各种信息,这可能会为您提供关于真正问题所在的线索。
此外,请考虑从网站、文档等复制和粘贴不可见或非 ASCII 字符可能导致的问题。并考虑“同形文字”,其中两个字母或符号看起来相同......但不是。
如果您在META-INF/*.SF 中有无效或不正确的签名,您可能会遇到此问题。您可以尝试在您最喜欢的 ZIP 编辑器中打开 .jar,然后从 META-INF 中删除文件,直到您只剩下您的 MANIFEST.MF。但是,一般不建议这样做。 (无效签名可能是由于有人将恶意软件注入到原始签名的 JAR 文件中。如果您删除了无效签名,您就是在用恶意软件感染您的应用程序!)推荐的方法是获取具有有效的 JAR 文件签名,或从(真实的)原始源代码重建它们。
最后,如果MANIFEST.MF 文件中存在语法错误(请参阅https://stackoverflow.com/a/67145190/139985),您显然会遇到此问题。
java 的替代语法
使用java command 启动Java 程序有三种替代语法。
-
用于启动“可执行”JAR 文件的语法如下:
java [ <options> ] -jar <jar-file-name> [<arg> ...]
例如
java -Xmx100m -jar /usr/local/acme-example/listuser.jar fred
入口点类的名称(即com.acme.example.ListUser)和类路径在 JAR 文件的 MANIFEST 中指定。
-
从模块(Java 9 及更高版本)启动应用程序的语法如下:
java [ <options> ] --module <module>[/<mainclass>] [<arg> ...]
入口点类的名称要么由<module> 本身定义,要么由可选的<mainclass> 给出。
-
从 Java 11 开始,您可以使用 java 命令使用以下语法编译和运行单个源代码文件:
java [ <options> ] <sourcefile> [<arg> ...]
其中<sourcefile>(通常)是一个后缀为“.java”的文件。
有关更多详细信息,请参阅您正在使用的 Java 版本的 java 命令的官方文档。
IDE
典型的 Java IDE 支持在 IDE JVM 本身或子 JVM 中运行 Java 应用程序。这些是一般来说不受此特定异常的影响,因为 IDE 使用自己的机制来构造运行时类路径、识别主类并创建 java 命令行。
但是,如果您在 IDE 背后执行操作,则仍有可能发生此异常。例如,如果您之前在 Eclipse 中为您的 Java 应用程序设置了应用程序启动器,然后您将包含“main”类的 JAR 文件移动到文件系统中的不同位置不告诉 Eclipse,Eclipse 会无意中使用不正确的类路径启动 JVM。
简而言之,如果您在 IDE 中遇到此问题,请检查过时的 IDE 状态、损坏的项目引用或损坏的启动器配置等。
IDE 也可能会简单地感到困惑。 IDE 是非常复杂的软件,包含许多交互部分。其中许多部分采用各种缓存策略,以使 IDE 作为一个整体响应。这些有时会出错,一个可能的症状是启动应用程序时出现问题。如果您怀疑这可能会发生,那么值得尝试其他方法,例如重新启动 IDE、重建项目等等。
其他参考