“Cannot find symbol”或“Cannot resolve symbol”错误意味着什么?

538
请解释以下关于Java中"Cannot find symbol"、"Cannot resolve symbol"或"Symbol not found"错误的内容:
  • 它们的含义是什么?
  • 有哪些因素会导致它们出现?
  • 程序员应该如何修复它们?
这个问题旨在为这些常见编译错误在Java中种下一个全面的问答。
18个回答

550

0. 这些错误有什么区别吗?

实际上没有。“Cannot find symbol”、“Cannot resolve symbol”和“Symbol not found”都是指同样的意思。(不同的Java编译器由不同的人编写,不同的人使用不同的短语表达同样的意思。)

1. “Cannot find symbol”错误是什么意思?

首先,它是一个编译错误1。这意味着要么您的Java源代码存在问题,要么您编译的方式存在问题。

您的Java源代码包括以下内容:

  • 关键字:如classwhile等。
  • 字面值:如truefalse42'X'"Hi mum!"
  • 运算符和其他非字母数字标记:如+={等。
  • 标识符:如ReaderitoStringprocessEquibalancedElephants等。
  • 注释和空格。

“Cannot find symbol”错误是关于标识符的。当您的代码被编译时,编译器需要弄清楚您代码中每个标识符的含义。

“Cannot find symbol”错误意味着编译器无法实现这一点。您的代码似乎引用了编译器无法理解的内容。

2. 什么会导致“Cannot find symbol”错误?

首要原因只有一个。编译器在应该定义标识符的所有位置都搜索过,但找不到定义。这可能由许多因素引起。以下是常见的原因:

  • 对于标识符通用的情况:

    也许您拼写错误了,例如使用了StringBiulder而不是StringBuilder。Java 不会尝试弥补拼写错误或打字错误。
    也许您大小写用错了,例如使用了stringBuilder而不是StringBuilder。所有 Java 标识符都区分大小写。
    也许您不恰当地使用下划线,例如mystringmy_string是不同的。(如果遵循Java的命名规范,您将在很大程度上免受此类错误的影响...)
    也许您正在尝试使用在其他地方声明的内容,即在隐式告诉编译器查找的位置(不同的类?不同的作用域?不同的包?不同的代码库?)中的不同上下文中。
    对于应该引用变量的标识符:
    也许您忘记声明变量了。
    也许在您尝试使用它的时候,变量声明超出了范围。(见下面的示例)
    对于应该是方法或字段名称的标识符:
    也许您正在尝试引用未在父/祖先类或接口中声明的继承方法或字段。
    也许您正在尝试引用在您正在使用的类型中不存在(即未声明)的方法或字段;例如:"rope".push()2
    也许您正在尝试将方法用作字段,或者反之;例如:"rope".lengthsomeArray.length()
    也许您错误地操作数组而不是数组元素;例如:
        String strings[] = ...
        if (strings.charAt(3)) { ... }
        // maybe that should be 'strings[0].charAt(3)'
    
  • 对于应该是类名的标识符:

    • 可能您忘记导入该类。

    • 可能您使用了“星号”导入,但该类未在您导入的任何包中定义。

    • 可能您忘记了 new 例如:

          String s = String();  // should be 'new String()'
      
      也许你在尝试导入或使用一个在 默认包 中声明的类;即没有 package 语句的类所在的包。
      提示:了解包。您应该只为由一个类或一个 Java 源文件组成的简单应用程序使用默认包,或者在极限情况下使用。
      对于类型或实例似乎没有您期望它具有的成员(例如方法或字段)的情况:
      也许您已经声明了一个嵌套类或泛型参数,这个嵌套类或泛型参数遮蔽了您想要使用的类型。
      也许您正在遮蔽一个静态或实例变量。
      也许您导入了错误的类型;例如,由于 IDE 完成或自动更正可能建议使用java.awt.List而不是java.util.List
      也许您正在使用(编译针对)错误版本的 API。
      也许您忘记将对象转换为适当的子类。
      也许您已将变量的类型声明为具有您正在查找的成员的类型的超类型。
      问题通常是上述情况的组合。例如,也许您“星号”导入了java.io.*,然后尝试使用Files类...它在java.nio中而不是java.io中。或者您可能打算编写File...这是java.io中的一个类。
      下面是一个不正确的变量作用域如何导致“找不到符号”错误的示例:
      List<String> strings = ...
      
      for (int i = 0; i < strings.size(); i++) {
          if (strings.get(i).equalsIgnoreCase("fnord")) {
              break;
          }
      }
      if (i < strings.size()) {
          ...
      }
      
      这将导致在if语句中的i出现“无法找到符号”的错误。尽管我们之前声明了i,但该声明仅适用于for语句及其主体。在if语句中对i的引用“看不到”该i的声明。它是“超出范围”的。(一个合适的更正方法可能是将if语句移到循环内部,或在循环开始之前声明i。)
      这是一个例子,其中一个拼写错误导致一个看似难以理解的“无法找到符号”的错误:
      for (int i = 0; i < 100; i++); {
          System.out.println("i is " + i);
      }
      

      println调用中,会出现编译错误,指出找不到i变量。但是(你可能会说)我已经声明了它!

      问题在于在{之前有一个隐蔽的分号(;)。在这种情况下,Java语言语法将分号定义为一个空语句。然后,空语句成为for循环的主体。因此,该代码实际上意味着:

      for (int i = 0; i < 100; i++); 
      
      // The previous and following are separate statements!!
      
      {
          System.out.println("i is " + i);
      }
      

      { ... }块不是for循环的主体部分,因此for语句中先前声明的i在该块中是不可见的


      以下是另一种因拼写错误导致的“找不到符号”错误示例。

      int tmp = ...
      int res = tmp(a + b);
      
      尽管有先前的声明,tmp(...)表达式中的tmp是错误的。编译器会寻找一个名为tmp的方法,但没有找到。 先前声明的tmp在变量命名空间中,而不是方法命名空间中。

      在我遇到的例子中,程序员实际上缺少了一个运算符。 他原本想写成这样:
      int res = tmp * (a + b);
      

      如果你在命令行编译代码时,编译器找不到某个符号的另一个原因可能是忘记编译或重新编译其他类。例如,如果你有一个使用了BarFoo类和Bar类,如果你从未编译过Bar而运行javac Foo.java,你会发现编译器找不到符号Bar。简单的解决方法是一起编译FooBar;例如:javac Foo.java Bar.javajavac *.java。或者更好的办法是使用Java构建工具,例如Ant、Maven、Gradle等。

      还有其他一些更隐晦的原因...我将在下面详细说明。

      3. 如何修复这些错误?

      通常情况下,你首先需要确定编译错误的原因

      • 查看编译错误消息所指示的文件中的行。
      • 确定错误消息所涉及的符号。
      • 找出编译器为什么无法找到该符号的原因;参见上文!

      然后,你需要思考代码要表达什么意思。最后,确定需要对源代码进行哪些更改才能实现你的目标。

      请注意,并不是每个“更改”都是正确的。例如:

      for (int i = 1; i < 10; i++) {
          for (j = 1; j < 10; j++) {
              ...
          }
      }
      

      假设编译器说找不到符号j时,可以通过多种方式进行“修复”:

      • 我可以将内部for更改为for(int j = 1; j < 10; j ++) - 可能是正确的。
      • 我可以在内部for循环或外部 for 循环之前添加对j 的声明- 可能是正确的。
      • 我可以在内部for循环中将j 更改为i - 可能是错误的!
      • 等等。

      关键是您需要理解您的代码尝试做什么才能找到正确的修复方法。

      4. 模糊的原因

      以下是一些看起来似乎无法解释“找不到符号”的情况,直到您仔细查看为止。

      1. 依赖关系不正确: 如果您正在使用管理构建路径和项目依赖项的IDE或构建工具,则可能会出现依赖项错误;例如省略了依赖项或选择了错误的版本。如果您正在使用构建工具(Ant、Maven、Gradle等),请检查项目的构建文件。如果您正在使用IDE,请检查项目的构建路径配置。

      2. 找不到符号“var”: 您可能正在尝试使用局部变量类型推断(即var 声明)编译源代码,其使用旧的编译器或旧的 --source 级别。 var 是在Java 10中引入的。检查您的JDK版本和构建文件以及(如果在IDE中出现此问题)IDE设置。

      3. 您没有编译/重新编译: 有时,新的Java程序员不了解Java工具链的工作原理,或者没有实现可重复的“构建过程”,例如使用IDE、Ant、Maven、Gradle等等。在这种情况下,程序员可能会一直寻找由于未正确重新编译代码而导致的虚假错误。

        另一个例子是当您使用(Java 9+)java SomeClass.java 编译和运行类时。如果类依赖于您没有编译(或重新编译)的另一个类,则可能会出现指向第二个类的“无法解析符号”错误。其他源文件不会自动编译。 java 命令的新“编译和运行”模式不适用于运行具有多个源代码文件的程序。

      早期构建问题: 可能存在早期构建失败的情况,导致 JAR 文件中缺少类。如果您正在使用构建工具,则通常会注意到此类故障。但是,如果您从他人那里获取 JAR 文件,则取决于他们正确构建并注意到错误。如果您怀疑此情况,请使用 tar -tvf 命令列出可疑 JAR 文件的内容。

    • IDE 问题: 有人报告了这样一种情况,即他们的 IDE 混淆了,IDE 编译器无法找到已存在的类,或者反过来。

      • 如果 IDE 配置了错误的 JDK 版本,则可能会发生这种情况。

      • 如果 IDE 的缓存与文件系统不同步,则可能会发生这种情况。有 IDE 特定的方法可解决此问题。

      • 这可能是 IDE 的错误。例如,@Joel Costigliola 描述了一个场景,在该场景中 Eclipse 未正确处理 Maven “test” 树:请参阅此答案。(显然,该特定错误已经很久以前得到修复。)

    • Android 问题: 当您为 Android 编程,并且出现与 R 相关的“找不到符号”错误时,请注意 R 符号由 context.xml 文件定义。检查您的 context.xml 文件是否正确并位于正确的位置,并且已生成 / 编译相应的 R 类文件。请注意,Java 符号区分大小写,因此相应的 XML ID 也区分大小写。

      其他 Android 上的符号错误可能是由于前面提到的原因导致;例如,缺少或不正确的依赖项、不正确的包名称、特定 API 版本中不存在方法或字段、拼写/打字错误等。

    • 隐藏系统类: 我曾经看到编译器抱怨在类似以下内容中,substring 是一个未知符号:

      String s = ...
      String s1 = s.substring(1);
      

      原来程序员创建了自己的版本String,而他定义的这个类没有定义substring方法。我曾经见过人们在SystemScanner和其他类中这样做。

      教训:不要定义与常用库类相同名称的自己的类!

      该问题也可以通过使用全限定名来解决。例如,在上面的例子中,程序员本可以这样写:

      java.lang.String s = ...
      java.lang.String s1 = s.substring(1);
      
    • 同形异义字:如果你在源代码中使用UTF-8编码,就可能会出现看起来相同但实际上不同的标识符,因为它们包含了同形异义字。请参见这个页面以获取更多信息。

      您可以通过将源文件编码限制为ASCII或Latin-1,并对其他字符使用Java \uxxxx转义来避免此问题。


    • 1 - 如果偶然地在运行时异常或错误消息中看到这个问题,那么要么是您已经配置了IDE以运行有编译错误的代码,要么是您的应用程序正在生成和编译代码...在运行时。
      2 - 土木工程的三个基本原则:水不会流到山上,木板横放更结实,以及你无法拉动绳子


  • 有点类似于上面的评论,当我从Eclipse编译和运行程序时,它能够正常工作。但是如果我从控制台编译,就会出现很多“找不到符号”的错误,通常与导入中的最后一个元素有关。我不知道出了什么问题,因为代码本身并没有错。 - Andres Stadelmann
    另一个问题是IDE可能会将其他错误“解释”为此类错误。例如,在标准编译器下,如果在类级别下放置System.out.println中的println,我们将得到<identifier> expected演示),但在IntelliJ中,我们将看到Cannot resolve symbol 'println'演示)。 - Pshemo
    似乎Intellij采取了一些在语法上无效的东西(需要声明的语句),然后尝试解析符号。但编译器混乱到足以无法解析应该解析的符号...如果该语句处于正确的上下文中,则可以解析并且会解析。这导致了误导性的编译错误消息。 - Stephen C
    这一点也没有帮助。 - user1034912
    @user1034912 - 好的...那就再读另一个答案。你还有17个可以选择。或者重新审视你的问题细节,着重阅读上面提到的特定领域。编译器无法解析什么类型的符号?变量?方法?类?包?你在使用IDE吗?Android?等等(提示:如果您不告诉我们您的问题是什么,我们怎么能帮助您呢?尝试提出一个新问题...其中包含一些实际的细节)。 - Stephen C
    显示剩余2条评论

    35

    如果您忘记使用new,也会出现这个错误:

    String s = String();
    

    对抗

    String s = new String();
    
    因为没有使用 new 关键字的调用会尝试查找一个名为 String 的(本地)方法而不带参数 - 而该方法签名很可能未定义。

    3
    扫描了大约一个小时的代码后,我找到了这个答案 - 谢谢上帝! - hazirovich

    23

    已解决

    使用IntelliJ

    选择构建->重新构建项目即可解决问题。


    7
    这非常取决于具体情况,通常不会如此。 - Maarten Bodewes
    这对我有用。我之前无法解决它,因为包/目录都是正确的。似乎需要重新构建以纳入更改。 - coder3

    20

    “变量超出范围”的另一个例子

    由于我已经多次看到这种问题,也许可以举一个即使感觉上可能没问题,但实际上是非法的例子。

    考虑以下代码:

    if(somethingIsTrue()) {
      String message = "Everything is fine";
    } else {
      String message = "We have an error";
    }
    System.out.println(message);
    

    那是无效的代码。因为两个名为message的变量都不在它们各自的范围之外可见 - 在这种情况下,就是包围它们的大括号{}

    你可能会说:"但是无论如何都定义了一个名为message的变量 - 所以,在if之后message被定义了"。

    但你错了。

    Java没有free()或者delete运算符, 所以它必须依靠跟踪变量范围来找出何时不再使用变量(以及对这些变量的引用)。

    特别糟糕的是,如果你认为自己做了正确的事情,实际上会导致这种错误。我曾经看到过这种错误在优化类似以下代码时发生:

    if(somethingIsTrue()) {
      String message = "Everything is fine";
      System.out.println(message);
    } else {
      String message = "We have an error";
      System.out.println(message);
    }
    

    “哦,这里有重复的代码,让我们将这个通用的行拉出来” —— 然后就有了。

    处理这种作用域问题最常见的方法是将 else 值预先分配给外部作用域的变量名,然后在 if 中重新分配:

    String message = "We have an error";
    if(somethingIsTrue()) {
      message = "Everything is fine";
    } 
    System.out.println(message);
    

    4
    Java没有free()或delete运算符,因此它必须依赖跟踪变量作用域来确定变量何时不再使用(当然还有这些变量的引用)。尽管如此,这段话并不相关。C和C++都有对应的free/delete运算符,但是如果将你的示例转换成等效的C/C++代码,则该代码是非法的。C和C++块与Java一样限制了变量的作用域。实际上,大多数“块结构”语言也是如此。 - Stephen C
    1
    对于在每个分支上分配不同值的代码,更好的解决方案是使用空白 final 变量声明。 - Daniel Pryden

    13

    在Eclipse中出现此错误的一种方法:

    1. src/test/java中定义一个类A
    2. src/main/java中定义另一个类B,并使用类A

    结果:Eclipse将编译代码,但Maven会给出“找不到符号”的错误。

    根本原因:Eclipse为主树和测试树使用了合并的构建路径。不幸的是,它不支持为Eclipse项目的不同部分使用不同的构建路径,而这正是Maven所需要的。

    解决方案:

    1. 不要以这种方式定义依赖项;即不要犯这个错误。
    2. 定期使用Maven构建您的代码库,以便及早发现此错误。其中一种方法是使用CI服务器。

    这个问题的解决方案是什么? - user4964330
    2
    无论您在src/main/java中使用什么,都需要在src/main/java或任何编译/运行时依赖项(而不是测试依赖项)中定义。 - Joel Costigliola

    8
    "找不到"意味着编译器无法找到适当的变量、方法、类等。如果您遇到了这个错误信息,首先要找到出错代码所在的行。然后,您就能找到哪个变量、方法或类在使用之前没有定义。确认后,初始化该变量、方法或类即可在以后的需求中使用。请看以下示例:
    我将创建一个演示类并打印一个名称...
    class demo{ 
          public static void main(String a[]){
                 System.out.print(name);
          }
    }
    

    现在看一下结果...

    输入图像描述此处

    这个错误提示是指“找不到变量名”...定义和初始化“name”变量的值可以消除这个错误...实际上是这样的:

    class demo{ 
          public static void main(String a[]){
    
                 String name="smith";
    
                 System.out.print(name);
          }
    }
    

    现在看一下新的输出...

    输入图像描述

    好的,成功解决了那个错误。同时,如果你遇到了“找不到方法”或“找不到类”的错误,首先要定义一个类或方法,然后再使用它。


    5
    如果您在其他地方构建时出现此错误,而您的 IDE 表示一切都完美无缺,请检查两个地方使用了相同的 Java 版本。
    例如,Java 7 和 Java 8 具有不同的 API,因此在旧版本的 Java 中调用不存在的 API 将导致此错误。

    5

    有很多种情况,如上面的人们所提到的。 以下是帮助我解决此问题的一些方法。

    1. 如果您正在使用IntelliJ

      文件->'使缓存无效/重启'

    或者

    1. 所引用的类在另一个项目中,并且该依赖项未添加到我的项目的Gradle构建文件中。因此,我使用以下内容添加了依赖项:

      编译项目(':anotherProject')

    然后它就可以工作了。希望对你有帮助!


    4
    如果Eclipse Java构建路径映射到7、8,并且在项目pom.xml Maven属性中提到了比7、8更高的Java版本(9、10、11等),则需要在pom.xml文件中进行更新。在Eclipse中,如果Java映射到Java版本11,在pom.xml中它映射到Java版本8。通过以下步骤在Eclipse IDE中更新支持Java 11: 帮助->安装新软件-> 在“工作区”中粘贴以下链接http://download.eclipse.org/eclipse/updates/4.9-P-builds 或者 添加(Popup窗口将打开)-> 名称:Java 11支持 位置:http://download.eclipse.org/eclipse/updates/4.9-P-builds 然后按照下面的方法在文件的Maven属性中更新Java版本。
    <java.version>11</java.version>
    <maven.compiler.source>${java.version}</maven.compiler.source>
    <maven.compiler.target>${java.version}</maven.compiler.target>
    

    最后,在项目上右键,选择“Debug as” -> “Maven clean”,“Maven build” 步骤。


    3

    我也遇到了这个错误。(我在谷歌上搜索后被引导到了这个页面)

    问题:我正在从项目B中定义的一个类中调用项目A中定义的一个静态方法。我遇到了以下错误:

    error: cannot find symbol
    

    解决方案:我通过先构建定义该方法的项目,然后构建调用该方法的项目来解决这个问题。


    是的,如果您决定将一些可重用的函数从当前包移动到公共工具包中,然后忘记在调用当前包中的函数之前编译公共包,那么就会发生这种情况。 - buildingKofi

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接