“找不到或无法加载主类”是什么意思?

新手上路,请多包涵

新的 Java 开发人员遇到的一个常见问题是他们的程序无法运行并显示错误消息: Could not find or load main class ...

这是什么意思,是什么原因造成的,您应该如何解决?

原文由 Stephen C 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.3k
2 个回答

java <class-name> 命令语法

首先,您需要了解使用 java (或 javaw )命令启动程序的正确方法。

正常的语法1是这样的:

     java [ <options> ] <class-name> [<arg> ...]

其中 <option> 是命令行选项(以“-”字符开头), <class-name> 是完全限定的 Java 类名, <arg> 是任意命令传递给您的应用程序的行参数。


1 - 在这个答案的末尾附近描述了一些其他语法。

类的完全限定名 (FQN) 按照惯例编写,就像在 Java 源代码中一样;例如

    packagename.packagename2.packagename3.ClassName

但是,某些版本的 java 命令允许您使用斜线而不是句号;例如

    packagename/packagename2/packagename3/ClassName

这(令人困惑地)看起来像一个文件路径名,但不是一个。请注意,术语 完全限定名称 是标准的 Java 术语……不是我编造的让你感到困惑的东西:-)

这是 java 命令的示例:

     java -Xmx100m com.acme.example.ListUsers fred joe bert

以上将导致 java 命令执行以下操作:

  1. 搜索 com.acme.example.ListUsers 类的编译版本。
  2. 加载类。
  3. 检查该类是否具有 main 方法,其 _签名_、 返回类型修饰符public static void main(String[]) 给出。 (注意,方法参数的名称 不是 签名的一部分。)
  4. 调用该方法,将命令行参数(“fred”、“joe”、“bert”)作为 String[] 传递给它。

Java找不到类的原因

当您收到消息“Could not find or load main class …”时,这意味着第一步失败了。 java 命令无法找到类。事实上,消息中的“…”将是 java 正在寻找的 _完全限定类名_。

那么为什么它可能找不到类呢?

原因 #1 - 你在类名参数上犯了一个错误

第一个可能的原因是您可能提供了错误的类名。 (或者…正确的类名,但格式不对。)考虑上面的例子,这里有多种指定类名的 错误方法

  • 示例 #1 - 一个简单的类名:
   java ListUser

当类在诸如 com.acme.example 的包中声明时,则必须在 java 命令中使用 包含 包名称的完整类名;例如

  java com.acme.example.ListUser

  • 示例 #2 - 文件名或路径名而不是类名:
   java ListUser.class
  java com/acme/example/ListUser.class

  • 示例 #3 - 大小写不正确的类名:
   java com.acme.example.listuser

  • 示例 #4 - 错字
  java com.acme.example.mistuser

  • 示例 #5 - 源文件名(Java 11 或更高版本除外;见下文)
   java ListUser.java

  • 示例 #6 - 你完全忘记了类名
  java lots of arguments

原因 #2 - 应用程序的类路径指定不正确

第二个可能的原因是类名是正确的,但是 java 命令找不到类。要理解这一点,您需要理解“类路径”的概念。 Oracle 文档对此进行了 很好 的解释:

所以…如果您正确指定了类名,接下来要检查的是您是否正确指定了类路径:

  1. 阅读上面链接的三个文档。 (是的……阅读它们!重要的是 Java 程序员至少 了解 Java 类路径机制如何工作的基础知识。)
  2. 查看运行 java 命令时生效的命令行和/或 CLASSPATH 环境变量。检查目录名和 JAR 文件名是否正确。
  3. 如果类路径中有 相对 路径名,请检查它们是否正确解析…从运行 java 命令时生效的当前目录。
  4. 检查类(错误消息中提到的)是否位于 有效 的类路径中。
  5. 请注意,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 。检查 javajavac 等的相应手册条目。
  • 在类路径中的绝对路径名和相对路径名之间进行选择时要仔细考虑。请记住,如果当前目录更改,相对路径名可能会“中断”。

原因 #2c - 类路径中缺少依赖项

类路径需要包含您的应用程序所依赖的所有 _其他_(非系统)类。 (系统类是自动定位的,你很少需要关心这个。)为了正确加载主类,JVM 需要找到:

(注意:JLS 和 JVM 规范允许 JVM 在一定范围内“延迟”加载类,这会影响何时抛出类加载器异常。)

原因 #3 - 该类已在错误的包中声明

偶尔会发生有人将源代码文件放入其源代码树中的错误文件夹中,或者他们遗漏了 package 声明。如果您在 IDE 中执行此操作,IDE 的编译器将立即告诉您。同样,如果您使用合适的 Java 构建工具,该工具将以检测问题的方式运行 javac 。但是,如果您手动构建 Java 代码,编译器可能不会注意到问题,并且生成的“.class”文件不在您期望的位置。

还是找不到问题?

有很多东西要检查,很容易漏掉一些东西。尝试将 -Xdiag 选项添加到 java 命令行(作为 java 之后的第一件事)。它将输出有关类加载的各种信息,这可能会为您提供有关真正问题所在的线索。

此外,请考虑从网站、文档等复制和粘贴不可见或非 ASCII 字符可能导致的问题。并考虑“homoglyphs”,其中两个字母或符号看起来相同……但不是。

如果您在 META-INF/*.SF 中有无效或不正确的签名,您可能会遇到此问题。您可以尝试在您最喜欢的 ZIP 编辑器中打开 .jar,并从 META-INF 中删除文件,直到您只剩下 MANIFEST.MF 。但是,一般不推荐这样做。 (无效签名可能是有人将恶意软件注入原始签名的 JAR 文件的结果。如果您删除无效签名,您将用恶意软件感染您的应用程序!)推荐的方法是获取具有有效签名的 JAR 文件签名,或从(真实的)原始源代码重建它们。

最后,如果 MANIFEST.MF 文件中存在语法错误,您显然会遇到此问题(请参阅 https://stackoverflow.com/a/67145190/139985 )。


java

使用 java command 启动 Java 程序有三种替代语法。

  1. 用于启动“可执行”JAR 文件的语法如下:
    java [ <options> ] -jar <jar-file-name> [<arg> ...]

例如

   java -Xmx100m -jar /usr/local/acme-example/listuser.jar fred

入口类的名称(即 com.acme.example.ListUser )和类路径在 JAR 文件的清单中指定。

  1. 从模块(Java 9 及更高版本)启动应用程序的语法如下:
    java [ <options> ] --module <module>[/<mainclass>] [<arg> ...]

入口点类的名称要么由 <module> 本身定义,要么由可选的 <mainclass> 给出。

  1. 从 Java 11 开始,您可以使用 java 命令使用以下语法编译和运行单个源代码文件:
    java [ <options> ] <sourcefile> [<arg> ...]

其中 <sourcefile> 是(通常)后缀为“.java”的文件。

有关详细信息,请参阅您正在使用的 Java 版本的 java 命令的官方文档。


集成开发环境

典型的 Java IDE 支持在 IDE JVM 本身或子 JVM 中运行 Java 应用程序。这些 通常 不受此特定异常的影响,因为 IDE 使用自己的机制来构建运行时类路径、识别主类并创建 java 命令行。

但是,如果您在 IDE 背后进行操作,则仍有可能发生此异常。例如,如果您之前在 Eclipse 中为您的 Java 应用程序设置了一个应用程序启动器,然后您在 没有告知 Eclipse 的情况下将包含“主”类的 JAR 文件移动到文件系统中的其他位置,Eclipse 将无意中启动 JVM使用不正确的类路径。

简而言之,如果您在 IDE 中遇到此问题,请检查是否存在陈旧的 IDE 状态、损坏的项目引用或损坏的启动器配置等问题。

IDE 也有可能简单地感到困惑。 IDE 是非常复杂的软件,包含许多交互部分。其中许多部分采用各种缓存策略,以使 IDE 作为一个整体响应。这些有时会出错,一种可能的症状是启动应用程序时出现问题。如果您怀疑可能会发生这种情况,则值得尝试其他操作,例如重新启动 IDE、重建项目等。


其他参考资料

原文由 Stephen C 发布,翻译遵循 CC BY-SA 4.0 许可协议

如果您的源代码名称是 HelloWorld.java,则编译后的代码将为 HelloWorld.class

如果您使用以下方式调用它,您将收到该错误:

 java HelloWorld.class

相反,使用这个:

 java HelloWorld

原文由 panoet 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题