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

2022-08-31 01:25:47

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

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


答案 1

命令语法java <class-name>

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

正常的语法1 是这样的:

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

其中 是命令行选项(以“-”字符开头),是完全限定的 Java 类名,并且是传递给应用程序的任意命令行参数。<option><class-name><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. 检查该类是否具有由 给出的签名返回类型修饰符的方法。(请注意,方法参数的名称不是签名的一部分。mainpublic static void main(String[])
  4. 调用该方法,将其作为 .String[]

Java 找不到类的原因

当您收到消息“无法找到或加载主类...”时,这意味着第一步已失败。该命令无法找到该类。事实上,“...”中将是要查找的完全限定类名javajava

那么,为什么它可能无法找到该类呢?

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

第一个可能的原因是您可能提供了错误的类名。(或...正确的类名,但格式错误。考虑到上面的示例,以下是指定类名的各种错误方法

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

    java ListUser
    

    当类在诸如 的包中声明时,则必须使用完整的类名,包括命令中的包名;例如:com.acme.examplejava

    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 - 应用程序的类路径指定不正确

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

所以。。。如果已正确指定类名,则接下来要检查的是是否已正确指定类路径:

  1. 阅读上面链接的三个文档。(是的...阅读它们!重要的是,Java程序员至少要了解Java类路径机制如何工作的基础知识。
  2. 查看命令行和/或运行命令时有效的 CLASSPATH 环境变量。检查目录名和 JAR 文件名是否正确。java
  3. 如果类路径中有相对路径名,请检查它们是否正确解析...从运行命令时有效的当前目录中。java
  4. 检查类(在错误消息中提到)是否可以位于有效类路径上。
  5. 请注意,Windows 与 Linux 和 Mac OS 的类路径语法不同(类路径分隔符位于 Windows 和其他版本上。如果为平台使用了错误的分隔符,则不会收到明确的错误消息。相反,您将在路径上获得一个不存在的文件或目录,该文件或目录将被静默忽略。;:

原因#2a - 类路径上有错误的目录

将目录放在类路径上时,它名义上对应于限定名称空间的根目录。类位于该根目录下的目录结构中,方法是将完全限定名映射到路径名。例如,如果“/usr/local/acme/classes”在类路径上,那么当JVM查找名为的类时,它将查找具有以下路径名的“.class”文件:com.acme.example.Foon

  /usr/local/acme/classes/com/acme/example/Foon.class

如果你把“/usr/local/acme/classes/com/acme/example”放在类路径上,那么JVM将无法找到这个类。

原因#2b - 子目录路径与 FQN 不匹配

如果你的类 FQN 是 ,那么 JVM 将在目录 “com/acme/example” 中查找 “Foon.class”:com.acme.example.Foon

  • 如果您的目录结构与上述模式的包命名不匹配,则 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-cpjavajavac
  • 在类路径中选择绝对路径名和相对路径名时,请仔细考虑。请记住,如果当前目录发生更改,相对路径名可能会“中断”。

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

类路径需要包括应用程序所依赖的所有其他(非系统)类。(系统类是自动定位的,您很少需要关注这一点。要正确加载主类,JVM 需要找到:

(注意:JLS 和 JVM 规范允许 JVM“懒惰地”装入类的某些范围,这可能会影响何时引发类装入器异常。

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

偶尔会有人将源代码文件放入其源代码树中的错误文件夹中,或者他们省略了声明。如果在 IDE 中执行此操作,IDE 的编译器将立即告诉您有关此操作的信息。同样,如果您使用一个像样的Java构建工具,该工具将以一种检测问题的方式运行。但是,如果您手动构建Java代码,则可以以编译器不会注意到问题的方式进行,并且生成的“.class”文件不在预期的位置。packagejavac

仍然找不到问题?

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

此外,请考虑从网站、文档等复制和粘贴不可见或非 ASCII 字符可能引起的问题。并考虑“同形异体”,其中两个字母或符号看起来相同...但事实并非如此。

如果您在 中具有无效或不正确的签名,则可能会遇到此问题。您可以尝试在自己喜欢的ZIP编辑器中打开.jar,然后删除文件,直到您拥有的只是.但是,通常不建议这样做。(无效签名可能是由于某人将恶意软件注入原始签名的 JAR 文件中。如果您删除了无效的签名,则您正在用恶意软件感染您的应用程序!推荐的方法是获取具有有效签名的 JAR 文件,或者从(真实的)原始源代码重新构建它们。META-INF/*.SFMETA-INFMANIFEST.MF

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


的替代语法java

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

  1. 用于启动“可执行”JAR 文件的语法如下:

    java [ <options> ] -jar <jar-file-name> [<arg> ...]
    

    例如:

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

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

  2. 从模块(Java 9 及更高版本)启动应用程序的语法如下:

    java [ <options> ] --module <module>[/<mainclass>] [<arg> ...]
    

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

  3. 从 Java 11 开始,您可以使用该命令使用以下语法编译和运行单个源代码文件:java

    java [ <options> ] <sourcefile> [<arg> ...]
    

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

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


集成开发企业

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

但是,如果在 IDE 后面执行操作,则仍有可能发生此异常。例如,如果您之前在 Eclipse 中为 Java 应用程序设置了应用程序启动器,然后将包含“main”类的 JAR 文件移动到文件系统中的其他位置,而没有告诉 Eclipse,Eclipse 会不知不觉地使用不正确的类路径启动 JVM。

简而言之,如果您在IDE中遇到此问题,请检查过时的IDE状态,损坏的项目引用或损坏的启动器配置之类的内容。

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


其他参考资料


答案 2

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

You will get that error if you call it using:

java HelloWorld.class

Instead, use this:

java HelloWorld